How to Tell a Joke With Unity Timeline

Writing jokes is easy. Telling jokes is hard. In this tutorial, you’ll learn about Unity’s Timeline and how to create and deliver your very own punchlines! By JJ Richards.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Understanding Comic Timing

The world is full of jokes in many forms. But ask yourself this: Do you laugh more reading a joke, or when you hear a professional tell it?

Try this at home with your favorite comedian on a streaming service. Watch with subtitles on and the sound off, and then watch with the sound on and the subtitles off. Which is funnier?

Spoken jokes are more popular than written jokes because timing is critical to getting a laugh. Rush it and you ruin it. Wait too long, and the audience fills in the gap. Even cartoon strips use the three-box format to control the delivery of the joke.

Those three parts are: the Setup, the Turn, and the Punchline. The length of each is important, the pace of each is important and the pause between each is important.

Professional stand-up comedians rehearse those timings over and over until they get it just right. But how do you control all that timing in Unity? The answer is: Timeline!

Understanding Timeline

Unity's Timeline infrastructure is powerful and flexible. It allows you to create cutscenes, cinematics and gameplay sequences by visually arranging tracks and clips linked to GameObjects in your scene.

In this tutorial, you'll use it to make a dialog system. The infrastructure is made of several parts that all need to work in harmony:

  1. Timeline Asset: An arrangement of tracks you manage in the Timeline Window.
  2. Timeline Track: A sequence of animation clips of a similar type.
  3. Playable Director GameObject: Associates a Timeline Asset with a GameObject in the scene.
  4. Signal Emitter: Contains a reference to a Signal Asset, which it visually represents visually with a marker on the Timeline.
  5. Signal Receiver: A component with a list of reactions linked to a Signal Asset.
  6. Signal Asset: The association between a Signal Emitter and a Signal Receiver.

This puzzle has a lot of pieces, but don't worry, they all fit together quite easily.

Prepping Assets

Create an empty GameObject called Timeline. The Hierarchy should now look like this:

Final Scene Hierarchy

Navigate to Window ▸ Sequencing ▸ Timeline to add an empty Timeline Window to the Editor workspace. Dock this tab to the bottom so you can easily see it and the Game view at the same time. The editor layout should now look like this:

Hierarchy with the Game view in a large panel and the Timeline window below

With the Timeline GameObject selected in the Hierarchy, click the Create button in the Timeline Window. This starts the process of creating and setting up the Timeline Asset.

When prompted, save a new Timeline Asset to the Timeline folder and name it JokeTimeline. This step automatically adds a Playable Director component to the Timeline GameObject and assigns the newly-created JokeTimeline asset as the Playable.

Finally, toggle Play On Awake to True in the Inspector.

Now that all the pieces are in place, it's time to connect them.

Activating GameObjects

Timeline can control many things, but one of its simplest uses is to enable and disable GameObjects.

First, select the Timeline GameObject in the Hierarchy and make sure the Timeline Window is visible. Then, drag the Comedian GameObject to the left panel of the Timeline Window.

At this point, you'll see a prompt for the type of track you want. Choose Add Activation Track. This populates a clip in the Timeline and associates the Comedian GameObject with that clip.

When the Timeline Marker is over the clip, the Comedian is active; when the marker is not over the clip, the Comedian is inactive.

You can change the duration of the clip and move it anywhere you like on the Timeline. You can view the Timeline either in seconds or frames.

Click the Gear icon in the Timeline Window to set your preference to Seconds. For the Comedian clip, start the clip at 0:00 and stretch it to 20:00.

Do this again for the JokeWindow GameObject. Drag it to the Timeline Window to create an Activation Clip. Start the clip at 2:00 and stretch it to 18:00. This makes the JokeWindow appear after the Comedian and disappear before the Comedian.

In the Game View, make sure you haven't selected Maximize On Play so you can see both the Game View and the Timeline Window simultaneously.

Click Play and watch the sequence play both in the Game View and the Timeline Window. In Edit Mode, scroll the mouse wheel while over the Timeline Window to see more or less of the overall Timeline. Then, scrub the Timeline Marker back and forth to watch the clips activate and deactivate their associated GameObjects.

GameObjects appearing and disappearing as you scrub the Timeline

Sending Signals

Timeline tells GameObjects what to do using Signals. Signals consist of three parts: the Signal Emitter, the Signal Receiver, and the Signal Asset. The Signal Emitters are placed on the Timeline and Signal Receivers are placed on GameObjects in the scene that you want to control. During playback, whenever Timeline passes a Signal Emitter, it sends the Signal Asset to the Signal Receiver.

Before writing code to send or receive a signal, it's best to define the elements that should be in the signal. There are many things you can include, but for this app, you'll pass four pieces of data with the signal.

To help manage that data, return to the Typewriter script and, at the very bottom, add a simple class with the following code:

public class Dialog
{
    //1
    public string Quote;
    //2
    public float PausePerLetter;
    //3
    public bool NewPage;
    //4
    public bool NewLine;
}

With this code, you're declaring four variables:

  1. Quote: The text that you'll send — in this case, the joke itself.
  2. PausePerLetter: Controls the speed of the text reveal.
  3. NewPage: Tells the Typewriter whether or not to start a new page.
  4. NewLine: Instructs the Typewriter whether or not to start a new line.

Now that you've created a form for the signal, create a new script called JokeMarker to send it. Add these declarations to the script, above the class declaration:

using UnityEngine.Playables;
using UnityEngine.Timeline;

These are necessary to access the Timeline API.

Instead of the usual Monobehaviour, this script needs to derive from Marker and implement INotification. So replace the default class definition with:

public class JokeMarker : Marker, INotification

You need to send four pieces of information in the signal, which can be private and visible in the Inspector via the SerializeField attribute:

    //1
    [SerializeField]
    private string quote = "";
    //2
    [SerializeField]
    private float pausePerLetter = 0.1f;
    //3
    [SerializeField]
    private bool startNewPage;
    //4
    [SerializeField]
    private bool startNewLine;

Here, you declare:

  1. quote and set it to be empty.
  2. pausePerLetter and set it to 0.1f.
  3. startNewPage, which defaults to false.
  4. startNewLine, which also defaults to false.

Next, add the following code, which uses the variables you just declared:

    public string Quote => quote;

    public float PausePerLetter => pausePerLetter;

    public bool StartNewPage => startNewPage;

    public bool StartNewLine => startNewLine;

This exposes read-only access for your four private variables. Using [SerializeField], private and read-only properties together is a good safety pattern. It ensures that other parts of your code can't randomly change the values you set in the Inspector.

Finally, implement the INotification interface by adding the following line:

    public PropertyName id => new PropertyName();

This is a necessary identifier for notifications. As your use of notifications grows, you can use this id to identify notification channels or even individual notifications.

Save the script and return to the editor.

Now that you've created the JokeMarker, you need to create a script that listens for it. You'll do that next.