I think these kinds of games usually separate their simulation from their regular update and render loops. That allows them to rapidly update the sim while keeping a normal framerate for rendering.
Two primary approaches to increase game speed come to mind:
- Increase delta time passed to your sim_update()
- Increase the number of times you call sim_update()
The first tends to be easier (and it's likely similar to using Engine.time_scale
). However, it can cause issues on fast mode where entities may make large leaps in their individual updates that modify the outcome of the sim.
This comment from Limeox explains:
Say some object goes invincible for 0.5 seconds and is about to take two damaging hits, one at 0.45s and one at 0.55s. If your delta is 0.01 seconds, the first hit will be blocked and the second one won't, as the effect has worn off by then. If it is 0.2 seconds, the effect may now block both, or neither, depending on when it is removed relative to the hits. Or you could have the "second" hit blocked, because it simulates first, then the effect is removed, then the "first" one hits?
By just simulating more ticks, this wouldn't happen. Of course, by pushing those occurrences closer together (hits at 0.499s and 0.501s), you can construct similar issues. But, and this is the important point, the behavior will be identical regardless of game speed. After all the result doesn't need to be 100% correct (especially in the case of physics, you'd go insane). It just needs to be 100% consistent at all speeds.
So instead, you can do #2 and update your sim more frequently to ensure the result of each update is the same, but occurs at a faster rate. Of course, this means your sim update must be fast enough that you can afford to run it multiple times in 33ms to still hit 30 FPS on fast mode. But by decoupling it from animations, fx, sound, and any other presentation elements, you can likely avoid a great deal of cpu time.
For gdscript, that means you'd have something like this:
@export_range(1, 5) var game_speed := 3
var sim
func _process(delta):
for i in game_speed:
sim.update(delta)
sim.apply_to_world.emit()
Everything processed in sim
wouldn't use _process
or _physics_process
. Likely, you wouldn't want to rely on a general physics simulation at all. apply_to_world
pushes the sim changes out so everything can grab the latest state for presentation.