Forum rules - please read before posting.

Setting exact NPC position in scene based on in-game clock

edited May 2021 in Extending the editor

This is the first thing I ever wrote in C#, and I haven't looked at it in a couple of years, so it is very hacky and badly coded (but it works!). I thought I'd share the idea anyway.

The context

I set up an Adventure Creator cutscene running in the background to increase my Time Master global variable every second. For my game in particular, I increase this variable by 5 (so 1 real second = 5 in-game seconds). For every loop of this cutscene, it runs a second cutscene that checks the current time and fires off different events in the scene (e.g. if time == 1200, an NPC is set to follow a path that leads from point A to point B ).

My OnStart/OnLoad cutscene also checks the current time and teleports NPCs to their respective positions before the scene fades in.

The problem

All of this is trivial to do with stationary NPCs. But what happens when the player enters the scene exactly when an NPC is supposed to be moving along a path from A to B? You need (1) a way to know where exactly the NPC should be, (2) a way to teleport them there, (2) a way to set them on the path from that point and not the beginning.

The solution

These two scripts do just that.

ScheduledPath allows you to select a path and input a readable start time (in 24h format), the NPC speed and your Clock variable increment (in my case, it would be 5). Based on this data it outputs the length of the path, how long it will take for the NPC to follow it to the end, and the estimated time of arrival. This information makes it easier for you to set up your cutscenes.

PathsSolution does the actual magic. All you need to do is add it to the same object as ScheduledPath. It takes the start time you entered, the finish time it calculated based on NPC speed and path length, and interpolates the exact position where the NPC should be when the player enters the scene halfway through. It does this by cutting the parts of the path that are supposed to have been walked by the time the player enters the scene, and moves the starting point of the path there.

This means that your OnStart/OnLoad cutscene only needs to check for the time the NPC is supposed to be walking the path (and the SchedulePath script outputs this data for you!), and if true, tell the NPC to follow that path (check "Teleport player to start?" to place them at the recalculated start).

using UnityEngine;
using AC;

[ExecuteInEditMode]

public class ScheduledPath : MonoBehaviour {

    public int startHour;
    public int startMinute;
    public int startSecond;
    public int timeMaster;

    public float speed = 10;
    public int timeIncrement = 5;
    public float pathLength;

    public float realTimeToDestination;
    public int timeMasterAtDestination;
    public string timeAtDestination;

    public Paths path;


    void Update () {

        timeMaster = ((startHour * 60 * 60) + (startMinute * 60) + startSecond);

        pathLength = path.GetTotalLength();
        realTimeToDestination = pathLength / speed;
        timeMasterAtDestination = (timeMaster + ((int)realTimeToDestination)*timeIncrement);

        int timer = timeMasterAtDestination;
        int hours = Mathf.FloorToInt(timer / 3600F);
        int minutes = Mathf.FloorToInt((timer - hours * 3600) / 60F);
        int seconds = Mathf.FloorToInt((timer - hours * 3600 - minutes * 60));
        string niceTime = string.Format("{0:00}:{1:00}:{2:00}", hours, minutes, seconds);

        timeAtDestination = niceTime;



    }

}

And here's the other one:

using System.Collections.Generic;
using UnityEngine;
using AC;

public class PathsSolution : MonoBehaviour {

    [HideInInspector]
    public Paths path; // The path to be controlled.
    [HideInInspector]
    public List<Vector3> nodes; // List of path nodes to be manipulated.
    [HideInInspector]
    public List<Vector3> origin; // Copy of original list of path nodes, which won't be changed.

    [HideInInspector]
    public float speed; // Character speed.
    [HideInInspector]
    public float startTime; // Time of the day when the character will start following the path (in Master Time format).

    [HideInInspector]
    public int currentTime; // Time of the day, in sync with Time Master variable.

    [HideInInspector]
    public float[] distance; // Calculated distance between two path nodes.
    [HideInInspector]
    public float[] realTimeToNextNode; // Time between two nodes in real-life seconds.
    [HideInInspector]
    public float[] interpolantT; // Number between 0 and 1, which reflects the position of current time between the start time at one node and the arrival time at the next node.
    [HideInInspector]
    public float[] timeAtNextNode; // Estimated time of arrival at next node (in Master Time format).
    [HideInInspector]
    public float[] realTimeToNextNodeSum; // Time taken from node 0 (the start of the path) to the next node.
    [HideInInspector]
    public int timeIncrement;

    public Vector3 position;

    void Start() 
    {
        path = gameObject.GetComponent<ScheduledPath>().path;
        nodes = gameObject.GetComponent<ScheduledPath>().path.nodes;
        origin = new List<Vector3>(nodes);
        speed = gameObject.GetComponent<ScheduledPath>().speed;
        startTime = gameObject.GetComponent<ScheduledPath>().timeMaster;
        timeIncrement = GlobalVariables.GetIntegerValue(24);

        realTimeToNextNode = new float[origin.Count];
        timeAtNextNode = new float[origin.Count];
        realTimeToNextNodeSum = new float[origin.Count];
        interpolantT = new float[origin.Count];
        distance = new float[origin.Count];
        //  }

        //      void Update()
        //      {
        currentTime = AC.GlobalVariables.GetIntegerValue(12);

        for (int i = 0; i < origin.Count; i++)
        {
            distance[i] = Vector3.Distance(origin[i], origin[i + 1]);
            realTimeToNextNode[i] = distance[i] / speed;

            for (int x = 0; x < origin.Count; x++)
            {
                switch (x)
                {
                    case 0:
                        realTimeToNextNodeSum[0] = realTimeToNextNode[0];
                        break;
                    default:
                        realTimeToNextNodeSum[x] = realTimeToNextNode[x] + realTimeToNextNodeSum[x - 1];
                        break;
                }                   
            }

            timeAtNextNode[i] = startTime + (realTimeToNextNodeSum[i] * timeIncrement);

            switch(i)
            {
                case 0:
                    interpolantT[i] = ((float)currentTime - startTime) / (timeAtNextNode[i] - startTime);
                    break;
                default:
                    interpolantT[i] = ((float)currentTime - (startTime + realTimeToNextNodeSum[i - 1] * timeIncrement)) / (timeAtNextNode[i] - (startTime + realTimeToNextNodeSum[i - 1] * timeIncrement));
                    break;
            }

            position = Vector3.Lerp(origin[i], origin[i + 1], interpolantT[i]);

            if (currentTime < timeAtNextNode[i])
            {
                switch (i)
                {
                    case 0:
                        path.transform.SetPositionAndRotation(position, Quaternion.identity);
                        break;
                    default:
                        nodes[i] = position;
                        nodes[i - 1] = position;
                        path.transform.SetPositionAndRotation(position, Quaternion.identity);
                        break;
                }
                break;
            }
            else
            {
                switch (i)
                {
                    case 0:
                        path.transform.SetPositionAndRotation(nodes[i + 1], Quaternion.identity);
                        break;
                    default:
                        nodes[i] = nodes[i + 1];
                        nodes[i - 1] = nodes[i];
                        path.transform.SetPositionAndRotation(nodes[i + 1], Quaternion.identity);
                        break;
                }
            } 
        }
    }
}

Comments

  • Just a side note: your path can have however many nodes you want, so you can draw pretty complex paths, and this script will still shorten it to the appropriate position based on the time you entered the scene.

  • Hi, these scripts are exactly what I am looking for! I'm trying to use them in my project, but I am a newbie here and can't figure out how to use them or where to attach them.
    Could you give me a little help please?

    My current situation:

    I created a path object my NPC should move along, and another path object with these two scripts attached.

    Also, I set up onStart/onLoad cutscenes to check if the NPC should be moving; for example, I'd like to move it during Time Master = 6~33, so I set the Action list that checks if Time Master is more than 5 and less than 34, and run the 'Character: move along path' action.
    Within it, I set 'Path to follow:' as the path object with these scripts. (I guess it is wrong?)

    Could you point out if I missed something, or misunderstood some part?
    Thank you.

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.