1. Game Development
  2. Unity 3D
Gamedevelopment

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

by
Difficulty:IntermediateLength:MediumLanguages:
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 2
Final product image
What You'll Be Creating

In this tutorial, we'll build a simple game where the player can rewind progress in Unity (it can also be adapted to work in other systems). This first part will go into the basics of the system, and the next part will flesh it out and make it much more versatile.

First, though we'll take a look at what games use this. Then we'll look at the other uses for this technical setup, before ultimately creating a small game that we can rewind, which should give you a basis for your own.

A demonstation of the basic functionality
A demonstation of the basic functionality

You will need the newest version of Unity for this, and should have some experience with it. The source code is also available for download if you want to check your own progress against it.

Ready? Let's go!

How Has This Been Used Before?

Prince of Persia: The Sands of Time is one of the first games to truly integrate a time-rewinding mechanic into its gameplay. When you die you do not just have to reload, but can rather rewind the game for a few seconds to where you were alive again, and immediately try again.

Prince of Persia The Forgotten Sands The Sands Of Time Trilogy integrates time-rewinding beautifully into its gameplay and avoids immersion-breaking quick-reloading
Prince of Persia: The Forgotten Sands. The Sands Of Time Trilogy integrates time-rewinding beautifully into its gameplay and avoids immersion-breaking quick-reloading.

This mechanic is not only integrated into the gameplay, but the narrative and universe as well, and is mentioned throughout the story.

Other games that employ these systems are Braid, for example, which is also centered around the winding of time. The hero Tracer in Overwatch has a power that resets her to a position a few seconds ago, essentially rewinding her time, even in a multiplayer game. The GRID-series of racing games also has a snapshot-mechanic, where you have a small pool of rewinds during a race, which you can access when you have a critical crash. This prevents frustration caused by crashes near the end of race, which can be especially infuriating.

When you have a fatal crash in GRID you get the chance to rewind the game to a point before the crash
When you have a fatal crash in GRID you get the chance to rewind the game to a point before the crash.

Other Uses

But this system can not only be used to replace quick-saving. Another way this is employed is ghosting in racing games and asynchronous multiplayer.

Replays

Replays are another fun way to employ this feature. This can be seen in games like SUPERHOT, the Worms series, and pretty much the majority of sports games.

Sports-replays work the same way they are presented on TV, where an action is showed again, possibly from a different angle. For this not a video is recorded but rather the actions of the user, allowing the replay to employ different camera angles and shots. The Worms games use this in a humorous way, where very comical or effective kills are shown in an Instant Replay.

SUPERHOT also records your movement. When you are done playing around your entire progress is then replayed, showing the few seconds of actual movement that happened.

Super Meat Boy uses this in a fun way. When you finish a level you see a replay of all your previous attempts laid on top of each other, culminating with your finishing run being the last left standing.

The end-of-level replay in Super Meat Boy Every one of your previous attempts is recorded and then played back at the same time
The end-of-level replay in Super Meat Boy. Every one of your previous attempts is recorded and then played back at the same time.

Time-Trial Ghosts

Race-Ghosting is a technique where you race for the best time on an empty track. But at the same time, you race against a ghost, which is a ghostly, transparent car, which drives the exact way you raced before on your best attempt. You cannot collide with it, which means you can still concentrate on getting the best time.

Instead of driving alone you get to compete against yourself, which makes time-trials much more fun. This feature shows up in the majority of racing games, from the Need for Speed series to Diddy Kong Racing.

Racing a ghost in Trackmania Nations This one has the silver difficulty meaning I will get the silver medal if I beat them Note the overlap in car-models showing the ghost isnt corporeal and can be driven through
Racing a ghost in Trackmania Nations. This one has the silver difficulty, meaning I will get the silver medal if I beat them. Note the overlap in car-models, showing the ghost isn't corporeal and can be driven through.

Multiplayer-Ghosts

Asynchronous Multiplayer-Ghosting is another way to use this setup. In this rarely-used feature, multiplayer matches are accomplished by recording the data of one player, who then sends their run to another player, who can subsequently battle against the first player. The data is applied the same way a time-trial-ghost would be, only that you are racing against another player.

A form of this shows up in the Trackmania-games, where it is possible to race against certain difficulties. These recorded racers will give you an opponent to beat for a certain reward.

Movie-Editing

Few games offer this from the get-go but used right it can be a fun tool.Team Fortress 2 offers a built-in replay-editor, with which you can create your own clips.

The replay-editor from Team Fortress 2 Once recorded a match can be seen from any perspective not just the players
The replay editor from Team Fortress 2. Once recorded a match can be seen from any perspective, not just the player's.

Once the feature has been activated you can record and watch previous matches. The vital element is that everything is recorded, not only your view. This means you can move around the recorded game-world, see where everyone is, and have control over time.

How to Build It

In order to test this system, we need a simple game where we can test it. Let's create one!

The Player

Create a cube in your scene, this will be our player-character. Then create a new C#-script calls Player.cs and adapt the Update()-function to look like this:

This will handle simple movement via the arrow keys. Attach this script to the player cube. When you now hit play you should already be able to move around.

Then angle the camera so that it views the cube from above, with room on its side where we can move it. Lastly, create a plane to act as floor and assign some different materials to each object, so that we're not moving it inside of a void. It should look like this:

Angle the camera so it views the cube from above

Try it out, and you should be able to move your cube using the WSAD and arrow-keys.

The TimeController

Now create a new C#-script called TimeController.cs and add it to a new empty GameObject. This will handle the actual recording and subsequent rewinding of the game.

In order to make this work, we will record the movement of the player character. When we then press the rewind button we will adapt the player coordinates. To do so start by creating a variable to hold the player, like this:

And assign the player-object to the resulting slot on the TimeController, so that it can access the player and its data.

Assign the player-object to the resulting slot on the TimeController

Then we need to create an array to hold the player data:

What we will do next is continuously record the position of the player. We will have the position stored of where the player was in the last frame, the position where the player was 6 frames ago, and the position where the player was 8 seconds ago (or however long you will set it to record). When we later hit a button we'll go backward through our array of positions and assign it frame by frame, resulting in a time-rewinding feature.

First, let's save the data:

In the FixedUpdate()-function we record the data. FixedUpdate() is used as it runs at a constant 50 cycles per second (or whatever you set it to), which allows for a fixed interval to record and set the data. The Update()-function meanwhile runs depending on how many frames the CPU manages, which would make things more difficult.

This code will store the player-position of each frame in the array. Now we need to apply it!

We'll add a check to see if the rewind button was pressed. For this, we need a boolean variable:

And a check in the Update()-function to set it according to whether we want to rewind the gameplay:

To make the game run backward, we will apply the data instead of recordingThe new code for recording and applying of the player position should look like this:

And the entire TimeController-script like this:

Also, don't forget to add a check to the player-class to see if the TimeController is currently rewinding or not, and only move when it is not reversing. Otherwise, it might create buggy behavior:

These new lines will automatically find the TimeController-object in the scene on startup and check it during runtime to see if we are currently playing the game or rewinding it. We can only control the character when we are currently not reversing time.

Now you should be able to move around the world, and rewind your movement by pressing space. If you download the build package attached to this article and open TimeRewindingFunctionality01 you can try it out!

But wait, why does our simple player-cube keep looking in the last direction we left them in? Because we didn't get around to also record its rotation!

For that you need another array to keep its rotation-values, to instantiate it at the beginning, and to save and apply the data the same way we handled position-data.

Try it out! TimeRewindingFunctionality02 is the improved version. Now our player-cube can move backward in time, and will look the same way it did when it was at that moment.

Conclusion

We have built a simple prototype game with an already usable time-rewinding system, but it is far from done yet. In the next part of this series we'll make it much more stable and versatile, and add some neat effects. 

Here is what we still need to do:

  • Only record every ~12th frame and interpolate between the recorded ones to save on the huge data load
  • Only record the last ~75 player positions and rotations to make sure the array doesn't become too unwieldy and the game doesn't crash

We'll also take a look at how to extend this system past just the player-character:

  • Record more than just the player
  • Add an effect to signify rewinding is happening (like VHS-blurring)
  • Use a custom class to hold player position and rotation instead of arrays

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