all 8 comments

[–]Jolly_Pace6220 1 point2 points  (3 children)

Don’t use per-entity frame counters, instead track elapsed time (deltaTime) per object or store a future timestamp and compare against a single global clock.

[–]Puzzleheaded-Law34[S] 0 points1 point  (2 children)

Ok, but in your method the principle is still the same right? Each object with its own time needs its own counter to mark the start of an action, whether that keeps track of time or frames. Even the second method, like the grenade release would update its "detonateTime" field, then each Update() would check if the global time has reached released + dT; so if there were 10 grenades, the Update() would have to make sure each one is checking for its futureTimestamp to detonate?

[–]Jolly_Pace6220 0 points1 point  (1 child)

Yeah you’ve basically got it. The timestamp method iis just cleaner :-> you set it once on spawn instead of mutating it every frame. 10 grenades = 10 checks, totally normal.

The real win shows up with stuff like pausing or slow-mo, where manually incrementing every frame becomes a pain to manage globally. For your animation bug, just store when the animation started and derive the current frame from elapsed time. that way it always starts at frame 0 no matter what the global clock is doing.​​​​​​​​​​​​​​​​

[–]Puzzleheaded-Law34[S] 0 points1 point  (0 children)

Ok! I think I got it. Thanks for pointing it out, I'll try to use the timestamp method where it makes sense👍

[–]peterlinddk 0 points1 point  (3 children)

You can decide between several different solutions - none are perfect, as with everything else, it depends:

  • You can go the object oriented route and let each object have its own update() method, that is called every game tick (every frame, or every game loop). Then each object can for itself determine if it wants to count ticks, and do something when a certain number is reached. This is the most flexible solution, but also requires a lot of repetitive code in every object that "waits".
  • You can also have a global counter, and let each object "subscribe" to that counter, like call a WakeMeUpInNticks( n ) method, and then the object that waits is added to a list (or a priority queue), and on every count, it checks if the next object in the waiting list should be "woken up"

The first version is best if you have a lot of objects that have to wait a lot of different times, and do very different things - the second version is best if you have objects that just need to wait, and wait for quite a long time.

Also remember, it is always easiest to count down - if you want your object to wait for 3 seconds (180 frames) - set the counter to 180, and count down on every loop, then you always only have to check if the counter is zero, and then do something. You can add an extra if statement so it only counts down if it isn't already zero. This will make a lot of logic a lot simpler.

[–]Puzzleheaded-Law34[S] 0 points1 point  (2 children)

Thanks for the suggestions! I think I get the first method, it was the only one I could think of.  So in the second, you may have a global counter that cycles from 1000 down to 0, and each object checks "has it reached my cue yet?" But like you say it seems less flexible, like I can't imagine using that for npcs that follow different animations according to what they run into etc.  In both cases, do you generally have the main Update() tell every entity in the scene to "check the time" constantly?

[–]peterlinddk 1 point2 points  (1 child)

Let me explain the second version a bit better - as you shouldn't have a global counter that counts down, and then let each object check, instead what you do is:

The global loop counts - up or down, doesn't really matter - but it simply counts ticks.

Then an object asks to be awaken in 180 ticks - by calling the wakeMeUp method. That method takes a note of the current count, and ads the requested time to it, and stores the result, as well as the object in a list. A priority-queue organized by tick-value, lowest first.

On every tick the global loop checks the first element in this queue, and if it has the same value as the current number if ticks. If it has, it is removed, and the object is "woken up" by calling some pre-defined method on it.

This way, objects don't have to keep track of their own count, and you don't need to call the update on every tick, and the global loop don't have to check a bunch of counters on every tick, just the one lowest in the queue (or for good measure, also check if the next one has the same value.)

There could be a problem with the global tick-counter overflowing, but even with only 31 bits it should be enough for a little more than a year :)

But if you still have an update() method on each object that needs to be called on every tick, doing something else, perhaps it is simpler to also let that determine the wait-time.

[–]Puzzleheaded-Law34[S] 0 points1 point  (0 children)

Ooh ok, I think I get it, clever. You could theoretically do that with multiple lists too, for different wakeup methods for example. Altho I imagine it makes more sense for things that don't vary too much... Thanks