Failsafe Devlog III

Hello everyone! I’m Evan Hemsley, a designer / programmer on Failsafe.

Once our time trial system was implemented, it seemed like an obvious decision to add some sort of replay function. It’s nice to be able to watch a really great run that you pulled off. One of the best things about racing games is being able to race against ghosts. Improving your time trial score is like competing against yourself – replay ghosts make that sense of competition a lot more tangible. Ghosts let you compete against other players as well.

How do you go about implementing a replay function?

The easiest way to implement a replay function is to simply capture video and then play it back. Unfortunately this has huge drawbacks in terms of file size – if the player’s rendering resolution is high enough, even short replays would take up hundreds of megabytes of space. Furthermore, if the player wants to race against themselves, there is no way to accomplish this with recorded video.

Essentially, any game is just a type of simulation environment – the program exists in a certain state, and the program’s state is updated based on various rules driven by inputs from the player. So, when you want a replay, what you really want is for the character’s state to change in the same manner as it changed when you were playing throu gh the level.

Our first attempt: on each frame, we capture each element of the player’s input – which buttons they were pressing, holding down, or releasing, and the axes of the joysticks. Then, we just replay those inputs in the same order as they were entered. Unfortunately, our player character’s movement is driven by the Unity physics engine, and the Unity physics engine is not deterministic – this means that even if you perform the exact same inputs on two different runs, the behavior of the player character might differ between the two runs. With this system, our replays would usually desynchronize and become inaccurate.

To get around this, we record the player’s input except for movement-related inputs. We then store the input data along with the player object’s position and rotation data on each frame. On playback, we set the player’s position and rotation on each frame, and update the player’s state (jumping, swinging, etc.) by replaying the button presses. Voila! A perfect replay!