Hello all. Finals season is over, and I have the most persistent cold I’ve ever experienced… which means I have a lot of time. Luckily, I’ve been working on this project with my friends and had the opportunity to write some really cool code that I’d like to share here. The other engineer on the project, and myself have been trying to build the architecture of this game utilizing a very powerful tool: Scriptable Objects. If you don’t know what these are, I highly recommend watching this talk by Ryan Hipple. TL;DR You can utilize Scriptable Objects to store data, as opposed to individual method calls and dependencies within code, to keep the architecture modular, easily debuggable, and less prone to collapse on you. A Scriptable Object will store a variable and allow any function to access it, or modify it, so that all methods can just respond to this one value. Think “global variable” that exists in the editor.
Another very cool principle in the talk by Ryan is using Scriptable Object Game Events (which take the same above principal, but utilize UnityEngine.Events;) to interface between everything in the scene that would otherwise be dependent through code. So here’s what I wrote: A cutscene manager for my game using Unity Timeline and Scriptable Object Game Events.
How it works: I have a UnityEvent StartScene
that is Invoked OnTriggerEnter().
public class StartCutscene : MonoBehaviour
{
//Event to start scene
public UnityEvent startScene;
//on trigger enter
private void OnTriggerEnter(Collider other)
{
//if player
if(other.tag == "Player")
{
//raise
startScene.Invoke();
Debug.Log("Invoked");
}
}
}
Then this is what it looks like in editor. As a matter of fact, take my whole “CutsceneTrigger” prefab while you’re at it:

Note the UnityEvent is calling on the Game Event Scriptable Object “PlayCutscene” and Raising it. This signals to any Game Event Listeners (Once again, a Scriptable Object implementing the functionality of a Unity Game Event Listener) elsewhere in the scene that were set to listen to our Game Event Scriptable Object. In this case, I have 2 prefabs listening in on “PlayCutscene”. The first one is my CutsceneManager.

So take a nice long look at that beauty, and I think you might fully see what’s going on here. First off, I have the Game Event Listener which is making a call within my CutsceneManager Script to startNextCutscene(). startNextCutscene utilizes a Dictionary, which I have serialized at the top of the prefab. This dictionary takes the string name and the associated PlayableDirector, which is what controls Unity Timeline Playables. If you’re familiar with Dictionaries then you know they’re not serializable in Editor, but we can fix that with a nifty little work around in code.
//Serialized Dictionary Class
[System.Serializable]
public class Cutscenes
{
public string cutsceneName;
public PlayableDirector cutscene;
}
//Cutscene Manager will play the next applicable cutscene, storing all in a dictionary
public class CutsceneManager : MonoBehaviour
{
//Note to design
[TextArea]
public string Notes = "Names should have no spaces or numbers and triggers should be place in order of encounter";
//Array of Cutscenes, which contain our Dictionary compontents
public Cutscenes[] toDictionary;
//Dictionary that takes string name and PlayableDirector
public Dictionary<string, PlayableDirector> listOfScenes;
//Int to monitor which have played
private int selection;
//Unity Event to on trigger end of scene
public UnityEvent endCutsceneEvent;
//establishes Dictionary from serialized "Cutscenes"
public void Awake()
{
//Instantiates Dictionary
listOfScenes = new Dictionary<string, PlayableDirector>();
//Fills that shit up
for(int rep = 0; rep < (toDictionary.Length); rep++)
{
listOfScenes.Add(toDictionary[rep].cutsceneName, toDictionary[rep].cutscene);
}
}
//Starts next cutscene
public void startNextCutscene()
{
//Sets temp Playable Director
Debug.Log("Signal Recieved");
PlayableDirector temp = listOfScenes[toDictionary[selection].cutsceneName];
//Starts cutscene
Debug.Log("Starting...");
temp.Play();
//Event "stopped" is assigned to endCutscene, will call this function on raise
temp.stopped += endCutscene;
//Increments cutscenes
selection++;
}
//Invokes UnityEvent to tell rest of scene
private void endCutscene(PlayableDirector aDirector)
{
//Ends the cutscene
endCutsceneEvent.Invoke();
Debug.Log("Cutscene Ended");
}
}
So I’ll try to break this down for those who can’t understand my comments, but basically at the very top we have the serialized class Cutscenes, which I’m taking all the components necesary for my dictionary. Then storing them in an array toDictionary
. Then, on Awake() I’m taking those individual values from the array and storing them together in my Dictionary ListOfScenes
. Also note that I have a UnityEvent here at the top.
So this is where startNextCutscene() comes into play. I take the next PlayableDirector in order and store it in my temp
, but note how in the search through my Dictionary ListOfScenes
I’m using the corresponding string stored in the Array toDictionary
to index the needed PlayableDirector. Just thought that was sick. A little wonky… but cool.
Then temp is played, which cues the animation sequence of the PlayableDirector, otherwise known as the Playable. The PlayableDirector then sets off an event upon finishing which I’m listening to and invoking my own UnityEvent endCutsceneEvent
.
This is where my CameraManager comes in. Note that in the above picture of my CutsceneManager Prefab, my Unity Event endCutsceneEvent
is attached to another Scriptable Object Game Event “EndCutscene”

From the bottom up, the CameraManager is listening to 2 events, EndCutscene and PlayCutscene. Remember, these are our scriptable events, any listener in the scene has access to them! So these are both invoking responses within my CameraManager script. At the top you can see the manager takes 2 Cameras, as this script is responsible for switching between the cutscene camera, used in our PlayableDirector Playables, and the main camera, used for gameplay. Don’t worry, this is not the only script working on the camera right now, this is just helping us manage between the 2 specifically with Playables. Here’s that code:
[System.Serializable]
public class CameraManager : MonoBehaviour
{
//Declares the cameras needed
public Camera playerCam;
public Camera cutsceneCam;
public void Awake()
{
switchAfterCutscene();
}
//Starts the cutscene, swapping cameras
public void cutsceneStart()
{
//Cameras swapped
switchBeforeCutscene();
//Debug
Debug.Log("play cutscene");
}
//Ends the cutscene, swapping cameras
public void cutsceneEnd()
{
//Cameras swapped back
switchAfterCutscene();
Debug.Log("Cutscene end");
}
//Swaps active cameras
private void switchBeforeCutscene()
{
playerCam.enabled = false;
cutsceneCam.enabled = true;
}
//Swaps active cameras
private void switchAfterCutscene()
{
cutsceneCam.enabled = false;
playerCam.enabled = true;
}
}
Pretty self explanatory, but just note that the methods switchBeforeCutscene() and switchAfterCutscene() are both triggered by the corresponding Game Events in scene, in addition to the startNextCutscene() in the CutsceneManager. I wanted to show you to give an example of how using GameEvent Scriptable Objects allows multiple scripts to interface with one another without ever knowing the other exists!
You have to see how this is such a god send for devs who want to keep dependencies low and the engine easy to use for designers. Here, a designer who has been briefed can easily establish a whole system of cutscenes in their level without ever having to see a single class.
Happy holidays to everyone! I’ll be back soon with more.