In this chapter, we are going to cover two main functionalities that Unity offers developers for issuing AI to our game objects and for animation that supports logic.
Unity has a ready-made system for our game objects to issue a path-finding algorithm where a game object can be given an area to patrol. This can be very handy in a series of games that use enemy soldiers to walk up and down a corridor looking for the player. A soldier can react depending on the behavior given to them.
The other functionality we will be covering is the Timeline component, which is used for animation in scenarios such as cutscenes in games/films. You may be thinking that we already covered an animation system back in Chapter 4, Applying Art, Animation, and Particles. Yes, you are right, but for a more complex animation that holds multiple game objects and animations, transitions and states can get complex pretty easily. Also, Timeline supports a series of tracks that work specifically with our code, and we can add our own custom animation tracks to our timeline.
These two main features will be assigned to our Killer Wave game project. The Navigation Mesh (NavMesh) controls a flock of small Non-Player-Character (NPC) robots that will move away from our player's ship as if they're panicking to stay alive.
Timeline will be used to apply a mid-level cutscene where our player will see the end-of-level boss rush past them and lights in the scene will flash red.
Finally, we will end this chapter with the last mini-mock test, which will include questions covering the content from this chapter and previous ones.
The following topics will be covered in this chapter:
Let's start by reviewing what skills are covered in this chapter.
Programming core interaction:
Working in an art pipeline:
Programming for the scene and environment design:
The project content for this chapter can be found at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition.
You can download the entirety of each chapter's project files at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition.
All the content for this chapter is held in the chapter's unitypackage file, including a Complete folder that holds all of the work we'll carry out in the chapter.
Check out the following video to see the Code in Action: https://bit.ly/3LsC0B4.
In this section, we are going to prepare our new level3 scene in two parts – the first part of our game will have some three-dimensional art assets for the player to potentially collide with. Also, the environment will be used for our new fleeing enemies. The second part of this section is used to upgrade our camera's behavior so that instead of it being static, we now require it to move across the level. The player will also be given the same fixed speed as the camera.
By the end of this section, we will have an environment set up, as shown in the following screenshot:
The arrow going from left to right in the previous screenshot shows the path of the camera. Once it reaches a particular point, it will stop, and the level will end shortly after.
It is worth mentioning that within the level3Env prefab is a game object that holds a script called BossTrigger. This game object contains a box collider, a rigid body, and a BossTriggerBox script.
The purpose of the BossTrigger game object is to activate the Timeline animation when the camera moves into the BossTrigger collider. We will discuss this further in the Exploring the timeline section of this chapter.
Let's continue by moving our level3 file's environment into the scene:
The following screenshot shows our scene setup, which is ready for our player to fly into:
So, we need to make some changes to our level3 scene so that it acts in a way that ensures the camera supports the new environment and the environment itself doesn't have extra assets that we don't need, such as the animating texture quad for the background and the prefab particle system for the stars flying past. Be aware, if any of these game objects are in a prefab state, be sure to unpack (right click game object in Hierarchy Prefab | Unpack) them otherwise we will be changing the content in levels 1 and 2.
Alter the level3 scene in the Hierarchy window by doing the following:
Our Hierarchy window for level3 will now look like the one in the following screenshot:
We now need to make it so that the Camera component supports a far clipping-plane value. To do this, we need to do the following:
So, we have adjusted the clipping plane of the camera to show all of the level3 file's environment, removed the GameSpeed game object that helps art assets for our previous levels, and added GameManager to level3 to make development easier. We now need to turn our focus toward making the camera actually move instead of creating the illusion it is moving, as with level1 and level2.
I have created a small script that will make the camera move from one point to another; everything inside the script demonstrates elements of code that we have already covered throughout this book. So, there's no need for you to create the script, but understanding it is obviously the main purpose.
We are going to attach the script to the main camera in our scene to control its movement. Follow these instructions:
Let's modify our Player script to act on the movement of the camera for level3:
float camTravelSpeed;
public float CamTravelSpeed
{
get {return camTravelSpeed;}
set {camTravelSpeed = value;}
}
float movingScreen;
The camTravelSpeed variable that we just entered will be used as an extra multiplier to set the pace of the player's ship when the camera moves along the x axis.
The second variable, movingScreen, will hold the result of Time.deltaTime multiplied by camTravelspeed. Later, movingScreen will be used when it comes to comparing the player's ship's x axis.
movingScreen = width;
Inside the Start function, we will add our width float measurement to movingScreen (this happens after width has been updated in the Start function), as this will be the starting position before it receives its increments from Time.deltaTime and camTravelspeed.
Still inside the Player script, scroll down to the Movement method.
if(camTravelSpeed > 1)
{
transform.position += Vector3.right *
Time.deltaTime * camTravelSpeed;
movingScreen += Time.deltaTime * camTravelSpeed;
}
In the code that we've just entered, we will run a check to see whether camTravelSpeed has increased from our new CameraMovement script. If camTravelSpeed has been updated, we fall into the scope of the if statement.
Within the if statement, we will increment the player's ship's x axis to the right, multiplied by Time.deltaTime and camTravelSpeed.
The second thing we do is add the movingScreen value that originally holds the current width of our playing area. However, because the screen is moving, we need to increment the playing area so that the player doesn't get left behind or go too far out of the camera view.
The last amendment we add to our Player script is our horizontal movements, still in the Movement method.
if (transform.localPosition.x < movingScreen+(width/0.9f))
If the player presses the right arrow on their keyboard/joypad, we can run a check to see whether the player's x axis is less than the movingScreen float value; plus, we will include a buffer to push the player further to the edge of the screen.
if (transform.localPosition.x > movingScreen+width/6)
Similar rules apply where we make use of the movingScreen variable, which is constantly incremented along with a slight buffer to keep our player's ship within the game screen.
Before we move on to the next script, we need to uncomment two lines of code in our new CameraMovement script so that it can interact with the Player script.
Back in the Project window, open the CameraMovement script and uncomment the two following lines by removing //. The first line to uncomment is the following:
// GameObject.Find("Player").GetComponent<Player>().CamTravelSpeed = camSpeed;
The second line to uncomment is the following:
// GameObject.Find("PlayerSpawner").GetComponentInChildren<Player>().CamTravelSpeed = 0;
Now, these two lines of code can alter the speed of the player ship.
Next, we need to update our GameManager script so that it recognizes the difference between level1, level2, and level3, which has a moving camera.
So, let's move to the GameManager script and add two main elements – the camera speed and noticing the difference between scenes. Let's start by opening the GameManager script:
You may or may not have looked into the CameraMovement script that we attached to the main camera, but inside that script is a variable called camSpeed. This variable manipulates the camera's speed; in our GameManager script, we set the speed of the main camera.
The main takeaway from this is that the CameraMovement script will manipulate the camera's speed from what is set in the GameManager script.
public void CameraSetup()
It will then change to this:
public void CameraSetup(float camSpeed)
gameCamera.GetComponent<CameraMovement>().CamSpeed = camSpeed;
Note that the line of code we add to our CameraSetup method needs to be added after the main camera has been stored in a gameCamera variable. Before continuing we also need to remove the reference to the CameraSetup from the PlayerSpawner script as we also don't need this anymore.
The last thing to do in the GameManager script is to update the LightandCameraSetup method so that when the CameraSetup method within it is called, it takes a value that sets the main camera's speed. So, in level1 and level2, we want the camera to continue to not move; in level3, we will need to apply speed to the camera.
switch (sceneNumber)
{
case 3 : case 4 :
{
LightSetup();
CameraSetup(0);
break;
}
case 5:
{
CameraSetup(150);
break;
}
}
So, before, our switch statement had all the cases run LightSetup and CameraSetup within cases 3, 4, and 5. But now, in the previous code, we have split the roles up. In cases 3 and 4, we run LightSetup as usual, and now, because CameraSetup now takes a float value, we set the camera speed to 0.
In case 5, which is the build number for our level3 scene, we ignore LightSetup, as we won't be using a directional light in this scene. We run CameraSetup but give it a value of 150, which will also be the speed we set the camSpeed variable within the method.Save the GameManager script.
The previous screenshot shows a series of events of what happens after we press Play in the Unity Editor Let's go through each event sequentially:
In the next section, we will add our second enemy type (flee enemy), which will flee across the span of our new art.
In this section, we are going to introduce a new enemy that will attempt to flee with frenzy-type behavior from our player's ship. This enemy's behavior will be implemented with the use of Unity's built-in NavMesh functionality.
As you can imagine, this built-in feature from Unity can answer a lot of problems with regard to games with NPCs, similar to ones in the Metal Gear Solid game, where the player has to sneak around and not get detected by the enemy soldiers.
NavMesh gives the enemy soldiers a path to walk around, and then if they see the player, their behavior changes from Patrolling to Attack.
So, with our game, we are going to implement NavMesh but make it so that our enemies react differently to how they would in Metal Gear Solid. We will add multiple flee enemies in clusters to our third-level scene. This chaotic, distracting behavior will make the final level more challenging for our players.
The following screenshot shows our fleeing enemy with a cylindrical radius around it. This radius is called the agent radius and can be altered to stop other obstacles and enemies from intersecting with each other:
Before we add these fleeing enemies to our scene, we need to tell the fleeing enemies where they can move around by baking a NavMesh first.
First, we need to select the game object that we will use to bake, which also means we need to deselect game objects that don't need to be Navigation Static. To bake a NavMesh, follow these instructions:
So, we have just deactivated all of our environment art assets in the level3 scene so that they are not recognized for navigation baking. We now need to turn on one of the child game objects within the _SceneAssets hierarchy:
The following screenshot shows that from the Hierarchy window, we have selected corridorFloorNav (denoted by 1.):
We now need to check the Navigation window so that we can set it to bake our CorridorFloorNav mesh.
It's likely that the Navigation window will appear at the top-right corner of the Editor If it doesn't and has appeared as a floating window somewhere in the Unity Editor simply click and hold on the Navigation tab and dock it next to the Inspector tab, as in the following screenshot:
Further Information
It's also worth noting that a game object that is manipulated in the NavMesh is referred to as an agent.
In this window, we are presented with a series of options for our navigation bake. This may look a little intimidating at first, but the blue cylinder is basically our agent (the fleeing enemy) and the following parameters are based on how flexible our agent is with the navigation path it'll be walking around. Let's briefly go through each of the options so that we are aware of its features before we bake:
Further Information
Information about the Off Mesh Link component can be found at https://docs.unity3d.com/Manual/class-OffMeshLink.html.
The following screenshot shows the navigation bake settings we just went through:
Thankfully, the Bake properties of our default setup window will work just fine as it is.
Once the navigation bake has completed, corridorFloorNav in our Scene window will have a NavMesh sitting on top of its mesh.
If you can't see the navigation-baked mesh, make sure that the Show NavMesh checkbox is ticked at the bottom-right corner of the mesh. The following screenshot shows our NavMesh and the Navmesh Display box:
The last thing to do in this section is turn off the corridorFloorNav game object's Mesh Renderer component. We only needed this component to be active for the NavMesh to be baked.
To turn off the corridorFloorNav game object's Mesh Renderer component, do the following:
The following screenshot shows the highlighted box that needs unchecking:
This is all that is needed to allow our fleeing enemies to move around.
Further Information
If you would like to find out more about the Navigation window, check out https://docs.unity3d.com/Manual/Navigation.html.
So far in this section, we have discussed the requirements of AI and how it is used in games and how we are going to apply such methods to our fleeing enemies, with the use of the NavMesh system that Unity offers as standard.
Now that we have our NavMesh baked for our agents to move around on, we can look into setting up our NavMeshAgent component to give our agents a set speed, acceleration, stopping distance, and more in the next section.
In this section, we will be continuing from setting up our NavMesh but shifting the focus toward the agent (the fleeing enemy game object). The agent will be moving around the baked NavMesh.
It is necessary for the fleeing enemy game object to be able to react and move within the NavMesh but also be able to move in a way that suits the behavior of what we're trying to achieve. For example, the enemy is to flee with an element of panic; so, we need to consider characteristics such as when the enemy decides to move, how quickly the enemy will react, and how fast the enemy can move. These properties, and more, come from a component called NavMeshAgent.
NavMeshAgent is a required component that will be attached to each of the fleeing enemy game objects. The purpose of this component is to make it so that the game object is recognized as an agent and will stick to the NavMesh.
Before we add NavMeshAgent to the fleeing enemy, we need to create a prefab of the enemy so that we have a place where we can grab and clone copies of multiple enemies:
That's our fleeing enemy created; now, we can apply a material to it by doing the following:
The following screenshot shows the enemy_flee prefab with its update material and the correct Transform values:
You may have noticed in the previous screenshot that enemy_flee has hard, shiny edges. We can make these appear smoother in our three-dimensional model import settings by doing the following:
We can now adjust the Smoothing Angle value with the slider to change the smoothness between angles, as shown in the following screenshot:
In the previous screenshot, you can see three distinct stages in making the model look smoother. This can be done with any three-dimensional model imported into Unity.
Coming back to enemy_flee in the Hierarchy window, as this is an enemy, we also need to give it an Enemy tag so that the player recognizes it as such if and when they collide with each other:
The following screenshot shows the Enemy tag selected for enemy_flee:
We are now ready to apply NavMeshAgent to the enemy_flee game object. With enemy_flee still selected, do the following:
enemy_flee now has NavMeshAgent attached to it. As previously mentioned, let's go through each of the properties. The following screenshot also shows the NavMeshAgent default values (these may differ from your default values, but don't be concerned, as we will be changing the majority of the values):
For NavMeshAgent, we will set its agents to a high speed, rotation, and acceleration value to make these enemies react fast to match their fleeing behavior.
In this section, we created the fleeing enemy prefab and gave it a material. We also applied a NavMeshAgent component to our enemy so that it's calibrated and ready to react.
The following screenshot shows what the fleeing enemy looks like with its NavMeshAgent component wrapped around it, which can only be seen in the Scene window:
In the next section, we will give the fleeing enemy prefab a collider so that when the player makes contact with it, the player and the enemy are destroyed with the soon-to-come scripting.
In this section, we are going to add a capsule collider to the fleeing enemy so that a collision can be detected from the player's ship when they collide with each other:
The following screenshot shows enemy_flee with its capsule collider; these values may differ to yours:
The fleeing enemy is nearly ready to be tried out in the game. We just need to add a script to tell the game object what to do when it gets within a certain distance of the player. We will cover this in the next section.
In this section, we will be making it so that the fleeing enemy detects when the player is getting close to them. If the player is too close, we will make it so that the enemy begins to flee.
We will be taking a script that is partially made and import it into this chapter, as the majority of the EnemyFlee script will contain a similar setup to the previous enemy that we made in Chapter 2, Adding and Manipulating Objects. Follow these steps:
The EnemyFlee script will contain similar-looking code to the EnemyWave script. The enemies in our game will carry the same properties, such as giving and taking damage when hit or dying, detecting a collision, and inheriting their own scriptable object asset. There's no real need to go through this process again. What we are interested in is how the enemy_flee game object acts.
To add the fleeing behavior to the EnemyFlee script, we need to do the following:
using UnityEngine.AI;
In our script, we will need access to the NavMeshAgent component (which is attached to our enemy_flee game object). The AI library gives us this functionality.
GameObject player;
bool gameStarts = false;
[SerializeField]
float enemyDistanceRun = 200;
NavMeshAgent enemyAgent;
The first variable will be used to store the reference to the player's ship, as we will be comparing its distance later on. The bool value will be used as part of a delayed start for our script. We will talk more about this later on as well.
enemyDistanceRun will be used as a rule to "act" within the measured distance between the player and our fleeing enemy. We have also added the SerializeField attribute to this, as it will be handy to change these values in the Inspector window while keeping this variable private. Do try a different value if you think 200 is too low. Experiment!
Finally, we have NavMeshAgent, which will be required to receive data from the player and fleeing enemy results.
void Start()
{
ActorStats(actorModel);
Invoke("DelayedStart",0.5f);
}
void DelayedStart()
{
gameStarts = true;
player = GameObject.FindGameObjectWithTag
("Player");
enemyAgent = GetComponent<NavMeshAgent>();
}
The Start function contains the ActorStats method, which will update our enemy's abilities (the health value, points added to the score, and so on), similar to our enemy_wave game object. We will also run an Invoke function, which takes the name of the method we wish to run along with a parameter that determines when the method will be run.
We are running a short 0.5f second delay, giving the player's ship time to be instantiated into the scene before we take a reference from it. We set a Boolean value to true to say that the update function can run the content within it, which we will cover shortly. The final thing we do is take reference from the NavMeshAgent component attached to the game object.
We need to add a slight amendment to our speed value in our ActorStats method. Because we are affecting NavMeshAgent_speed, we need to manipulate this directly.
To make the enemies' speed adjustable, add the following line of code within ActorStats of the EnemyFlee script:
GetComponent<NavMeshAgent>().speed = actorModel.speed;
The enemy flee speed value is now hooked up.
void Update ()
{
if(gameStarts)
{
if (player != null)
{
float distance = Vector3.Distance(transform.position,player.transform.position);
player.transform.position);
if (distance < enemyDistanceRun)
{
Vector3 disToPlayer = transform.position -
player.transform.position;
Vector3 newPos = transform.position + disToPlayer;
enemyAgent.SetDestination(newPos);
}
}
}
}
In the Update function, we will run an if statement to check whether the gameStarts Boolean value is true; if it is true, we then check to see whether player_ship is still in the scene. And if that is true, we move on to the content in that if statement. Within this if statement, we use Vector3.Distance to measure the distance between the player's ship and the fleeing enemy. We then store the measurement as a float value called distance.
Next, we run a check to see whether the distance measured is less than the enemyDistanceRun value, which is currently set to 200.
If the distance variable's value is lower, then that means the player's ship is too close to the fleeing enemy, so we run the following steps for it to react.
We are now ready to attach the EnemyFlee script to our enemy_flee prefab. Let's do this now; then, we will be able to test the results:
The following screenshot shows the scriptable object asset for the EnemyFlee script's Actor Model parameter on the right:
We now need to make our enemy_flee script recognizable on the radar map in the game HUD, as with the enemy_wave game object. As a reminder, we made a radarPoint object before in Chapter 9, Creating a 2D Shop Interface and In-Game HUD. So, in this chapter, we're going to speed things up and use a ready-made radarPoint object to attach to the enemy_flee game object. The only difference with the ready-made radarPoint game object is that I have attached a small script called RadarRotation that will make the radarPoint sprite always face the camera, regardless of which rotation the enemy_flee game object makes.
The RadarRotation script takes the current rotation in the Awake function, followed by reapplying the rotation on LateUpdate.
What Is LateUpdate?
LateUpdate is the last function called in Unity's execution order game logic. The benefit to this is that there is no fighting between the rotation of the radarPoint object and the enemy_flee rotation being called at the same time. If you would like to learn more about the execution order, check out https://docs.unity3d.com/Manual/ExecutionOrder.html.
To attach the pre-made radarPoint object to the enemy_flee prefab, we need to do the following:
If you also have the EnemySpawner object in the scene close to the start of the level, push it back along the x axis as far as 1000 to get it out of the way.
Feel free to select the enemy_flee object in the Hierarchy window and press Left + Ctrl (Command on macOS) + D on your keyboard to spread a few fleeing enemies around to make the level more interesting, as shown in the following screenshot:
The following screenshot shows our new fleeing enemies trying to escape from the player in pure panic!
That's the end of this section, and hopefully, you now feel comfortable with this introduction to using the NavMesh and agents. As you can imagine, our fleeing enemy could have other events attached to it, such as shooting bullets at the player when at a safe distance, taking cover around a corner, and calling for help. Adding a series of events to an NPC would require a finite state machine to go through each appropriate event.
In this section, we introduced a new enemy that acted differently from our current wave enemy. We also became familiar with the ready-made path-finding algorithms offered by Unity, such as NavMesh.
We are going to move on to the next section, where we will introduce the timeline, which works as an animator but can also be used with regard to blending the logical behavior with our components – for example, to make a light blend into a different color by using scripting.
Timeline is a component in the Unity Editor that is intended to put a sequence of animations together, which is attractive to industries such as film and TV. Timeline is also welcomed in games that may have cutscenes to help tell a story or introduce a player to a level. Unity also has two other useful components – Animator Controller and Animation Clips – as you will know if you have been following this book, as we covered these other components in Chapter 4, Applying Art, Animation, and Particles. They carry out the same tasks, but as a scene becomes busier with a series of individual animation clips, things can get messy quickly in the animator controller, with the multiple states transitioning between each other.
The following screenshot shows the animator controller with multiple states and transitions:
Timeline supports three tasks:
These three capabilities on their own limit Timeline – for example, if we want to change the color of light, Timeline wouldn't be able to change the individual property alone. To change the color of light, we will need to change the light's property values in the Animation window itself. However, with some extra scripting to our timeline, we can introduce dragging and dropping game objects that hold components, such as a light component, where changes can be made on the fly.
In this section, we are going to start by animating a large robotic craft in the timeline. Then, we will discuss playables and how they can extend a timeline's functionality. Finally, we will implement additional tracks to the timeline to control the color of the lights and fade the level into darkness once the player reaches the end of the level.
Let's start by creating our Timeline game object and adding a Timeline component to it in the next section.
In this section, we are going to get more familiar with the Timeline component and create some of our own animations with a large flying robot. When setting up the timeline, we will also discuss the components and properties that are involved.
To add a timeline to our scene, do the following:
With our Timeline game object still selected in the Hierarchy window, we can now open our Timeline window.
At the top of the Unity Editor, click on Window | Sequencing and then Timeline; the following screenshot shows this:
It's likely that the Timeline window will appear in the same window layout as your scene, which isn't ideal, as we want to see our Scene while animating. To move the Timeline window to a better place, click on the name of the Timeline tag and drag it down to the bottom of the screen, where the Console and Project windows are. The following screenshot shows my current Unity Editor layout proportions:
We can now continue to create our Timeline asset, where we will be creating our new animations for all of our game objects and their components.
To make a playable Timeline asset, do the following:
Our Timeline asset has been created.
If you have been following along with this book, at first glance, the Timeline window will likely look like the Animation window we saw in Chapter 4, Applying Art, Animation, and Particles. If so, that's good! A good section of the controls and methodology will be familiar to you. One of the main differences of Timeline is that any of the game objects can be dragged into the Timeline window without needing to have any kind of hierarchical relationship between them.
The following screenshot shows our Timeline window holding the Timeline game object in its first Timeline track:
Also, in the Inspector window, our Timeline game object has gained some extra components. The following screenshot shows two of the added components – Playable Director and Animator:
We worked with the Animator component in Chapter 4, Applying Art, Animation, and Particles, so for more details about this particular component, refer back to that chapter. Also, we don't actually do anything with the Animator component; it's just a required component for our Timeline setup.
The other component we gain when creating a Timeline asset file is Playable Director. It's the responsibility of this component to keep a relationship between the timeline and the game objects/components that are being manipulated. So, let's go through each of the properties under the Playable Director component to briefly get a general understanding of them.
First, we have Playable. When we click on the Create button in the Timeline window, we create a Playable file. This file holds all of the animations and game object/component instances relating to the timeline.
Then, we have Update Method. This parameter offers four properties that control how time affects the timeline. These properties are as follows:
Next, we have Play On Awake. If this checkbox is ticked, our timeline will play as soon as the scene is active.
The next parameter is Wrap Mode. This property determines what happens when the timeline has finished playing:
Initial Time adds a delay in seconds before the timeline begins.
Finally, we have Bindings. When a game object or component is dragged into the Timeline window, the Bindings list will update and show what object is connected to the timeline.
So far, we have discussed the timeline and introduced it to our scene. We have also gone through the components that are required to make the timeline work.
Now that we are more familiar with the timeline and the components that work in conjunction with it, in the next subsections, we are going to incorporate our large boss ship into our level3 scene and animate it through the timeline.
In this section, we are going to take a static UFO-looking game object from our imported project files, drop it into the scene, and attach it to the timeline. From there, we will animate our UFO so that it spins and moves across the scene on two occasions.
To bring the large boss UFO game object into our scene before animating it, we need to do the following:
Next, we need to position the boss so that it's in our scene but out of view of the camera. That way, when it comes to animating the boss in the timeline, we can change its position and rotation when required.
The following screenshot shows the imported boss prefab, which contains a list of components and property values:
The boss object holds the following component and property values, as shown in the previous screenshot:
Before we move on to the next section, we need to add a RadarRotation script to the radarPoint game object, which is a child of the boss game object. This script will make radarPoint always face the camera:
Now that the boss object is in the scene, we can add it to the timeline in the next section.
In this section, we are going to take the boss game object from our Hierarchy window, drag it into the timeline, and animate it to fly past the player's ship at a particular point in the game. Finally, we will make the boss object greet the player at the end of the level before jetting off after the player.
Further sections will continue to make use of the timeline, including using specialized Playable scripts from the Asset Store. But for now, let's get the boss object animated.
To animate the boss object in the timeline, do the following:
To trigger the Timeline animation, we need to apply a box collider to the main camera so that it is recognized when it collides with the BossTrigger game object, which we mentioned at the beginning of this chapter.
To have the main camera recognized as a trigger, we need to do the following:
Let's now continue setting up our Timeline window so that we can drag our boss game object into it:
The Timeline game object and the Timeline tab should now be selected. We can remove the Timeline game object from the Timeline window because we aren't going to animate it.
The following screenshot shows the Timeline game object being deleted:
A dropdown will appear with a choice of three selections:
We will now have the boss game object in our Timeline window, and our boss game object will gain an Animator component in the Inspector window.
The following screenshot shows what our timeline currently looks like:
Next, we will start adding keyframes to our Timeline window, which will affect our boss's position and rotation. Let's start by locking our Timeline window so that when we click on another game object, it will remain active:
The padlock button is highlighted in the following screenshot:
Let's now move on to the next section, where we will start adding keyframes to the timeline and make our boss game object move and rotate in two phases of the third level. Let's start with phase one.
In this section, we will be adding keyframes to the Timeline window for the boss game object. This will make the boss game object travel from one point to another while rotating on its center pivot.
To start adding keyframes for the boss game object, do the following:
We will now start recording our boss's position and rotation.
Now, to animate the boss object from one end of the corridor to the other, we need to add another keyframe for the boss. Follow these steps:
Further Information
The Timeline and Animation windows have the same navigation rules with regard to zooming and panning in either window. Holding down the middle mouse button and moving the mouse will pan. Rolling the middle mouse wheel up or down zooms in and out. Hovering the mouse cursor over the animation bar and pressing F on the keyboard shows all the keyframes on the window.
The following screenshot shows a bird's-eye view of the boss game object moving from left to right:
Later on, when we play the third level, we will see a moment where the boss game object rushes past the player in the distance. For now, we will continue by adding more keyframes to our Timeline window before moving on to looking at playables.
Let's move on to phase two of animating our boss game object.
In this section, we are going to animate our boss for a second time before the level ends as some form of resolution for the ending of this third and final level.
We are going to continue from the same Timeline track that we started in the previous section.
So, let's continue animating our boss from where we left off:
With the boss object still selected in the Hierarchy window, we can now make changes to the Transform property values. Set the boss object's position and rotation values to the following:
The following screenshot shows where our boss sits in phase two:
The following screenshot shows a bird's-eye view of each of these positions with their Timeline frame numbers:
If you want to adjust the boss game object rotation more, it is recommended to have boss selected in the Hierarchy window. Make sure the local position is selected (denoted by 1 in the following screenshot) and, with the Timeline still in record mode, rotate the z axes several times (denoted by 2).
Finally, scrub backward and forward (move the Timeline indicator) in Timeline to see the result you are given.
When you are happy, stop recording.
As you can see in the previous screenshot, the boss game object flies in from the left to the right toward where the player will be. The boss will stop rotating, pause, turn around, and zip off to the far right.
Let's press Play in the Unity Editor and see the results of our level3 scene so far, with the boss versus our player's ship throughout the level3 scene.
The player moves through the level, and the boss animation is seen on two occasions. What we do see that we likely shouldn't is the large yellow dot of the boss even when the boss has moved on. It would be good if we could make the boss disappear off the radar when we can't see the game object itself.
The following screenshot shows the boss object on the left screen, which is not visible on the right screen but still detected on the radar:
Let's make use of the timeline before ending this section to simply turn the radarPoint game object off and then on when we see the boss game object. Follow these steps:
Let's now add an activation clip to decide when the player should and shouldn't see the radarPoint object. Follow these steps:
For the second occasion, we can set the boss object from around 1020 to 1420.
Repeat this process by doing the following:
We have now set the settings so that the boss game object and its radarPoint object are active at the same time.
We have successfully introduced a timeline to our scene and customized it so that it accommodates a new game object that needs to be animated throughout the third and final level of our game. In the next section, we are going to look further into extending the features of the timeline by introducing animating lights.
In this section, we are going to add more functionality to the timeline by increasing its standard track selection, as shown in the following screenshot:
From this new extended track selection list, as shown in the previous screenshot, we will make use of Light Control Track.
It is possible to see a bigger selection from the drop-down list; this, however, is beyond the scope of this book. However, if you are interested, I will direct you to what to read to find out how to extend the list later on in this section.
In the next few sections, we will increase our tracklist with the aid of the Asset Store and download a free asset from Unity to increase our Timeline functionality. Then, we will animate the lights in our scene, which wouldn't have been possible before.
In this section, we will take a shortcut in scripting to extend the timeline's functionality by going to the Unity Asset Store and downloading a free package called Default Playables. We will discuss the main features of playables and what they entail, but it is too lengthy to discuss it as a scripting approach.
Playables organize, mix, and blend data through a playable graph to create a single output. To download and import Default Playables to our list, do the following:
Default Playables can now be downloaded from your Unity project via Package Manager.
Package Manager gives us access to assets we've downloaded from the Asset Store and also other "packages" we can add to our project. We will talk about Package Manager more in the next chapter.
To download Default Playables into our project, we need to do the following:
We will now be presented with the assets we own from the Asset Store.
We will then be presented with a list of folders and files that will be imported into the project.
We now have extra functionality added to our timeline, with an added folder in our Assets folder called DefaultPlayables. Also, as mentioned, to add even more functionality (such as timeline tracks) to the timeline, check out the file inside the DefaultPlayables folder named DefaultPlayablesDocumentation.
Let's now move on to the next section, where we will make use of manipulating the lights in the scene.
In this section, we will continue working on the same timeline and expand it to hold more tracks. In the last section, we introduced a free-to-download asset from the Asset Store called Default Playables to save us from writing code from scratch and to offer new playables. This asset gave us the ability to add new tracks to our timeline. To continue adding new tracks, we will manipulate the lights in our third-level scene.
To add a light component to the timeline, we need to do the following:
We now have an empty light component track added to our timeline.
These properties will directly change the values of the light that sits in the None (Light) parameter.
Because this light is going to flash white and then red, ideally, it would make sense to have a loop between the two transitions. However, for the sake of blending and filling up the timeline, we are going to do it this way.
Once we've merged the clips, we can now duplicate our light track asset so that more than one light can flash.
As these are all the same types of tracks, we can put them into a track group to keep our timeline tidy.
You can use the + button to expand and collapse the group.
The following screenshot shows the final result of the timeline lights:
The final step is to drag and drop the five lights that will flash red and white into the Game window.
The following screenshot shows our player on the third level with a new set of AI enemies, a large boss flying in the background, and flashing lights:
Now would be a good time to apply the pause screen, if you haven't already done so, to all three scenes. Follow these instructions if you don't feel comfortable doing this on your own:
The following screenshot shows level3 paused:
Let's move on to summarizing what we have covered in this chapter.
In this chapter, we introduced a new concept to our game to make it more interesting than just taking place in space. Our camera and player needed to be slightly tweaked for the final level to support side-scrolling, instead of them being static in one screen, as is the case on the previous two levels. We also introduced a second enemy that can move around the floor, dodging other enemies in a panic-like state. The fleeing enemy used the navigation system that comes with Unity as standard, and we gave the enemy a floor to run around on. Lastly, we introduced the Timeline feature, which is related to the original animation system we used in Chapter 4, Applying Art, Animation, and Particles. We also discovered how Timeline lets any game object or component be animated in it without needing some form of hierarchical link between the game objects. Also, we extended the timeline to cover other components, such as lights, which we can't animate alone with the Timeline feature.
The main takeaways from this chapter are the introduction of AI with a navigation system that can also be used for other behaviors in other games and the introduction of the timeline and its use to encourage creativity in projects, such as cutscenes, films, and animated TV sequences.
In the next chapter, we will look at polishing the visuals of our game, and we will see what tools can help us in optimizing performance and testing for bugs.
Before you move on to the next chapter, try out the following mock test, as this is the last mini-mock test in this book before the big one.
You notice that when the game is paused and then un-paused, the audio and animation are out of sync with each other.
Which property in the Playable Director component will likely fix this issue?
Which PlayerGraph function should we use?
How can we improve the drop in the frame rate?
What setting do you need to increase to stop these arms and heads coming through objects that they shouldn't?
Which NavMesh agent property can simulate this cautious behavior?
Yet again, you have applied the NavMesh agent so that your vigilante can run and jump across buildings in a linear path. You have correctly hooked all your animation controls up but have noticed that your vigilante isn't animating when it comes to jumping between building rooftops.
What setting or property do we need to change to solve this issue?
What finite state machine component is the programmer working on?
When importing the models into the scene, you notice that all the models have sharp edges. You have asked the artist to make the models smoother.
Is there anything else that can be done on the developer side to possibly fix these sharp three-dimensional model edges?
How do we make the lighting act how it should in the scene?