1. Game Development
  2. Unity 3D
Gamedevelopment

How to Build a Prince-of-Persia-Style Time-Rewind System, Part 2

This post is part of a series called How to Build a Prince-Of-Persia-Style Time-Rewind System.
How to Build a Prince-of-Persia-Style Time-Rewind System, Part 1
Final product image
What You'll Be Creating

Last time we created a simple game where we can rewind the time to a previous point. Now we will solidify this feature and make it much more fun to use.

Everything we do here will build on the previous part, so go check it out! As before, you will need Unity and a basic understanding of it.

Ready? Let's go!

Record Less Data and Interpolate

Right now we record the positions and rotations of the player 50 times a second. This amount of data will quickly become untenable, and this will become especially noticeable with more complex game setups and mobile devices with less processing power.

But what we can do instead is only record 4 times a second and interpolate between those keyframes. That way we save 92% of the processing bandwidth, and get results that are indistinguishable from the 50-frame recordings, as they play out within fractions of a second.

We'll start by only recording a keyframe every x frames. To do this, we first need these new variables:

The variable keyframe is the frame in the FixedUpdate method at which we will record the player data. Currently, it is set to 5, which means every fifth time the FixedUpdate method cycles through, the data will be recorded. As FixedUpdate runs 50 times per second, this means 10 frames will be recorded per second, compared to 50 before. The variable frameCounter will be used to count the frames until the next keyframe.

Now adapt the recording block in the FixedUpdate function to look like this:

If you try it out now, you will see that the rewinding takes part in a much shorter time than before. This is because we recorded less data, but played it back at regular speed. Now we need to change that.

First, we need another frameCounter variable to keep track not of recording the data, but of playing it back.

Adapt the code that restores the player's position to utilize this the same way we record the data. The FixedUpdate function should then look like this:

When you rewind time now, the player will jump back to their previous positions, in real time!

That's not quite what we want, though. We need to interpolate between those keyframes, which will be a bit trickier. First, we need these four variables:

Those will save the current player data and the one from the recorded keyframe before that so that we can interpolate between those two.

Then we need this function:

This will assign the corresponding information to the position and rotation variables which we will interpolate between. We need this in a separate function, as we call it in two different spots.

Our data-restoring block should look like this:

We call the function to get the last and second to last information sets from our arrays whenever the counter reaches the keyframe interval we have set (in our case 5), but we also need to call it on the first cycle when the restoring is happening. This is why we have this block:

In order for this to work, you also need the firstRun variable:

And to reset it when the space button is lifted:

Here is how the interpolation works:

Instead of just using the last keyframe we saved, this system gets the last and the second-to-last and interpolates between them. The amount of interpolation is based on how far between the frames we currently are. 

This all happens via the Lerp function, where we add the current position (or rotation) and the previous one. Then the fraction of the interpolation is calculated, which can go from 0 to 1. Then the player is placed at the equivalent place between those two saved points, for example, 40% on the route to the last keyframe.

When you slow it down and play it frame by frame, you can actually see the player-character move between those keyframes, but in gameplay, it's not noticeable.

And thus we have greatly reduced the complexity of the time-rewinding setup and made it much more stable.

Only Record a Fixed Number of Keyframes

Now that we have greatly reduced the number of frames we actually save, we can make sure we don't save too much data.

Right now we just pile the recorded data into the array, which will not do long-term. As the array grows, it will become more unwieldy, access will take longer amounts of time, and the entire setup will become more unstable.

In order to fix this, we can institute code that checks if the array has grown over a certain size. If we know how many frames per second we save, we can determine how many seconds of rewindable time we should save, and what would fit our game and its complexity. The somewhat complex Prince of Persia allows for maybe 15 seconds of rewindable time, while the simpler setup of Braid allows for unlimited rewinding.

What happens is that once the array grows over a certain size, we remove the first entry of it. Thus it only stays as long as we want the player to rewind, and there is no danger of it becoming too large to use efficiently. Put this in the FixedUpdate function after the recording and replaying code.

Use a Custom Class to Hold Player Data

Right now we record the player positions and rotations into two separate arrays. While this does work, we have to remember to always record and access the data in two places at the same time, which has the potential for future problems.

What we can do, however. is create a separate class to hold both of these things, and possibly even more (if that should be necessary in your project).

The code for a custom class to act as container for the data looks like this:

You can add it to the TimeController.cs file, right before the class declaration starts. What it does is provide a container to save both the position and rotation of the player. The constructor method allows it to be directly created with the necessary information.

The rest of the algorithm will need to be adapted to work with the new system. In the Start method, the array needs to be initialized:

And instead of saying:

We can save it directly into a Keyframe object:

What we do here is add the position and rotation of the player into the same object, which then gets added into a single array, which greatly reduces the complexity of this setup.

Add a Blurring Effect to Signify That Rewinding Is Happening

We drastically need some kind of signifier telling us the game is currently being rewound. Right now, we know this, but a player might be confused. In such situations it is good to have multiple things telling the player that rewinding is happening, like visually (via the entire screen blurring a bit) and audio (by the music slowing down and reversing).

Let's do something similar to how Prince of Persia does it, and add some blurring.

A screenshot of the time-rewinding from Prince of Persia The Forgotten Sands
Time-rewinding from Prince of Persia: The Forgotten Sands

Unity allows you to add multiple camera effects on top of each other, and with some experimenting you can make one that fits your project perfectly.

Before we can use the basic effects, we need to import them. To do this, go to Assets > Import Package > Effects, and import everything that is offered to you.

View of the effects-menu in Unity 3D

Visual effects can be added directly to the main camera. Go to Components > Image Effects and add a Blur and a Bloom effect. The combination of those two should provide a nice effect for what we are going for.

View of the effects-inspector in Unity 3D
These are the basic settings. You can adjust them to better suit your project.

When you try it out now, the game will have this effect on all the time.

A screenshot of the effect in use

Now we need to activate it and deactivate it respectively. For that, the TimeController needs to import the image effects. Add this line to the very beginning:

To access the camera from the TimeController, add this variable:

And assign it in the Start function:

Then add this code to activate the effects while rewinding time, and have them activated otherwise:

When you press the space button, you now not only rewind the scene, but you also activate the rewind effect on the camera, telling the player that something is happening.

The entire code of the TimeController should look like this:

Download the attached build package and try it out!

Conclusion

Our time-rewind game is now much better than before. The algorithm is noticeably improved and uses 90% less processing power, it is much more stable, and we have a nice signifier telling us that we are currently rewinding time.

Now go make a game using this!

Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.