arcade
arcade copied to clipboard
Time enhancements
Enhancement request:
Make the timer in arcade better.
What should be added/changed?
P.S: dt stands for delta_time
- clamped dt
- paused dt
- slow-mo - give both a delta_time and a real_delta_time?
- expose
Timeobject with these additional values, to avoid polluting update function signatures w/a bunch of argstime.delta_timeandtime.real_delta_time,time.set_slowmo()
What would it help with?
- There are huge dt's after debugging.
- Loading games causes huge dt.
- Makes slow-mo effect easy.
Should we apply the same scaling for arcade.schedule() / arcade.schedule_once()? Or should we deliberately keep them on real-world time for simplicity?
Could be tricky:
arcade.schedule_once(thing, 5) # run in 5 seconds
after 2s, we still have 3s left to go
Set slowmo factor of 0.5 so that things run at half-speed.
The scheduled function should thus run after 6s not 3s
Lends credence to @gran4's idea that this be implemented closer to pyglet. Maybe arcade implements its own subclass of pyglet's Clock?
Should we allow scheduled items to bypass time scaling, so they follow real-world time no matter what?
Another option: fixed delta_time. Effectively, your on_update receives a constant value no matter if your computer slows down and cannot maintain the requested framerate.
Linking to #1543
Okay, here is my proposal for how we provide this functionality.
We have a PrimaryClock, which each window has one of. The window also now has a special method called _tick that always runs before any other method and at the same speed as the update_rate (Will need to be rescheduled whenever a view is shown).
Then, at any time, the dev can request the PrimaryClock to make a SubClock. These are ticked by the primary and can have their own delta_time, which could be clamped or any other funky feature.
You can not get the SubClock reference directly, but I'll figure out some not too expensive way to set the properties of the clock. (maybe a named tuple? with PrimaryClock implementing __set_item__, we will see).
Finally, there are ClockImages, which have a label and all instances with the same label point to the same SubClock.
The reason ClockImages and SubClocks are different is so you can manipulate what a bunch of different systems see as the same time without screwing with others.
Here is an example say your player has a slow downtime ability. When it activates, you don't want the player's clock to change, but you want the enemies/environment to slow down. So you have a gametime SubClock, and two ClockImages player, and gameenviroment. For 99% of the time, both ClockImages look at gametime, but when you activate the slowdown time ability, you make a new temp SubClock and tell only the gameenviroment ClockImage to switch. If you've coded your game correctly, this should slow down everything, but your player and none of your systems need to be aware of anything but their ClockImage delta_time.
While initially, it would appear to be an over complication. It is actually a wonderful bit of abstraction, so devs dont need to reason with the clock system when developing an unrelated system.
in other words, "It Just Works".
My game_coro implements a similar clock/subclock API. It's called "context" but similar idea: an object that tracks time and exposes it on various properties.
For example, coroutines can create a sub-clock that starts tracking local time starting at zero: https://github.com/cspotcode/py_game_coroutines/blob/ab56fbbcdffe405c111f64ee9a001c8428ded09e/example/example.py#L27
https://github.com/cspotcode/py_game_coroutines https://pypi.org/project/game-coro/
Has anything been done on this? If not, I like DragonMoffon's idea of a game clock being split off. One way you could implement it for speed, is to have 2 layers of clocks(It will be easier to show through code). Basically, each system/ or entity in the world chooses 1 clock: the player clock or the game clock. These 2 clocks are different objects(so we should give them different names since it is not the clock, it just references the clock.). These "clocks" will reference the main clock but not actually do the timing. The main clock will do the timing and the "clocks" will just reference the main clock.
When we need to slow mo, we can just change the reference of the "game clock" and change back to make it normal. Theoretically using this method, you could have as many clocks as you want(although I'm not sure why you would do that). This solves the speed(a lot) and complication(a little) problem since you do not need to change each reference one by one.
For the references to the clock maybe the names could be: game_clock_reference and player_clock_reference?
What if we just let people create their own references so it does not have to be in the arcade code? We can still make it easier though by adding a function for splitting off a clock
It could be something like this where the player could just handle it within an arcade framework Basically, this program would theoretically toggle slow mode every 1000 times the loop runs. We need to set the objects to reference one thing and that one thing to reference the actual clock, so we can change the clock without changing every reference of every entity. Also, there could definitely be better names for these
i = 0
slow_mode = False
player_clock = clock_reference()
game_clock = clock_reference()
player_clock.reference = Clock()
game_clock.reference = player_clock.reference
player.clock = player_clock
for animal in animals:
animal.clock = game_clock
while True:
i += 1
if i > 1000:
i = 0
if slow_mode:
game_clock.reference = player_clock_reference
else:
game_clock.reference = player_clock_reference.new_clock(speed=0.5)
The new clocks in 3.0 should help here