How to accommodate for unstable physics delta’s for timing based attacks/effects?
My game consists of a player and a zombie(s). If a player is in reach the zombie starts charging a hit, this takes 1.0 seconds and once the timer is complete it performs a hit and start the cooldown timer for 0.5 seconds. Once the cooldown timer is complete we repeat the cycle depending on some conditions. I currently have implemented this logic through timers and callbacks (with process_in_physics=true) but I am concerned that an unstable physics delta could mess this up as a timer might trigger later than expected, causing the next hit(s) and cooldown(s) cycle being skipped. I am also concerned that this might cause players to be out of sync when in Multiplayer.
To be specific my concern with using a timer for this stems from this section in SceneTree.create_timer: “If process_in_physics is true, the timer will update at the end of the physics frame, instead of the process frame.”
Which tells me that its possible to skip over a cycle of hitting and cooldowns when the delta is large.
Alternative Implementation
I have read a few posts that mention using delta from _physics_process is the only way to get the most accurate timing information.
Would it make sense to process the the timings manually using the delta value in _physics_process?
What is the proper way of handling timings for cases like this?
(sorry if the question doesn’t really make sense - I am new to godot and game engines in general)
Delta will typically be very small. If for some reason the shoot into ballpark of your cooldown times, the game will be pretty much unplayable due to low framerate, and you’ll have bigger problems to worry about than cooldowns - bringing the framerate up to bearable levels.
With normal framerates ~60 fps, timers will work as expected for common timing tasks.
In general I agree that the framerate will be terrible, but in case of sudden but temporary spikes in delta (possibly caused by external processes) I want any timing based to be performed as if there was no spike in similar fashion to move_and_slide.
Ideally I want to be able to gracefully handle time based events even in the case of funny delta values to keep the gameplay consistent across devices especially in multiplayer.
I know no approach is flawless but I would still prefer something that can at least handle such cases better than just skipping over timings based events. Then again maybe this is the norm for handling these kind of cases and I not aware of it.
move_and_slide() operates with delta as well. Ditto for all timers, animation tracks and tweens. You can’t go around delta in a game engine. It’s the smallest time slice there is. If you detect an unacceptably high delta, you can simply auto-pause the game.
I know that move_and_slide operates with delta. My point was that the movement performed by the function will move using the delta so regardless of its value the movement will be correct.
What I want to be able to do is perform timing based events consistently even if the delta does spike which causes a delay when SceneTreeTimer.timeout is emmited. What I am looking for is a way to avoid that or at least improve the current implementation.
I’m not trying to find a way to avoid using the delta. I just want a way to perform this timing based event consistently even when the delta spikes.
The “auto-pause” you mentioned would work but I would prefer finding a way to handle the unusual delta rather than to pause the game if possible. I might be asking too much here if so let me know.
Well analogus to move_and_slide the timing will be correct as well. If the delta spike happens, timing will overshoot. move_and_slide will overshoot as well in such cases. There’s nothing you can do about it as the update code, be it your scripts or engine native code, can happen only once per time step. And delta is the minimal time step you can get.
I think I am having trouble explaining what I mean or I am lacking some fundamentals on the topic.
Regardless; Your explanations have been helpful, I don’t think there is a way to perform accurate timings based events in the way that I want to. Still open to other ideas other than timers and signals.
If something in your game needs to happen in regular short intervals, and the delta overshoots the interval (or even several intervals), simply subtract your interval from delta until all of the delta is consumed, and put the last remainder back in the counter. That way you’ll be able to account for multiple counter expirations that needed to “happen” in the last time slice if the interval or remaining time is shorter than anomalously large delta
var interval = 0.03
var time_to_event = interval
func _process(delta):
time_to_event -= delta
while time_to_event < 0.0:
print("thing happened")
time_to_event += interval
Note that this will properly account for the remaining/overshoot time even if only a single expiration “happened” during the last time slice as time_to_event will be set to delta remainder instead reset to zero. This makes it more precise for perpetual events than timers/signals as you always keep track of the remainder and you won’t accumulate error caused by ignoring that remainder as is the case when using timers that send signals.
Thanks! This is the kind of solution I meant by “Would it make sense to process the the timings manually using the delta value in _physics_process?”
After creating the post I actually got a working implementation. My concerns where if this approach had any pitfalls. From what I can tell its a better approach than timers for my use case so delta tracking it is for me.
Yes it’s considerably better if your use case involves relatively short intervals that happen perpetually. It completely eliminates time reminder/error accumulation over longer time periods.
The drawback is that you need to process your custom timers in script code. It’s always preferable to leave timing to native code. But if you don’t have a massive amount of timers to track in such way - it’s of no concern.