dorien
dorien

Reputation: 5407

How to have Unity do something at a large number of fixed timings

I have a List of Timings (in seconds), at which a function needs to be executed. I tried a very naive way but it doesn't seem to work, most likely because Time.fixedTime isn't always exact. Any better ideas on how to execute something on 1000 determined (but not equally spread out) times?

if (Timings.Contains(Time.fixedTime)){
    //do something
}

Upvotes: 0

Views: 1352

Answers (5)

joshwilsonvu
joshwilsonvu

Reputation: 2699

You may be able to use Invoke in a recursive manner. Like this:

void Start() {
    Timings.sort();
    Timings.reverse(); // use like a stack, removing last elements in order
    InvokeRecursive();
}

void InvokeRecursive() {
    if (Timings.Count <= 0) return;
    float time = Timings[Timings.Count - 1];
    Timings.remove(Timings.Count - 1);
    // do stuff
    Invoke("InvokeRecursive", time - Time.realtimeSinceStartup);
}

Upvotes: 0

Programmer
Programmer

Reputation: 125435

If you want to continuously do something for a specific amount of time,you have to use Time.deltaTime in a coroutine. Increment a float value from 0 with Time.deltaTime until it reaches the time you want to do that thing.

IEnumerator executeInWithFixedTiming(float time)
{
    float counter = 0;

    while (counter <= time)
    {
        counter += Time.deltaTime;

        //DO YOUR STUFF HERE
        transform.Rotate(Vector3.right * Time.deltaTime);

        //Wait for a frame so that we don't freeze Unity
        yield return null;
    }
}

You can start as many tasks as possible like below. The example run the code for 5 seconds:

StartCoroutine(executeInWithFixedTiming(5));

You can also extend this function and make it take a parameter of what to do in that coroutine as Action. You can then pass in the code to run inside that function too. Not tested but should also work.

IEnumerator executeInWithFixedTiming(Action whatToDo, float time)
{
    float counter = 0;

    while (counter <= time)
    {
        counter += Time.deltaTime;
        whatToDo();
        //Wait for a frame so that we don't freeze Unity
        yield return null;
    }
}

then use it like this:

StartCoroutine(executeInWithFixedTiming(
delegate
{
    //DO YOUR STUFF HERE
    transform.Rotate(Vector3.right * Time.deltaTime);
}, 5));

EDIT:

The thing is, I don't want to continuously do it for X seconds, but only once at each point of Timings

You mentioned that the timer is sorted so a for loop to loop through it and while loop to wait for the timer to finish should do it.

List<float> timer = new List<float>();

IEnumerator executeInWithFixedTiming()
{
    float counter = 0;

    //Loop through the timers
    for (int i = 0; i < timer.Count; i++)
    {
        //Wait until each timer passes
        while (counter <= timer[i])
        {
            counter += Time.deltaTime;
            //Wait for a frame so that we don't freeze Unity
            yield return null;
        }

        //TIMER has matched the current timer loop.
        //Do something below
        Debug.Log("TIMER REACHED! The current timer is " + timer[i] + " in index: " + i);
    }

    //You can now clear timer if you want
    timer.Clear();
}

Just start the coroutine once in the Start function and it should handle the timer. StartCoroutine(executeInWithFixedTiming());. You also also modify it with the second example in this answer to make it take a parameter of each code to execute.

Note:

In Unity, it's better to time something with Time.deltaTime. It's the most accurate way of timing that I know about. Other Unity variables tend to loose their accuracy over time.

Upvotes: 2

BlackMB
BlackMB

Reputation: 228

At first you need sort your list, then do the rest:

float time = 0;
Int count = 0;
Lisr<float> timeList;
void Update()
{
  time += Time.deltaTime;
  If(timeList.count > count)
{
  If(time >= timeList[count])
  {
    //do something
    ++count;
  }
}
}

Upvotes: 0

brehonia
brehonia

Reputation: 66

Instead of checking against the exact time, check whether that time has passed. This will account for the inaccuracy of fixedTime and your code will fire on the correct frame or the one after it. If that's not timely enough for what you're doing, try some of the more accurate timing methods outlined in the other answers.

Keep your collection of timings sorted and remove them as they fire. Then you can keep checking the first (earliest) instead of iterating through them all.

if (Time.fixedTime <= Timings[0])

Upvotes: 0

Weyland Yutani
Weyland Yutani

Reputation: 4960

You can use Time.realtimeSinceStartup to get the absolute time since the game started and use that with a reference point to get the elapsed time since X.

If you need to compare a lot of timings against that I suggest sorting them into order so the earliest is first. That way you only ever need to check if the earliest timing has happened, if it hasn't you can skip any more checks.

float refTime;
Queue<float> timings;
void Update() {
    var elapsed = Time.realtimeSinceStartup - refTime;
    //test against the earliest timing
    if (timings.Count > 0 && refTime > timings.Peek()) {
        //do stuff
        //remove earliest timing from queue
        timings.Dequeue();
    }
}

If your timings happen less often than once a frame you can get away without checking the next item in the queue until next frame.

Upvotes: 0

Related Questions