The Timeline
Arms Race (but it’s a play) is primarily made on the Unity Timeline. Everything, from the stage curtains opening and closing, to lights moving and turning on, to actors moving around the stage, to sounds, is controlled via the timeline, often using custom clips.
The documentation on creating custom clips was not amazing at the time, but I got by on various articles and examples. In particular, these were the resources I used:
https://blog.unity.com/technology/creative-scripting-for-timeline - there might be newer now, but having a source project to look at was very important
https://forum.unity.com/threads/how-to-check-if-timeline-editor-is-scrubbing.600946/ - for previewing custom clips in the editor
https://forum.unity.com/threads/create-timeline-assets-through-script.493331/ - forum post about creating timelines programmatically.
(It’s to the point that it’ll be one of the biggest things I’ll miss from Unity if I decide to move away from the engine. The timeline is kind of clunky, but it’s fantastic, and I enjoy how much you can script on it while still using it as an art tool.)
These are some of the more interesting clips I ended up making.
Stage Light Clip
This was a fairly simple modification of the light clip demonstrated in Unity’s official guide to extending Timeline, but also had support for changing a stage light’s beam properties.
I think one of the more interesting ideas for timeline was how different types of clips could be attached to the same object, and move different parameters at different times. A Stage Light clip could change the intensity, color, etc. of a light, while a transform clip could cause the light to move across the stage or rotate.
Details
Spotlights exist as Playables, which control a single light.
Playable fields:
- intensity of the light
- color of the light
- whether to turn the light on or off (which happens at the very beginning of the clip)
There was a little Mixer work, so that colors can smoothly blend and other parameters can change as well. Example: https://blogs.unity3d.com/2018/09/05/extending-timeline-a-practical-guide/
Things that can’t be changed:
- What object the light is following – like with Animation Rigging, it’s best to have a single Target object that instead follows characters if we need the light to follow a character.
- Width of the beam - like real lights!
Mixer behavior:
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
StageLight trackBinding = playerData as StageLight;
float finalIntensity = 0f;
Color finalColor = Color.black;
if (!trackBinding)
return;
int inputCount = playable.GetInputCount (); //get the number of all clips on this track
float intensityWeight = 0f;
float colorWeight = 0f;
intensityList.Clear();
colorList.Clear();
for (int i = 0; i < inputCount; i++)
{
float inputWeight = playable.GetInputWeight(i);
if (inputWeight < float.Epsilon) {
continue;
}
ScriptPlayable<StageLightBehavior> inputPlayable = (ScriptPlayable<StageLightBehavior>)playable.GetInput(i);
StageLightBehavior input = inputPlayable.GetBehaviour();
if (input.intensity > float.Epsilon) {
intensityList.Add((inputWeight, input.intensity));
intensityWeight += inputWeight;
}
if (input.lightColor != Color.black) {
colorList.Add((inputWeight, input.lightColor));
colorWeight += inputWeight;
}
}
if (intensityWeight > 0) {
float multiplier = 1f / intensityWeight;
foreach (var item in intensityList) {
finalIntensity += item.Item1 * item.Item2 * multiplier;
}
}
if (colorWeight > 0) {
float multiplier = 1f / colorWeight;
foreach (var item in colorList) {
finalColor += item.Item1 * item.Item2 * multiplier;
}
}
//assign the result to the bound object
if (finalIntensity > float.Epsilon) {
// set intensity of lights
trackBinding.SetIntensity(finalIntensity);
}
if (finalColor != Color.black) {
trackBinding.SetColor(finalColor);
}
}
Pathfinder Clip
Used to move actors around the stage. The only inputs they had was the actor they were attached to, and a target transform somewhere in the scene. When the clip started, the actor would begin moving towards the target using A* Pathfinding, and stop when the clip ended.
I don’t know if I ever got to this, but I intended on updating the actor’s running speed based on the clip length, so that the actor would always reach the destination at the end of the clip. However, if a new pathfinder clip had started in that time, the actor would follow the new one. This allowed me to chain multiple clips together with different targets, so actors could follow complex paths.
Details
The pathfinder itself is reliant upon the Unity AStar Pathfinding Asset:
How this works
- Pathfinding starts when the clip starts.
- Pathfinding stops when the clip stops.
- You can have smooth movement chaining multiple paths together by starting a new clip right at the border where the last one ends.
- Each clip can set the target object that the Pathfinder should move to.
Function Clip
Probably the simplest and also most useful, this clip bound itself to a GameObject, and called SendMessage
on that object with the function name in its input when the clip started, and an optional second function when it ended. I used this to trigger explosions, summon actors and props, turn on and off various lighting effects, etc.
Anyway, the timeline was good, and frankly anything that let me animate without having to hardcode numbers in code is good. I haven’t had too much experience with 3D animation tools in other software, so I don’t know exactly how Unity’s Timeline differs from how editing normally works – Blender appears to do something similar with its NLA Editor.
Dialogue
Dialogue had its own clip, an extension of the Function Clip, that called into the UI’s dialogue manager to show a textbox with the desired dialogue when the clip was entered.
To avoid having to manually place the many, many lines, the script format for Arms Race but it’s a play had timing information baked into metadata, and then I programmatically created a timeline from the dialogue.