This article explains you the time rewind/reverse mechanic to implement in your 2D/3D game.
Click To Download Full Source Code
Overview of Time Rewind mechanic:
One approach to add this game mechanic is to continuously store the data of all the game objects that are supposed to follow the flow of time. For example, we can store the different positions of a game object as time flows forward. Then, when required, the last stored positions can be accessed and sequentially applied to the game objects to create an illusion of time moving backwards.
Creating the storage using List class:
In this tutorial, we’ll store three separate values, i.e. the position, rotation, and velocity of our game objects. To store them, we’ll use Lists.
Create a C# script and open it. To use Lists, we need to include the System.Collections.Generic namespace.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TimeKeeper : MonoBehaviour
{
}
Now, we create 3 separate Lists to store the data. We’ll also need a few other variables to store the index of the list, a counter, a bool and reference to the Rigidbody component. We also add two int variables to store the maximum time limit for which the mechanic can be used and for maximum size of the list respectively.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TimeKeeper : MonoBehaviour
{
List positionVal = new List();
List rotationVal = new List();
List velocityVal = new List();
int indexVal;
float counter;
bool isRewinding;
int listLimit = 500;
int timeLimitInSeconds =10;
Rigidbody rb;
}
In the Start() method, cache the Rigidbody component.
void Start()
{
rb = GetComponent<Rigidbody>();
}
In the Update() method, we first check for a key press.
The code checks if the key “e” is pressed. If it is not pressed, that means the time is flowing in forward direction and and the counter value is increased. If it is pressed, that means the rewind mechanic is being used and so the counter value is decreased. The isRewinding bool is set to true or false accordingly.
The counter is used to limit the amount of time the time rewind mechanic can be used. Currently, it is set to a maximum of 10 seconds.
Storing the data:
void Update()
{
if(Input.GetKey(KeyCode.E))
{
if(counter > 0)
{
counter-=Time.deltaTime;
isRewinding = true;
Rewind();
}
}
else
{
if(counter<10)
{
counter+=Time.deltaTime;
}
isRewinding = false;
}
}
Next, we need to store the data, i.e., the status of the game objects every frame. To achieve that, we store the current position, rotation and velocity values in their respective Lists as shown below. We also increase the index value every time we store the value as all the Lists move to the next index.
if(!isRewinding)
{
positionVal.Add(transform.position);
rotationVal.Add(transform.eulerAngles);
velocityVal.Add(rb.velocity);
//increase the index every frame
if(indexVal<listLimit)
{
indexVal++;
}
}
To prevent the code from storing and accumulating large amount of data, we need to cap the maximum amount. The code below starts removing the first element of all the Lists if the List size goes beyond a certain limit (600 here).
if(indexVal>listLimit && !isRewinding)
{
positionVal.RemoveAt(0);
rotationVal.RemoveAt(0);
velocityVal.RemoveAt(0);
}
That is it for the Update() method.
Rewinding:
Next we need to create the Rewind() method that is called on detecting the key press in Update(). In this method, first, we go one step back in the List by decreasing the indexVal variable.Then, we obtain the last value of all the properties and apply it to the game object.
To simplify, we access the positionVal List, get the latest value in the List, and apply it to the current position of our game object. This way, the game object moves to the position it was in the last frame. Same goes for rotation and velocity.
Next we remove the last data so that it can be over-written by the new data.
That makes up for the Rewind method.
void Rewind()
{
if(indexVal>0)
{
indexVal--;
transform.position = positionVal[indexVal];
positionVal.RemoveAt(indexVal);
transform.eulerAngles = rotationVal[indexVal];
rotationVal.RemoveAt(indexVal);
rb.velocity = velocityVal[indexVal];
velocityVal.RemoveAt(indexVal);
}
}
Creating the time reversal scene
Create a scene with some primitive shapes and add the rigidbody component to all of them. Unity now provides a number of models for prototyping in the Standard Assets package which can be useful here. Next, add this script to any game object you want to get affected by the rewind mechanic.
Put the game objects in positions such that on hitting play, their properties like position and rotation get affected. I have created an example scene which involves using a ramp and dropping the game objects from a height. After you hit play, let the game objects move around and store some data. Pressing ‘E’ during in the play mode then should execute the time rewind mechanic.
Tips:
It should be noted that storing huge chunks of data in considerably larger scenes can prove to be resource heavy. So, the code must be optimised. One way is to create a separate master script for the counter so the counter calculation is not done by all the game objects. Do not add the script to any game objects that you do not wish to get affected by the time rewind mechanic.
That should do it. Tweak around with the values like maximum time or List size or add more properties like torque, scale, etc. to get the most out of it. Check out the full script below or download the package file.
Script:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TimeKeeper : MonoBehaviour
{
List positionVal = new List();
List rotationVal = new List();
List velocityVal = new List();
int indexVal;
float counter;
bool isRewinding;
Rigidbody rb;
int listLimit = 500;
int timeLimitInSeconds =10;
void Start()
{
rb = GetComponent();
}
void Update()
{
//we use a counter variable to limit the rewind mechanic usage to 10 seconds
if(Input.GetKey(KeyCode.E))
{
if(counter > 0)
{
counter-=Time.deltaTime;
isRewinding = true;
Rewind();
}
}
else
{
if(counter<timeLimitInSeconds)
{
counter+=Time.deltaTime;
}
isRewinding = false;
}
//if time is moving forward, keep adding new elements to the arrays
if(!isRewinding)
{
positionVal.Add(transform.position);
rotationVal.Add(transform.eulerAngles);
velocityVal.Add(rb.velocity);
//increase the index every frame
if(indexVal<listLimit) { indexVal++; } }
//keep removing old data if the list size exceeds a certain value
//this is extremely important because without this logic, the list size
//will continue to increase which is not desirable if(indexVal>listLimit && !isRewinding)
{
positionVal.RemoveAt(0);
rotationVal.RemoveAt(0);
velocityVal.RemoveAt(0);
}
}
//method that actually 'rewinds' the game
void Rewind()
{
//if current index is not 0
if(indexVal>0)
{
//decrease index
indexVal--;
//get last data of this gameobject and apply it to the gameobject
//remove the used data thereby decreasing the list size
transform.position = positionVal[indexVal];
positionVal.RemoveAt(indexVal);
transform.eulerAngles = rotationVal[indexVal];
rotationVal.RemoveAt(indexVal);
rb.velocity = velocityVal[indexVal];
velocityVal.RemoveAt(indexVal);
}
}
}
Get Free Quote to Develop Your Game
Harshit Dubey | Unity Game Developer
I am Unity game developer and a casual gamer. I am fond of story-driven games and creating one based on strong and compelling narrative structure is my dream.