I am working on an arcade-style 2D shmup and recently started implementing a replay system that allows players to view their runs of the game.
I use the technique of recording input each FixedUpdate()
and rerunning the game as a simulation, reading the saved input each frame and processing it in the core game loop as if it was real-time input from the player.
This requires an extremely deterministic game, and in order to get perfectly synced movement and scoring in the replay, I have had to shift to a paradigm of using exclusively FixedUpdate()
and Time.fixedDeltaTime
.
I don’t mind this, as I now have 100% reliable replays with no desyncing.
My question concerns whether some of the techniques I used to port my game to the deterministic FixedUpdate
paradigm are problematic, or if there are better ways to achieve the same effect.
For example, previously I relied heavily on coroutines yielding using WaitForSeconds()
. This method is underpinned by normal Update()
loop and so introduced indeterminism into the game leading to desyncs.
To fix it, I still use coroutines, but any time I need to wait, I do it by waiting for n number of calls of FixedUpdate()
. For example,
for (int i = 0; i < 60; i ++)
{
yield return new WaitForFixedUpdate();
}
This seems to be working just fine to keep the game deterministic, but I wonder if there’s a more straightforward way, or if there are potential issues this type of technique might cause?
In general, are there any other techniques or pitfalls I need to be aware of to keep my game deterministic enough for perfectly synced replays?