Time Manipulation in Unity – Rewinding Time

Indie developers often wear a bunch of hats at the same time, so in this article, we will also be delving into the design and hope it will also be helpful

Articles:

While developing and playtesting our game we noticed that it required a lot of memorizing and players often forget what they did wrong while playing the level. In fact, some levels in our game are based on distracting the players attention from different sides, so you can sometimes overlook enemies until the moment you lose. And so we thought about different options we had to help player remember what happened. Showing some trails of past enemies or points of collisions where they’ve most died on the previous attempt.

Some of the fears were that this would clutter up the screen or that players would mimic what happened again and again instead of trying new ideas. And so we thought why not use our good old timeline again, but this time not just for us as developers. After the loss, we decided to do the following, when the player presses the “restart” button we show how they played in the reverse direction. (Starting with the moment of your defeat, it shows probably the most important part why you lost. But going for the perfect solution, you will probably also need to see what happened before, which happens without any pressure this time, as it is just quicker replay)

Unity Time Rewinding

Implementation

Almost everything for the playing in reverse was already done in the timeline we made for the level editing. We still didn’t know if it was the best solution but figured it was easy to do. So for the movement of the enemies and crystals we’ve just used stepping back as it was already possible. (more on it in the previous article).

However, during the level, other events were triggered by the player or caused by players actions (for example, barrier creation). And for this kind of things we’ve added a new interface to the TimeManager:

 1public delegate void ReversingTimeActionDelegate();
 2
 3public class TimeManager
 4{
 5    class ReversingActionWithTime
 6    {
 7        public float Time {get; set;}
 8        public ReversingTimeActionDelegate ActionToCarryOut {get; set;}
 9    }
10
11    public void RememberAction(ReversingTimeActionDelegate action)
12}

We’ve introduced the ReversingActionWithTime class that memorizes the time of the action and a function(delegate) that will be able to execute the reverse action (similar to the Command Pattern in programming but tied to time instead of pressing a ctrl+Z or something like that).

When the action happens, while the level is running, we simply add the reversing function (RememberAction) at this particular moment (i.e. 1337th millisecond of the level) so it will be called when we reverse time.

For example, when the enemies are hit by a barrier and disappear we add the delegate. Later when the restart is pressed and we reach this point in time – the delegate is called automatically by the TimeManager which just activates enemies again (so that they appear on screen):

 1public class Enemy{
 2
 3  void onCollisionWithBarrier()
 4  {
 5     timeManager.RememberAction(Activate);
 6     Deactivate();
 7  }
 8
 9  void Activate()
10  {
11   // do the activation work
12  }
13
14//.. Remaining implementation
15}

In the rewind mode we actually do not care about collisions because they’ve already happened when the game was running normally (forwards). And now we can just revert the state back on an increased speed skipping some frames, we may require this on a less powerful devices because when you rewind back you can do this at the 20x+ of the normal game speed, and because after level rewinded all the way back we just reset all objects positions anyway.

If needed, the values of some variables can be stored in the delegate. For example – the exact movement of carrier enemies (yellow ones that create small enemies when destroyed), we need to store it because the trajectory of the spawned ones is actually random for a second or two).

Currier Enemies Rewinding [ There is a story reason that the thing on the right also remains red a bit. It is not a bug, I promise =) ]

Visuals & Feedback

Players really enjoy the visual part of the rewind (especially when more advanced things like moving crystals and carriers are involved)
Aside from just a visual thing, rewind also is a good time for the player to see once more that dirty enemy pack that’s lead to the decline. This also gives the player some time without direct involvement and lets us reduce the buildup of frustration a bit.

A little side note on the topic of frustration, we do roughly the same thing when you‘ve successfully completed the level. We take you to the map (instead of just opening next level) and show different sorts of animations and the place where you can also see the progress you’ve made
(We probably could go in-depth about frustration mitigation (arrows, etc.) and clear feedback so that the player wouldn’t blame the game. But we’ll leave it for another time)

At first, when we’ve been implementing this feature we just rewinded back the time and the game looked mostly the same, but playtesting showed us that having no visual effect at this point was a really bad idea. Players just didn’t understand why they don’t have the control anymore (you can’t create connections while rewinding),

Lintrix back time

So, for the player to better realize that the rewind is happening, we’ve added some visual feedback.

We highlight red enemies because it is the most important thing you need to see and it also results in a nice visual effect. We’ve also added the rewinding icon. At first, we also tried VHS like rewind effect but is just doesn’t suit our visual style.

This simple effect was achieved using a grayscale but letting the red pixels with a value over 0.7 be unchanged. Here is an example: https://gist.github.com/Stals/b01487d44932a30be7d005870ab53798

By the way, if you’re wondering how to apply this to the camera instead of just separate objects you can put the shader on the material, add this simple script to the camera

 1using UnityEngine;
 2
 3public class BasicPostEffect : MonoBehaviour
 4{
 5    [SerializeField]
 6    Material mat;
 7
 8    void OnRenderImage(RenderTexture src, RenderTexture dst)
 9    {
10        Graphics.Blit(src, dst, mat);
11    }
12}

And drag & drop the new material into the field.

But back to the topic. The interesting thing is that after adding the effect people finally got what was happening on the intuitive level and didn’t tap to create barriers anymore. Also funny enough quite a few people realized that there was a rewinding moment only after ten restarts without any memory that it happened previously.
On top of that, we’ve also added an ability to skip rewinding just by double-tapping the screen anywhere which was the most often reaction when players wanted to skip it.

Other things that you can (and maybe we will) use if for is tutorials. In a game like this you can allow the player fail the part you are trying to teach him, and then just rewind time a little bit and ask to try again. In our opinion, this option could work better than just restarting the level entirely or just stopping the time at the moment player has to make his action. You can revert to that option if the player fails a few time in a row, though.

We’ve also prepared a simple project as an example how we implemented and used the new feature of our TimeManager that allows to memorize reversible actions. The scene contains two cubes that change color when they collide. After 2.5 seconds time starts playing in the reverse direction and the color changes back, thanks to the canceling action we remembered in time manager.
You can find the example here.

We hope it wasn’t hard to follow, and now you have the power to turn back time.
In the next article, we will be looking at the game design applications for the timeline, how it helped us fix some problems and improve the game.

If you liked this article and would like to see more like this one, or just catch the next article in this series – you can subscribe to the blog here.

Sharing is caring!


You may also like:

Related Content: Time Manipulation in Unity – Level Creation

Time Manipulation in Unity – Level Creation

Related Content: Time Manipulation in Unity – Recorded Solutions

Time Manipulation in Unity – Recorded Solutions

Related Content: Iterative Design – Why Playtesting Matters

Iterative Design – Why Playtesting Matters

Related Content: Making a Game Jam Game During Self-Isolation

Making a Game Jam Game During Self-Isolation