Implementing a Common Scenario (Loot Chest) with Timeline in Unity 2020

GameDev Dustin
23 min readApr 6, 2022

This is going to be a long read, so you might want to just bookmark this and come back to it as needed!

Objective

For this demo, we’ll setup a player character w/ controller that triggers a loot chest to open which contains a crown.

On open, particle effects will be triggered, the loot chest will open, and the crown will float up and then attach to the player character’s head.

Initial Setup

In our scene, we need to place a plane and color as desired to represent the ground.
We’ll then need to import a treasure chest of our choosing, a reward to be place in the chest, and a player controller with model.

I’ve used assets from GameDevHQ’s Filebase for the chest and reward and the free Third Person Controller asset from Unity.

Once you have imported your assets, drag the “PlayerArmature” from the Starter Assets folder into your scene hierearchy.

Your scene should look similar to the above.

Implementing Our Scenario

Now, we can approach the loot in various ways, either having it always in scene (inside the chest) as shown above, or spawning it when the chest has opened being the two most common approaches.

For simplicity’s sake since this is just a demonstration, I’ve placed it in the scene as a child game object in the loot chest.

We’ll also add some placeholder art for the particle effects.

Make sure to disable “Play on Awake” on the Particle system.
I’ve also childed the particle effect to the treasure chest.

Next, we need to create a trigger for our loot box.

Add a Cube to the scene, remove unnecessary components (mesh renderer) and check that “Is Trigger” is enabled on the Box Collider component.

Place and resize similar to the above.

Empty Game Objects > Director Game Objects

We’ll need to organize our various directors and it’s best practice not to place directors’ directly on the game object they will be controlling for most circumstances.

The reason that is bad practice is that often the game objects your Director Timelines will be controlling have times when they are disabled.

If the game object the director is attached to is disabled, the remaining portion of the Timeline will not occur.

Everything simply stops.

So, I’ve created four empty game objects, three of which are children.

Now, we’ll create a Director object for each of the children game objects as shown above.
Each of these game objects now has a Playable Director component automatically added.

We don’t want our animations to occur as soon as the game runs or the game object is enabled so we’ll disable “Play on Awake” for each of them on the Playable Director components.

Note also that our Project view shows where the Timeline objects for these Directors has been saved.

Next, I’m going to create an animation using the animation window, not the Timeline window.

First select the game object we wish to animate, in this case treasure chest, and then click Create in the Animation window and save the animation as shown above.

Now we will hit record in the Animation window, and with the time selected still at 0:00, we’ll add a starting key to our animation by right-clicking on the Transform component as shown abvove.

I think I want this animation to take about three seconds, so I’ll next click on the 3:00 point of the animation timeline.

In the Hierarchy window, I want to select the child game object of my Treasure Chest game object that represents only the lid.

With that selected we can continue our animation.

Make sure to enable the record button in the animation window and reposition your lid as desired.
When satisfied, disable the record button.

You should not see a few vertical diamonds on the animation timeline under the three second mark that indicate you have created new keys for the animation at that point in time.

Hit play to make sure it works as desired, adjust as needed.

We want to also make a close lid animation, so make sure to note the transform position and rotation values for the lid game object, not the parent treasure chest game object, at the three second marker of your animation.

At this juncture, it is a good idea to go ahead and do a copy component of the transform for the open lid.

There are several ways to create a new animation for the ChestClose_anim.
I’ve chosen to do it from the Animation window as shown above and named it appropriately.

You can make sure it saved by checking your Project Window.

Note that at the 0:00 marker, our lid game object (Plane_003 in my case) has position and rotation values that will be relevant.

We can use copy/paste component from our original animation to expedite this process.

Remember to hit record before pasting any new values or they won’t stick!

I’ve animated the lid closing as shown above by copy/pasting the transform component values from the lid opening animation.

All we do is reverse our transform values at the same time markers of the animation window.

Now we have two usable chest lid animations we can call on from our Directors.

First, we need to create a new animation for the crown rising clip.

Now we can keyframe the crown at its starting and ending positions.

If we wanted to get fancy, we could add several more keyframes rotating the crown between the 0:00 and 0:30 marks.

The easiest way I can think to make this rotation is to think of it in terms of cycles.

If I want the crown to do one full rotation in these three seconds as it rises, I can break it down to just two angles of rotation.

The first is 180 degrees, the second 360 degrees.
I just need at least one angle between the start and stop since those are identical essentially.

Since I want this to be one full rotation over 3 seconds, I want it to reach 180 degress at the 1.5 (1:30) second marker.

So we add the rotation keyframes as shown above, making sure to modify our ending keyframe on the 3:00 marker to have a 360 rotation.

Note, that it is the Y axis we are “rotating around”, even though we may intuitively gravitate towards using the x axis as I initially tried to above.

If we play our animation, we should see something like that shown above.

Adding Animations to Director Timelines

Now that we are nearing the finish line on this little project, I’ve decided to use just the ChestOpenDirector to control both the lid opening and the crown rising.

Now, depending on your game/scenario, etc, you may very well want a separate director for the game object that will be revealed when the chest opens in a game.

For this demo, it isn’t necessary, so why not combine them onto one Timeline?

Simply drag the ChestOpen_anim and CrownRising_anim onto the Timeline window while ChestOpenDirector game object is selected in the Hierarchy window.

You could do these in-line, or one after the other, but I think I want to have some overlap of the animations without blending them so I’ve placed them as two separate animation tracks.

We need to assign the game object with the animator component to each of these animation tracks in the Timeline window as shown above.

If we hit play with both animation tracks starting at 0:00, we can see that there is an overlap issue where the crown goes through the lid.

We need to adjust the starting time of our crown animation to avoid this.

By letting a half-second (0:30) go by before starting the CrownRising_anim, we avoid this conflict.

Now, the Crown rising out of the, (um, sand?), is annoying me so I’m going to adjust the CrownRising_anim in the Animation window.

I’m thinking it may be better or even outright necessary to give the Crown an Animator component.

First, we’ll add the Animator component to our Crown01 game object.
Next, create an Animator Controller in the Project window as shown above.

Lastly, assign the Animator Controller to the Animator component on the Crown game object.

Now we can assign the newly created crown director to our ChestOpenDirector Timeline as shown above.

Next, we’ll open our Crown Animator Controller and set it to default to an idle state.

We’ll also drag the CrownRising_anim onto the Controller so that it recognizes it for use in the future.

We’ll do the same for the Treasure_Chest_01 Animator Controller, setting it to idle by default.

Changing up the Animator Controller for our crown seems to have broken the animation for CrownRising_anim.

Well, *bleep* happens, so always be ready to fix things!

The Fix is In

Within the Animation window, with CrownRising_anim selected, we need to fix a few things.

It seems our keyframes are still marked correctly, but these will really just get in the way.
Note that we need to keyframe at 0:00, 1:30, and 3:00 and then delete all of the keyframes from the animation timeline.

First, remove the current position and rotation properties which will erase all of our broken keyframes.

Next, let’s add our initial position and rotation starter keyframe.
Then, we’ll add all of rotation keyframes.

If we hit play now, our crown won’t move up, but it will rotate 360 degrees as desired.

Now, adjust the crown height at the 3:00 marker and make sure to keyframe it.

If we hit play, we should see our crown animation working as intended!

Note, if once you go back to work in the Timeline window or elsewhere, and you see the crown hanging in the air at its end position, simply go back to the animation window and reset to the 0:00 marker and it will go back to where it should be inside the chest.

Also, make sure that the Animator component is enabled on the Crown01 game object.

If you are still having issues w/ the crown position during Timeline playback being incorrect, as I was, this is probably due to Timeline keyframes recorded before we made all kinds of changes.

Delete the current ChestOpenDirectorTimeline in the Project window and create a new ChestOpenDirectorTimeline.

When you run into issues that are time-consuming or tedious to fix, always consider if it would be easier to start from scratch as it were.

Since our Director Timeline in this case is messed up and adding two animations to it is straight-forward, we’ll just recreate it!

Drag the animations back into Timeline as shown above.

If you do not see the Animator fields for these on the left, click on the ChestOpenDirector game object in the Hierarchy window and they should appear.

Assign the correct game objects with animators to Timeline.

Play Timeline and see that the crown animation is still broken, spawning at 0,0,0 it appears.

Hmm, several options come to mind here:

All good options, but it is such a hassle to replace broken monitors and crying just won’t fix this.

Take a breath, find the calm, and let’s figure this out.

As you can see, I’m not even hiding the issues I’m having in my article here.
There’s a good reason for that.

There will *always* be issues.
The important thing is that we solve them!

So, why is this happening?
Somewhere the Crown game object is set to 0,0,0 position on its Transform component.

If I select the Crown01 game object and check its position in the scene by default, I see the correct 0.42 Y value.

So, this isn’t the issue.

Maybe the position is still saved somewhere in the chest open or close animations I thought.
Nope, not there.

Carefully look over the CrownRising_anim itself, scrub through the entire track and check 0:00 carefully.
Nope, all correct.

The Work-around

When all else fails, use a work-around.

I’ve decided since I can’t find an underlying issue for the position, I’ll just force the animation track itself to be positioned at the correct height since this will not affect any other game objects.

Select the CrownRising_anim clip in the Timeline window.
In the Inspector window on the right, set the Y Position to 0.42.

Play the Timeline.

It works, but we still have some more tweaking to do.
Notice that the lid snaps closed at the end of the ChestOpen_anim clip.

Select the ChestOpen_anim clip in the Timeline window.
In the Inspector window, set Loop to off.

Do the same for the CrownRising_anim clip.

Now, we want to always try to be thinking ahead.
I want to add a little buffer time for the chest to stay open, so I’ve extend the animation to 5 seconds in length for now.

Add Particles Activation

We also want particles to play when the chest opens, using placeholder particles for now.
We just want the system to be complete and then design later can drop in the final assets.

Just drag the Particle System game object from the Hierarchy window onto the Timeline window and select Activation Track.

Position as necessary.

When we play our Timeline, we can clearly see when the particles will be active.
Nice.

Now, let’s disable our Particle System game object by default and set it to Play on Awake.

If we hit play on the game scene itself and then hit play on the Timeline window for our ChestOpenDirector, we can see that our sequence is working as expected.

Alright, we are back to making progress!

I’ve updated my chest close animation to be take a full second and then added it to the ChestCloseDirectorTimeline.

We can see it working as expected above.

Testing Our Progess

Just to keep an eye on things as we go, let’s test these in Editor Play Mode.

If we hit Editor Play Mode, select ChestOpenDirector game object and hit play in the Timeline we can see that it opens and the crown rises as desired.

Clicking on ChestCloseDirector closes the chest as expected.

Note that the crown still hanging in the air is okay because later when we trigger these Timelines from a script, we will disable the crown game object’s visibility after chest opens and place a duplicate crown on the player character model’s head.

While we are thinking about it, let’s go ahead and add another Crown prefab as a game object to our Player Character model.

Let’s go ahead and position the crown on our player character’s head.

Now let’s disable the Crown01 game object that is childed to our Player Character game object.

Once the player opens the chest, the animations will play, we will disable the crown from the chest and enable the crown on our character.

Everything is starting to fall into place!

Scripting it Out

The first script I want to add is the TriggerLoot script to my TriggerLoot game object.

This game object sits in front of the treasure chest so that when the player walks up to the chest it will detect a collision and trigger the timelines and animations we’ve created.

Next, I want to add a DirectorsControl script to my parent Directors game object.
This way I can access all of the childed Director game objects and components fairly easily.

Planning

Thinking ahead a little bit, I intend to use the TriggeredLoot script to call methods from the DirectorsControl script which will start/stop the timelines.

For the TriggeredLoot script, all I really need is to establish a link to the DirectorsControl script and check for on player collision.

To make linking and checking for player collision easier, we’ll make use of Tags.

Tag You’re It

The player is the easiest since the Player tag already exists, so select your player character game object and assign “Player” as the tag.

This tag may be unnecessary but it won’t hurt.
If we end up not using it, we can always remove the tag from the Tags and Layers window.

Can We Code Something Now?

Time to put our C# skills to good use.

We’ll add our OnTriggerEnter() method first.
Then a simple if statement to check for the player tag on the “other” game object that collided with this game object.

Next, we need to know if the chest is currently in an open state.
If it is, triggering the open animations again will look really bad!

If the chest is not currently open, we’ll call on the DirectorControl script to play the Timeline that opens the chest.

So, right now, I know I need a public variable on DirectorControl that indicates whether the chest is open or closed.

First, I know that I will need a using statement for Playables to work with my Director components in Unity.

Second, we need a vew variables.
We need a chest state and two PlayableDirector variables for each timeline.

I’ve added a few methods I know will be needed.
Methods to open/close chest and a method I use often called DoNullChecks().

Not shown above, but the only thing in the Start() method is a call for DoNullChecks() to run.

I’ve also got some commented pseudo code to point me in the right direction.

Before I forget, I also want to assign my Director game objects to the DirectorsControl script as shown above.

Back on the TriggerLoot script I need to add a couple variables.
Namely one for the Directors game object itself and another for the DirectorsControl script on it.

Now we’ll assign the Directors game object to our TriggerLoot script as shown above.
We could probably also assign the script directly in the Editor as well, but I want to show how to do it in code.

In the TriggerLoot script I’ve added my customary DoNullChecks() method which is called in the Start() method.

I’ve also added the Directors script assignment to the Start() method.

By default, we coders like to make everything private.

I should have known better here, but my habit got the better of me!

In the DirectorsControl script we need to make the Open/Close Chest methods public so that we can call them from our TriggeredLoot script.

Now, when we are looking at our _directorsControlScript we see the open and close chest methods come right up.

I just want note that we could alternatively have left our chestIsOpen variable as private in the DirectorsControl script and did an if/then check on it there instead of checking here.

Just to be bullet-proof, I’m going to check it both here and there.

So, back in our DirectorsControl script, I’ve updated the OpenChest() method with a chestIsOpen check and called the openChestDirector.Play() method.

Also, don’t forget to set chestIsOpen to true before exiting the if/then statement.

We’ll do more or less the same thing in our CloseChest() method.

Up until now, I hadn’t really decided under what conditions the chest would close.

For brevity’s sake (too late for this article!), we’ll just do it on trigger exit.

As you can see above, we simply add OnTriggerExit() method and more or less do the same things we did with OnTriggerEnter, just calling the opposite method.

Alright, we can test what we’ve got now.

We can see that on entering our TriggerLoot game object’s box collider, the chest open animation sequence plays correctly.

The crown still hangs in the air, but that is okay we’ll get to it.

When the player exits the TriggerLoot game object’s box collider, we see the close animation play.

And then it happens.
It just opens right back up!

If we select our ChestCloseDirectorTimeline, open the Timeline window, and click on the ChestClosed animation clip, we can see that “Animation Extrapolation” is set to “Hold” and “Infinity” which is what we want.

This won’t happen though unless we drag the right side of our animation clip in timeline to the right a bit until we see the word “HOLD” appear right on the track.

Now if we hit play in the Editor and test this out, we can see that it is working as expected.

That just leaves the floating crown and placing it on our character’s head!

We could turn off and on in the scripts, but why not showcase Timeline’s ability to accomplish the same task?

We drag each crown game object into our Timeline window and select “Activation Track”.
It helps to rename these as well.

I’ve renamed all of the tracks for the ChestOpenDirectorTimeline for clarity’s sake.

I had issues where the playback activations for the crowns wasn’t working, despite those being the game objects dragged in for the created activation tracks.

If this happens to you as well, select the appropriate director game object and drag and drop them onto the Playable Director component.

After playing around with the timing of the activation tracks on the ChestOpenDirectorTimeline, we can hit Play in the Editor and test it out.

Looks pretty good.

We can see that currently the crown isn’t moving with the head, so we’ll reparent that on our character game object accordingly.

There is still more fine-tuning to be done, such as when the player leaves the trigger collider mid-timeline sequence triggering the lid to close.

Also, once acquiring the crown, re-triggering the chest to open makes the crown on the character head disappear due to the activation track.

In a production game or even a serious prototype, we would want to drill down and correct all of these.

As far as our goal for basic functionality is concerned, we have completed a Timeline sequence with newly created animation tracks, added activation tracks for particles and crowns alike.

Let’s just take the win and call it a night!

Addenda — Additional Tweaks and Fixes

The Issues

1. Exiting the trigger box while the chest open Timeline is still playing triggers the close chest

2. After player has the crown placed on head, walking back up to chest removes it due to activation track on ChestOpenDirectorTimeline.

The Plan

  1. Add ChestClosed animation to the ChestOpenDirectorTimeline

After adding our ChestClose animation to the ChestOpen Timeline, we can see that it is working correctly if the player stays in the trigger box.

Remove OnTriggerExit() method on TriggerLoot script.

We can just comment out the OnTriggerExit() method in the TriggerLoot script.
We can always remove it later when we are happy with our results.

Double check that our ChestClose_anim settings are as shown above.

As we can see above the player can now walk away from the TriggerLoot collider and the chest no longer closes.

Resolve crown on character head activation issue created by extending the Timeline sequence for chest open.

We just need to extend our crown (attached to character) activation track to the end of the sequence as shown above and the crown will remain after the Timeline sequence completes.

ISSUE 1: Solved

2. We need to modify our code to track the status of the ChestOpen Timeline sequence and prevent it from triggering.

I’ll use a Signal Track to accomplish this more elegantly.

Sadly, this makes the work done above in Step 1 of The Plan irrelevant.

First, let’s add a signal receiver component to our Directors game object since it also contains the script that controls the open/closing of the chest Timelines.

We need to add the Signal Receiver component, create a signal for chest closing, and assign it to the component as shown above.

We also need to add the appropriate reaction on the Signal Receiver component.

We want to call the DirectorsControl script and the CloseChest() method.

This will reset the Boolean for chest open/close state appropriately and with the correct timing.

We can see in the DirectorsControl script above the methods in question do indeed reset our Boolean value correctly.

We need to remove the ChestClose animation from our ChestOpen Timeline as this will now be redundant and cause issues.

Now add the Signal track to the ChestOpen Timeline.

We should see a warning on our signal emitter because we have not assigned the Signal it will emit.

We can set that in the Inspector window.

Issue #1: Solved (again)

3. Now we have the proper framework to fix the crown on player head disappearing when the player walks up the chest a second time.

To avoid this issue, we can simply get rid of the chest and trigger box game objects at the end of our sequence.

We add two activation tracks, one for the Treasure Chest game object and one for the TriggerLoot game object.

We end their active state just before the entire sequence finishes on the Timeline.

ISSUE #2: Solved.

And that’s it, now I’m much happier with the functionality on this!

Adding an Audio Track

Adding audio is pretty simple, just drag and drop your audio files into the Timeline.

With the Audio Track not being assigned an Audio Source, the audio plays directly without spatial influence in Editor Play mode.

If we want the sound to be Spatial, we’ll need to add an Audio Source to our Treasure Chest game object and assign it to the audio track.

There we go, the audio is now linked to our Treasure Chest game object.

We can also adjust the volume and Spatial Blend by clicking on the Audio Track and modifying these settings in the Inspector window.

These are the settings that worked for me, but play with the sliders as needed.

Alright, I think that is my last addenda on this one!

--

--