Cutscenes in a grid-based Unity games

  • 11th Aug 2020
  • by Pav
  • Reading Time: 9 minutes

Introduction

Cutscenes play a huge role in video games as they immerse the player into their stories. They allow to follow the relationship between characters, explain the motives for our protagonist’s actions and reveal the details of our next goal. In other words, they can be treated as video sequences to be played in-between gameplays that enhance our overall experience with the game. The Shining Force II from the Sega / NES era delivers lots of examples of cutscenes in a grid-based RPG game.

Cutscenes in retro games

In this tutorial, I will develop a cutscene manager system. I’m going to build this system on a basis of my previous tutorials on grid-based movement and interactions system so I recommend reading them beforehand. Let’s get to it!

Table of contents:

  1. What is a cutscene?
  2. Creating cutscenes in Unity using Timeline
  3. Turning off the HUD screen during cutscenes
  4. Triggering the right cutscene at the right time
  5. Providing player with the opportunity to interact during a cutscene (optional)
  6. “Tell me your story!” Expanding the cutscenes dialogue system (optional)

1. What is a cutscene?

In order to implement a cutscene system let’s first specify what exactly is a cutscene. As the name implies, it is an interruption that occurs at specific moment of our gameplay (cut) to provide an emphasis on some crucial moment in a story (scene). In modern games it usually takes a form of a fully rendered video sequence, which not only delivers a compelling visual experience to the player but also clues on the next task. However, that is not always the case. In older as well as newer games we can see cutscenes that are rendered with the use of native gameplay systems. What that means in practice is that a cutscene doesn’t have to necessarily be pre-rendered using sophisticated graphical tools. Rather, we can use standard render loops and scripts to go through our desired sequence.

That being said, coding every single sequence may not be the most optimal approach to the development. Imagine that you are writing a storyline for a single game constituting of a hundred cutscenes. In order to prepare that amount you would have to prepare, at the very least, hundred different functions or scripts! To make matters worse there is a little room for abstraction since every cutscene, apart from small examples, is different. Luckily for us, Unity has a built-in feature that might be able to make our lives a bit easier!

2. Creating cutscenes in Unity using Timeline

The timeline feature was introduced in Unity back in 2017 and constantly expanded over the years. The developers added lots of improvements that now makes it a perfect tool for creating cutscenes for your game. There is a fantastic in-depth tutorial by Anthony Uccelo on timeline features and I definitely recommend reading it. In here I’ll focus more on how to utilize the timeline’s functionalities to create a fully-fledged practical cutscene system.

  • I’ll start by creating an empty GameObject and name it “Cutscene Manager”.
  • After that I’ll add the PlayableDirector component to it. This is going to be the main element for playing our sequences. You may have noticed that the Animator was automatically added during the process.
  • I like my cutscenes to have a bit of “cinematic” feel to them so I’ll also add a letterbox effect. In order to achieve this I’ll create a new canvas and add a panel with two images representing the top and bottom bars.

You should end up with the following hierarchy:

Cutscenes Manager hierarchy

Adding cinematic effect ‘letterbox’ to cutscenes

Select CutsceneManager and open the ‘Animator’ panel. We’re going to create two analogous animations for opening and closing a cutscene sequence. You can do it any way you like but I’ll just stick with simply sliding the bars in and out of the viewport. I’ll do it by interpolating the values of anchored positions of both bars. I’m setting the bars with initial offsets to do this.

Cinematic sequence for cutscene in Unity

In order to make two animations quickly, simply copy all the keyframes from one animation to another. Then scale them using vertical blue bars present on the timeline. The end result should look more or less like this:

Sliding in cinematic bars for cutscenes

Since we don’t want to play these animations by default when the game starts, in the Animator window create an empty state and set it as default one.

Creating and defaulting an empty state in Animator

Also don’t forget to untick the “Loop Time” on each of those animations as we want to play them only once!

Making a cutscene sequence

Now that we have the elementary components done, we need to create the actual cutscene. In order to make one, open the Timeline panel by selecting Window > Sequencing > Timeline. In Unity technical terms, the animation we are about to make is called a “Timeline asset“. It is a self-contained sequence composed of multiple lower level sub-animations. What it means in practice is that it allows us to combine the animations of multiple different game objects and components into one. We can for instance drop in the recorded walk animation of our character, move him by changing the position over time while display some dialog in the background. To do this within the timeline panel, we will use tracks.

Animation and activation tracks for composing cutscenes

There are two types of tracks that I’m going to use inside a timeline, namely the ‘Animation Track‘ and ‘Activation Track‘. I’ll use the animation tracks to play back the recorded animations while activation tracks to control the ‘active’ state of game objects. I’ll organize my tracks and keep things clean on the timeline with the help of ‘Track Group‘.

  • Right-click on an empty space in the left hand side section of the panel and select ‘Track Group’ from the list. Name it “Player”.
  • Create two animation tracks under the newly created track group. Drag & drop the ‘Player’ game object to their reference fields.
  • In the top animation track, drag & drop any player’s animations you have recorded in it’s animator. Subsequently, hit the red dot button in the bottom animation track to change the position of the player over time. Here, it’s really up to you what kind of a cutscene you want to make. However, try to keep animation tracks in sync, e.g. changing the global position appears natural with the walking animation of the player.
  • Optionally, inside the interpolation panel you can adjust the interpolation you’d like to make to your animations. For instance, in my animation I’d like to remove the easing. To that end, I right-clicked on the handles and selected ‘Linear’ for ‘Both Tangents’.
Timeline panel and composing a cutscene with it.

3. Turning off the HUD screen during cutscenes

When you watch cutscenes in games you probably noticed that all the HUD screen components disappear for their duration. In most cases this is intentional since we don’t want them to cover the screen when our carefully crafted animation is playing. To that end, I’m going to use the previously mentioned activation tracks to control the active state of HUD game objects.

  • Create a new ‘Track Group” and call it “HUD”. Here, I’m going to control the flow of activating and deactivating everything that’s related to GUI and HUD.
  • Add one animation track and drag our ‘Cutscene Manager’ to its field. We need to do this in order to play the animations responsible for adding the cinematic letterbox effect described earlier.
  • Depending on how many HUD elements you want to disable, add one activation track per element. In my case I have 3 components – the D-Pad buttons, the ‘Action’ button and health bar.
  • Add one more activation track for your Cutscene Manager canvas that we have defined earlier. I’ll use it to enable it in order to play the letterbox animations.

When it comes to activation track it’s important to set its ‘post-playback state’ right. This setting basically states how the state of an object shall look like after the animation is finished at specified time. In the beginning of a cutscene, I want my HUD screen to be visible for one frame, then disappear and finally appear at the end. To do this I’ll set my tracks the following way:

Setting activation tracks with correct post playback setting

Of course I want the ‘cutscene canvas’ to be active only for the duration of the sequence so I’ll set it’s post-playback state to ‘Inactive’. After that, play the animation adding the cinematic effect right after the HUD components are deactivated (on second frame).

4. Triggering the right cutscene at the right time

We have now prepared our cutscene, but how can we ensure to play it at the right time? The Playable Component has a setting to start the desired cutscene on script ‘awake’ but that’s obviously not always the case. We want a specific cutscene to play when the player enters into the dungeon, when he picked up important item and rescued his team member. In order to do that, I’ll create a special prefab that will hold the information on the cutscene. It will have the standard box collider component to detect when player comes into contact with it. That event shall trigger the sequence.

Create an empty Game Object in your project hierarchy and add Box Collider 2D and Rigid Body 2D components to it. After that, add a script called ‘Cutscene Trigger’ to it.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;

public class CutsceneTrigger : MonoBehaviour
{
    public PlayableDirector timeline;
    public PlayableAsset cutscene;
    public bool hasPlayed = false;

    void OnTriggerEnter2D(Collider2D c)
    {
        if (!hasPlayed && c.gameObject.tag == "Player")
        {
            StartCoroutine(StartCutscene());
        }
    }

    IEnumerator StartCutscene()
    {
        yield return new WaitForSeconds(.2f);

        hasPlayed = true;
        timeline.playableAsset = cutscene;
        timeline.Play();
    }
}


The code should be self-explanatory. The timeline field contains a reference to our Playable Director component while the cutscene a sequence we want to play. The hasPlayed boolean value makes sure that the animation will be played only once. Since I want the cutscene to start with a slight delay I’m going to use a coroutine.

Place the object in the path of your player and the designated cutscene will now play at that precise moment! Just don’t forget to fill in the necessary fields!

5. Providing player with the opportunity to interact during a cutscene (optional)

If you have followed all the steps up till now then you should already have a solid cutscene system in place. However, at this point I need to mention that some cutscenes may require the player to interact at specific times. For instance, the player may have to press a button during a sequence in order to advance from a dialogue box that popped up. In practice it means that we have to pause and resume our cutscene in response to the flow of events. In order to do that Unity developers introduced a concept within the timeline feature called ‘signal emitters‘.

The concept of a signal emitter / receiver is fairly easy to understand. We place (or mark) the signal emitter at specific moment in our timeline. The signal emitter then broadcasts a message to associated scripts to execute their functions mapped by signal receiver.

In my case, I’m activating the ‘action’ button on frame 210. The player then needs to press it in order to advance from the dialogue box. The function that is responsible for displaying a dialogue is called TriggerDialogue()and it comes from new DialogueTrigger script attached to our CutscenesManager.

Timeline signal emitters

To keep things simple, I’m pausing the timeline, displaying the sentence in a console and waiting for the player to press the button. When he does, the timeline resumes from the moment it was paused with the Resume method. I’ve left the below script as generic as possible so that you can expand it for your game however you like.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;

public class DialogueTrigger: MonoBehaviour
{
    public PlayableDirector director;
    public string sentence;

    public void TriggerDialogue()
    {
        Pause();
        Debug.Log(sentence);
    }

    void Pause()
    {
        director.playableGraph.GetRootPlayable(0).SetSpeed(0);
    }

    public void Resume()
    {
        director.playableGraph.GetRootPlayable(0).SetSpeed(1);
    }

}

Please take note that I’m merely displaying one sentence here. In a finished game you probably would like to implement some more elaborated dialogues consisting of more sentences.

6. “Tell me your story!” Expanding the cutscenes dialogue system (optional)

In this section I’ll show you how you can make your dialogues more elaborate. It goes with the assumption that your characters will have more things to say than just one sentence during the conversation. Let’s start by defining a class that will represent the dialogue itself. Inside the class I’ll have two public fields containing the name of a character who speaks the dialogue and the dialogue itself.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class Dialogue 
{
    public string name;

    [TextArea(3,10)]
    public string[] sentences;
}

The [TextArea(3,10] specifies how big the input text area should be inside the Unity editor. Let’s now assume that during each cutscene we want our character to say something at specific moment. To that end we need to modify CutsceneTrigger and DialogueTrigger to include the dialogue. First, I’ll add few lines to the CutsceneTrigger.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;

public class CutsceneTrigger : MonoBehaviour
{
    public PlayableDirector timeline;
    public PlayableAsset cutscene;
    public bool hasPlayed = false;

    public Dialogue dialogue;

    void OnTriggerEnter2D(Collider2D c)
    {
        if (!hasPlayed && c.gameObject.tag == "Player")
        {
            StartCoroutine(StartCutscene());
        }
    }

    IEnumerator StartCutscene()
    {
        yield return new WaitForSeconds(.2f);

        hasPlayed = true;
        timeline.GetComponent<DialogueTrigger>().dialogue = dialogue;
        timeline.playableAsset = cutscene;
        timeline.Play();
    }
}

Next, I’ll modify the DialogueTrigger so that the next sentence can be displayed from a queue.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;

public class DialogueTrigger: MonoBehaviour
{
    public PlayableDirector director;
    public Dialogue dialogue;
    private Queue<string> sentences;
    public string sentence;
    
    void Start() 
    {
        sentences = new Queue<string>();
    }

    public void TriggerDialogue()
    {
        Pause();

        sentences.Clear();
        
        foreach(string sentence in dialogue.sentences)
        {
            sentences.Enqueue(sentence);
        }

        DisplayNextSentence();
    }

    public void DisplayNextSentence()
    {
        if(sentences.Count == 0)
        {
            Resume();
            return;
        }

        sentence = sentences.Dequeue();
        Debug.Log(sentence);
    }

    void Pause()
    {
        director.playableGraph.GetRootPlayable(0).SetSpeed(0);
    }

    void Resume()
    {
        director.playableGraph.GetRootPlayable(0).SetSpeed(1);
    }

}

You may already know where I’m going with this. Every cutscene in the game has a dialogue assigned to it. I’m loading the dialogue into the memory when the player triggers it during a gameplay. Then, I’m simply displaying the next sentence from the data structure every time the DisplayNextSentence() is executed. That said, you should set this function to trigger whenever specific button is pressed or clicked during a cutscene. Once there is no sentences left in a dialogue, the timeline will resume.

With a bit of polishing and work, your end-result may now look like this!

Conclusion

Cutscene are a great way to immerse the player into the game’s story. In this tutorial I have shown you how to make cutscenes in Unity. We have explored the ways of implementing a cutscene system that will trigger a given sequence at any point of our story. We have used the built-in Timeline system to make cutscenes and later took advantage of ‘signal emitters / receivers’ concept to show dialogues. The signal emitters allowed us to create a way of interacting during the sequence while pausing it at the same time.

References

How to make a Dialogue System in Unity by Brackeys