Chapter 12: NavMesh, Timeline, and a Mock Test

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:

  • Preparing the final scene
  • Developing AI with NavMesh
  • Exploring the timeline
  • Extending the timeline

Let's start by reviewing what skills are covered in this chapter.

The core exam skills covered in this chapter

Programming core interaction:

  • Implementing behaviors and interactions of game objects and environments
  • Identifying methods to implement camera views and movement

Working in an art pipeline:

  • Knowledge of materials, textures, and shaders: The Unity rendering API
  • Knowledge of lighting: The Unity lighting API
  • Knowledge of two-dimensional and three-dimensional animation: The Unity animation API

Programming for the scene and environment design:

  • Determining scripts for pathfinding with the Unity navigation system

Technical requirements

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.

Preparing the final scene

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:

Figure 12.1 – Our third level assets

Figure 12.1 – Our third level assets

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:

  1. From the Project window, navigate to Assets/Scene and double-click on the level3 scene file.
  2. From the Project window, navigate to Assets/Prefab.
  3. Click and drag the level3Env prefab into the _SceneAssets game object in the Hierarchy window.
  4. Select level3Env from the Hierarchy window, and in the Inspector window, update its Transform property values with the following:
    • Position: X: 2500, Y: 17, and Z: -24
    • Scale: X: 0.55, Y: 0.55, and Z: 0.55

The following screenshot shows our scene setup, which is ready for our player to fly into:

Figure 12.2 – The level 3 assets in place

Figure 12.2 – The level 3 assets in place

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:

  1. Thankfully, the aforementioned two assets sit within the GameSpeed game object in the Hierarchy window. All that we need to do is delete the GameSpeed game object.
  2. We won't be using any directional lighting in our scene, so also remove Directional Light from the Hierarchy window.
  3. Also, for the sake of running our level3 scene without going through the entire game loop, we can drop our GameManager prefab from the Assets/Prefab folder location into the Hierarchy window.

Our Hierarchy window for level3 will now look like the one in the following screenshot:

Figure 12.3 – Our Hierarchy layout so far

Figure 12.3 – Our Hierarchy layout so far

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:

  1. Select the main camera in the Hierarchy window.
  2. In the Inspector window, change the Clipping Planes Far component property of Camera to 1300.

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:

  1. Select the main camera from the Hierarchy window.
  2. Click the Add Component button in the Inspector window and type CameraMovement until you see the script appear in the dropdown.
  3. Select the CameraMovement script.
  4. Click on Add Component again and type box collider into the drop-down list. When it appears, select it and check its Is Trigger box.
  5. In brief, this script will translate along its x axis after 6 seconds when active. When the script reaches a particular point, it will stop; it will also make sure that the player stops traveling with it.

Let's modify our Player script to act on the movement of the camera for level3:

  1. In the Project window of the Unity Editor, navigate to Assets/Script and double-click on the Player script to open it.
  2. Inside the Player script, at the top where we have our global variables, enter the private variable and its property:

    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.

  1. In the Start function, add the following line at the bottom of its function:

    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.

  1. At the top of the Movement method, enter the following code, which will multiply our player's ship's speed:

    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.

  1. Scroll down until you get to where the player can press the right arrow to move (Input.GetAxisRaw("Horizontal") > 0). Within the scope of the if statement, we can amend the second if statement to the following:

    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.

  1. We can then do the same for when the player presses the left arrow on the keyboard/joystick within the second if statement:

    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.

  1. Save the Player script.

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:

  1. From the Project window, navigate to Assets/Script.
  2. Double-click on the GameManager script to open it in your IDE.

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.

  1. In the GameManger script, scroll down to the method titled CameraSetup.
  2. We are going to make this method take a variable to alter the camera's speed. Change the CameraSetup method to take a float value in its parameter. The CameraSetup method will first look as follows:

    public void CameraSetup()

It will then change to this:

public void CameraSetup(float camSpeed)

  1. Within the CameraSetup method, we need to transfer camSpeed to the new CameraMovement script:

    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.

  1. Open the PlayerSpawner script.
  2. Scroll to the bottom and remove: GameManager.Instance.CameraSetup().
  3. Save the script and return to the GameManager script.

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.

  1. Within LightandCameraSetup, replace its original switch statement with the following:

        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.

  1. Press Play to see how the level3 scene plays out. The following screenshot shows what we have so far:
Figure 12.4 – Level 3 in Play mode

Figure 12.4 – Level 3 in Play mode

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:

  • The level3 scene before pressing Play (denoted by 1.).
  • The scene is in play mode and sets up the camera position and background (denoted by 2.).
  • The UI for the level is animated, and the enemies start floating into the game window (denoted by 3.).
  • The player enters the level, and the scene pauses for a few seconds before the player gets control of their ship (denoted by 4.).
  • The camera begins to move along with the radar camera, following the progress of the player and the oncoming enemies (denoted by 5.).

In the next section, we will add our second enemy type (flee enemy), which will flee across the span of our new art.

Developing AI with NavMesh

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:

Figure 12.5 – Fleeing enemies

Figure 12.5 – Fleeing enemies

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:

  1. From the Hierarchy window, select the _SceneAssets game object.
  2. From the Inspector window, at the top-right corner, we need to deactivate Navigation Static for _SceneAssets.
  3. The following screenshot shows _SceneAssets selected and the static dropdown (denoted by 1.), followed by Navigation Static being unticked (denoted by 2.):
Figure 12.6 – Unticking Navigation Static

Figure 12.6 – Unticking Navigation Static

  1. A window pops up asking whether we want to apply the changes to all the child objects. Select Yes, change children.

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:

  1. From the Hierarchy window, click on the arrows next to the following fields to expand out its content until you get to the game object titled corridorFloorNav:
    • _SceneAssets
    • level
    • corridorFloor

The following screenshot shows that from the Hierarchy window, we have selected corridorFloorNav (denoted by 1.):

  1. With corridorFloorNav selected, make sure its Mesh Renderer component is ticked in the Inspector window (denoted by 2.).
  2. Finally, select Navigation Static for this game object (denoted by 3.):
Figure 12.7 – Turning on the 'corridorFloorNav' game object, Mesh Renderer, and the 'Navigation Static' layer

Figure 12.7 – Turning on the 'corridorFloorNav' game object, Mesh Renderer, and the 'Navigation Static' layer

We now need to check the Navigation window so that we can set it to bake our CorridorFloorNav mesh.

  1. Select Window at the top of the Unity Editor and then click on AI | Navigation.

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:

Figure 12.8 – The Navigation window

Figure 12.8 – The Navigation window

  1. In the Navigation window, click on the Bake button at the top to give us our Navigation bake options.

    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:

  • Agent Radius: This will create an invisible shield around our agent so that they can't clip into other agents, walls, doors, and so on.
  • Agent Height: Similar to Agent Radius, this gives our agent an invisible height; this can be useful for the game object that the NavMesh is manipulating to pass through doors.
  • Max Slope: We can alter, in degrees, how much of a slope our agent can walk up.
  • Step Height: This is similar to the Max Slope property, but in this case, this controls how much our agent is allowed to move up a step/stair.
  • Drop Height: Enter a value for the maximum height the character can drop down from (associated with the Off Mesh Link component).
  • Jump Distance: This specifies the value for the jump distance between the character and the object (associated with the Off Mesh Link component).

    Further Information

    Information about the Off Mesh Link component can be found at https://docs.unity3d.com/Manual/class-OffMeshLink.html.

  • Manual Voxel: A voxel is a three-dimensional measurement that is used to scale the accuracy of our navigation bake.
  • Voxel Size: If the Manual Voxel option is ticked, this means we can give each agent tighter precision. The lower the number, the more accurate our agent will be; note that this will make the NavMesh take longer to bake.
  • Min Region Area: This specifies a minimum area that a surface must have for it to be included in the NavMesh.
  • Height Mesh: This checkbox will create a height mesh, which will improve the movement accuracy. This will also make navigation baking slower.

The following screenshot shows the navigation bake settings we just went through:

Figure 12.9 – The Navigation window with the 'Bake' tab selected

Figure 12.9 – The Navigation window with the 'Bake' tab selected

Thankfully, the Bake properties of our default setup window will work just fine as it is.

  1. Click on the Bake button at the bottom right of the Navigation window and wait until the meter at the bottom-right corner of the Editor completes, as shown in the following screenshot:
Figure 12.10 – Exporting Tiles for your NavMesh

Figure 12.10 – Exporting Tiles for your NavMesh

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:

Figure 12.11 – Our navigation mesh and its display at the bottom-right corner of the 'Scene' window

Figure 12.11 – Our navigation mesh and its display at the bottom-right corner of the 'Scene' window

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:

  1. Select the corridorFloorNav game object in the Hierarchy window.
  2. In the Inspector window, uncheck the Mesh Renderer component.

The following screenshot shows the highlighted box that needs unchecking:

Figure 12.12 – Unticking the 'corridorFloorNav' mesh

Figure 12.12 – Unticking the 'corridorFloorNav' mesh

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.

Customizing our agents – NavMeshAgent

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:

  1. From the Project window, navigate to the Assets/Model folder and drag enemy_flee.fbx to the bottom of the Hierarchy window.
  2. Drag enemy_flee from the Hierarchy window to the Project window in Assets/Prefab/Enemies. When the window pops up asking if you want a variant or original. Pick original. As there is only one type of this enemy.

That's our fleeing enemy created; now, we can apply a material to it by doing the following:

  1. Navigate to the Assets/Prefab/Enemies folder and select the enemy_flee prefab.
  2. From the Inspector window, select the remote button of the Mesh Renderer component (denoted by 1. in the following screenshot).
  3. A dropdown will appear. Type darkRed in the search bar at the top if you can't see the material on the list.
  4. Double-click on darkRed from the dropdown (denoted by 2. in the following).
  5. At this point, make sure that the Transform component's Position and Rotation properties all have a 0 value and that the Scale property value is set as 1.

The following screenshot shows the enemy_flee prefab with its update material and the correct Transform values:

Figure 12.13 – Adding the 'darkRed' material to the 'enemy_flee' Mesh Renderer Materials slot

Figure 12.13 – Adding the 'darkRed' material to the 'enemy_flee' Mesh Renderer Materials slot

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:

  1. From the Project window, navigate to the Assets/Model folder and select enemy_flee.
  2. In the Inspector window, change the property value for Normals from Import to Calculate.

We can now adjust the Smoothing Angle value with the slider to change the smoothness between angles, as shown in the following screenshot:

Figure 12.14 – Smoothing the edges of our enemy

Figure 12.14 – Smoothing the edges of our enemy

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.

  1. Once you're happy with the Smoothing Angle value, click on Apply at the bottom-right corner of the Inspector window.

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:

  1. Click on the Tag parameter at the top of the Inspector window.
  2. Select Enemy.

The following screenshot shows the Enemy tag selected for enemy_flee:

Figure 12.15 – Our enemy being given an 'Enemy' tag

Figure 12.15 – Our enemy being given an 'Enemy' tag

We are now ready to apply NavMeshAgent to the enemy_flee game object. With enemy_flee still selected, do the following:

  1. Click on the Add Component button in the Inspector window.
  2. A drop-down list will appear. Type in nav and select NavMeshAgent from the list.

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):

Figure 12.16 – The 'Nav Mesh Agent' component (its own default values – ignore them)

Figure 12.16 – The 'Nav Mesh Agent' component (its own default values – ignore them)

  • Agent Type: By default, there is only one agent type. This holds a preset of the name of the agent, the radius, height, step height, and max slope. To find out more about these values, check the previous section.
  • Base Offset: This will change the placement of the agent mesh, which wraps around the fleeing enemy in the form of a cylinder that can only be seen in the Scene window.
  • Speed: The maximum speed value, based on world units per second.
  • Angular Speed: Sets how quickly the agent can rotate in degrees per second.
  • Acceleration: Maximum acceleration based on world units per second squared.
  • Stopping Distance: Agents will stop when they are at a particular measurement.
  • Auto Braking: The agent will slow down gradually before reaching a complete stop.
  • Radius: The agent's spatial area will increase the scale of the agent cylinder.
  • Height: This will increase the height of the agent's cylinder.
  • Quality: The ranges of the accuracy of obstacle avoidance.
  • Priority: Agents of a lower priority will be ignored by this agent when performing avoidance.
  • Auto Traverse Off Mesh Link: If you want the agent to move between gaps, keep this checked; otherwise, custom animation will move the agent across the gap.
  • Auto Repath: If the agent is no longer on the path they are walking, with this option checked, they will try and make their way back to the nearest point.
  • Area Mask: With navigation baking, we can set which area this agent belongs to.

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.

  1. Change the NavMeshAgent values for the enemy_flee prefab to the ones shown in the following screenshot. However, it's recommended to change and alter the values to make the enemies behave differently. Theres no wrong approach. But as a guide, match the ones in the following screenshot:

Figure 12.17 – 'Nav Mesh Agent' with set values

  1. Click Overrides | Apply All at the top-right corner of the Inspector window to confirm your prefab changes.

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:

Figure 12.18 – Our enemy with its NavMeshAgent collider wrapped around it

Figure 12.18 – Our enemy with its NavMeshAgent collider wrapped around it

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.

Adding a capsule collider to our fleeing enemy

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:

  1. With the enemy_flee prefab still selected, scroll down to the bottom of the Inspector window and click on Add Component.
  2. Start typing Capsule into the drop-down window until you see Capsule Collider.
  3. Select Capsule Collider from the drop-down list. The fleeing enemy will now have a capsule collider wrapped around them.
  4. Finally, tick the Is Trigger checkbox in the Capsule Collider component.
  5. Click on the Overrides | Apply All button at the top-right corner of the Inspector window to save the enemy_flee prefab's settings.
  6. Select the enemy_flee game object in Hierarchy and delete it.

The following screenshot shows enemy_flee with its capsule collider; these values may differ to yours:

Figure 12.19 – 'enemy_flee' with its capsule collider

Figure 12.19 – 'enemy_flee' with its capsule collider

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.

Creating our fleeing enemy script

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:

  1. From the Project window, navigate to Assets/Script.
  2. Double-click on the EnemyFlee script to begin adding its navigation code.

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:

  1. At the top of the script, add the AI library to give our script access to the Navigation Agent files:

    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.

  1. Scroll down in the script to where our global variables are (health, travelSpeed, fireRate, and so on) and add the following variables that we will be using with our navigation setup:

    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.

  1. Create a Start function that will require a short delay to get a reference from the player ship. Enter the following code. We will go through each step to see why there is a delay and the standard ActorStats method:

        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.

  1. Moving on to the last piece of our code, the Update function will be measuring and reacting to and from the distance of our fleeing enemy and the player. Enter the following Update function and its content, and we will go through each step:

        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.

  • Store the Vector3 variable, which minuses the player's position from our own.
  • We then add this Vector3 variable to the fleeing enemy's Transform position as a newPos position of Vector3, which will be the direction for the enemy flee to run in.
  • Finally, we send this newPos position to NavMeshAgent.
  • Save the EnemyFlee script.

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:

  1. Back in the Unity Editor navigate to the Assets/Prefab/Enemies folder in the Project window.
  2. Select the enemy_flee prefab.
  3. Click on the Add Component button in the Inspector window and type in EnemyFlee.
  4. Select the Enemy Flee script from the drop-down list.
  5. Create a new Actor (refer back to Chapter 2, Adding and Manipulating Objects). Name the Actor BasicFlee_Enemy, and then store it in Assets/ScriptableObject. Drag the Actor into the Actor Model area of the EnemyFlee script in the Inspector window, as shown in the following screenshot.

The following screenshot shows the scriptable object asset for the EnemyFlee script's Actor Model parameter on the right:

Figure 12.20 – The 'enemy_flee' game object holding its 'EnemyFlee' script and updated fields

Figure 12.20 – The 'enemy_flee' game object holding its 'EnemyFlee' script and updated fields

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:

  1. Back in the Project window, drag and drop the enemy_flee prefab from Assets/Prefab/Enemies into the Hierarchy window.
  2. Drag and drop the radarPoint object from Assets/Prefab/Enemies onto the enemy_flee prefab in the Hierarchy window.
  3. Then, drag and drop the RadarRotation script from Assets/Script into the Inspector window. This will make the enemy_flee radarPoint object point toward the camera.
  4. Once applied, select the enemy_flee prefab from the Hierarchy window, and then click on Overrides | Apply All at the top-right corner of the Inspector window.
  5. The following screenshot shows the enemy_flee prefab holding the radarPoint object, along with the radarPoint object in the Inspector window, as a reference to help avoid any errors:
Figure 12.21 – The 'radarPoint' game object and its Transform property values

Figure 12.21 – The 'radarPoint' game object and its Transform property values

  1. Our enemy_flee prefab is now ready to be trialed out in Play mode. Drag and drop enemy_flee from its current location to the _Enemies game object in the Hierarchy window. The following screenshot shows where enemy_flee now is in the Hierarchy window:
Figure 12.22 – The 'Enemies' game object children in the Hierarchy window

Figure 12.22 – The 'Enemies' game object children in the Hierarchy window

  1. Set the enemy_flee prefab to somewhere near the start of the level. I have placed mine at the following Transform values:
Figure 12.23 – The 'enemy_flee' game object placement in level 3

Figure 12.23 – The 'enemy_flee' game object placement in level 3

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.

  1. Click the Play button in the Unity Editor and your enemy_flee object should now start panicking and moving around to try and escape from you!

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:

Figure 12.24 – Duplicated 'enemy_flee' game objects

Figure 12.24 – Duplicated 'enemy_flee' game objects

The following screenshot shows our new fleeing enemies trying to escape from the player in pure panic!

Figure 12.25 – Enemies fleeing from the player!

Figure 12.25 – Enemies fleeing from the player!

  1. Save the scene.

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.

Exploring the timeline

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:

Figure 12.26 – An example of how chaotic Animator Controllers can get

Figure 12.26 – An example of how chaotic Animator Controllers can get

Timeline supports three tasks:

  • Playing animations and clips
  • Playing audio
  • Turning game objects on or off

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.

Creating a timeline

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:

  1. In the Unity Editor's Project window, navigate to Assets/Scene.
  2. Double-click on level3 to load the scene if it isn't loaded already.
  3. Right-click in the Hierarchy window and select Create Empty from the dropdown to create an empty game object.
  4. Click on GameObject twice slowly to rename it.
  5. Rename GameObject Timeline.

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:

Figure 12.27 – Selecting the 'Timeline' game object and opening a Timeline window

Figure 12.27 – Selecting the 'Timeline' game object and opening a Timeline window

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:

Figure 12.28 – Window placements in the Unity Editor

Figure 12.28 – Window placements in the Unity Editor

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:

  1. With the Timeline game object still selected in the Hierarchy window, click on the Create button in the Timeline window.
  2. A window browser will appear to let us select where we want to save our playable file.
  3. Choose the Assets folder.
  4. Give the playable file a name (something relevant to what it's going to be used for; I'm naming mine level3) and click on the Save button.

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:

Figure 12.29 – The Timeline window

Figure 12.29 – The Timeline window

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:

Figure 12.30 – The 'Timeline' game object holding the Playable Director and Animator components in the Inspector window

Figure 12.30 – The 'Timeline' game object holding the Playable Director and Animator components in the Inspector window

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:

  • Digital Signal Processing (DSP) helps to improve accuracy between our timeline and its audio to prevent it from going out of sync.
  • Game Time: The time for the timeline will be sourced from the game's time. This also means the time can be scaled (that is, slowed down or paused).
  • Unscaled Game Time: This option works the same as the Game Time property, but it is not affected by scaling.
  • Manual: This property uses the clock time we give it through scripting.

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:

  • Hold: When the timeline reaches the end, it holds on to the last frame.
  • Loop: The timeline repeats.
  • None: The timeline plays and then resets.

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.

Setting up the boss game object in our scene

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:

  1. Drag and drop the boss.prefab object from Assets/Prefab/Enemies into the _Enemies game object in the Hierarchy window.

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.

  1. Select the boss game object in the Hierarchy window and make sure that in the Inspector window, its Transform properties are set to the following:
    • Position: X: 0, Y: 0, and Z: -2000
    • Rotation: X: 0, Y: 0, and Z: 0
    • Scale: X: 1, Y: 1, and Z: 1
  2. With the boss object still selected in the Hierarchy window, press F on your keyboard to see what it looks like in the Scene window.

The following screenshot shows the imported boss prefab, which contains a list of components and property values:

Figure 12.31 – The 'boss' game object tagged as 'Enemy', positioned with the Transform component, and finally, set as a trigger with a scaled radius in Sphere Collider

Figure 12.31 – The 'boss' game object tagged as 'Enemy', positioned with the Transform component, and finally, set as a trigger with a scaled radius in Sphere Collider

The boss object holds the following component and property values, as shown in the previous screenshot:

  • Tagged as Enemy (denoted by 1.).
  • The Transform property values are set to the ones detailed under step 2 (denoted by 2.).
  • Sphere Collider is set as a trigger with a Radius value of 80 (denoted by 3.).
  • BossScript makes the boss game object invincible to the player, and if the player makes contact with the boss, the player will die (denoted by 4.).
  • Because the boss object is an enemy, it has a radarPoint object that is picked up on the radar (denoted by 5.).

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:

  1. Expand the boss content in the Hierarchy window.
  2. Select radarPoint, and then drag and drop the RadarRotation script from Assets/Script, moving it from the Project window to the Inspector window.
  3. Finally, select the boss game object in the Hierarchy window. Then drag and drop the BossScript from Assets/Script into the Inspector window.

Now that the boss object is in the scene, we can add it to the timeline in the next section.

Preparing the boss for the timeline

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:

  1. Select the Timeline game object, and in the Inspector window, untick Play On Awake, as we will be triggering the Timeline animation ourselves.

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:

  1. Select the main camera in the Hierarchy window.
  2. Click on the Add Component button in the Inspector window.
  3. Type in Box Collider, and when you see it in the drop-down list, select it.
  4. Tick the Is Trigger box under the Box Collider component.

Let's now continue setting up our Timeline window so that we can drag our boss game object into it:

  1. Select the Timeline tab, which, as you will know if you have been following along with the previous sections, is found at the bottom of the Unity Editor

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.

  1. Right-click on the Timeline object in the Timeline window and select Delete from the dropdown.

The following screenshot shows the Timeline game object being deleted:

Figure 12.32 – Deleting the 'Timeline' game object from the Timeline window

Figure 12.32 – Deleting the 'Timeline' game object from the Timeline window

  1. With the Timeline game object still selected in the Hierarchy window, click and drag the boss game object from the Hierarchy window down into the Timeline window.

A dropdown will appear with a choice of three selections:

  • Activation Track: Turns a game object on or off
  • Animation Track: Animates the game object
  • Audio Track: Sets particular audio on or off
  1. Because we want to animate our boss game object, we will choose Animation Track.

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:

Figure 12.33 – Timeline holding the 'boss' game object

Figure 12.33 – Timeline holding the 'boss' game object

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:

  1. Select the Timeline game object in the Hierarchy window.
  2. Select the Timeline window tab.
  3. Click on the padlock button at the top-right corner of the Timeline window.

The padlock button is highlighted in the following screenshot:

Figure 12.34 – Locking the Timeline window

Figure 12.34 – Locking the Timeline window

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.

Animating the boss in the timeline – 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:

  1. With the Timeline window still locked, select the boss game object.

We will now start recording our boss's position and rotation.

  1. In the Timeline window, click on the record button next to the boss game object name; the button should begin to flash.
  2. Make sure that the Timeline Frame is set to 0, as shown in the following screenshot:
Figure 12.35 – The Timeline frame set to zero

Figure 12.35 – The Timeline frame set to zero

  1. In the Inspector window, change the boss's Transform property values to the following:
    • Position: X: 1675, Y: 0, and Z: 600
    • Rotation: X: 60, Y: -90, and Z: 0
Figure 12.36 – The 'boss' game object's current location in our level 3 scene

Figure 12.36 – The 'boss' game object's current location in our level 3 scene

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:

  1. With the record button still flashing in the Timeline window, drag the timeline to frame 112 or change the value of the Frame parameter to 112.
  2. Select the boss game object in the Hierarchy window, and in the Inspector window, change the Transform property values to the following:
    • Position: X: 3160, Y: 0, and Z: 600
    • Rotation: X: 60, Y: -90, and Z: 20

      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.

  3. Click on the record button next to the boss game object in the Timeline window to stop recording.
  4. Click and scrub (scrub is an animator term for dragging) back and forth on the timeline's white arrow to see the boss game object move from left to right while rotating.

The following screenshot shows a bird's-eye view of the boss game object moving from left to right:

Figure 12.37 – The 'boss' game object will move from left to right

Figure 12.37 – The 'boss' game object will move 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.

Animating the boss in the timeline – phase two

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:

  1. Keep the Timeline window padlocked to stop the window from losing its display.
  2. Select the boss object from the Hierarchy window.
  3. Press the record button next to the boss object's name in the Timeline window so that the button flashes.
  4. Enter 1012 into the Frame parameter.

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:

  • Position: X: 4545, Y: 0, and Z: 600
  • Rotation: X 60, Y: -90, and Z: 0

The following screenshot shows where our boss sits in phase two:

Figure 12.38 – The 'boss' game object positions

Figure 12.38 – The 'boss' game object positions

  1. With the record button still flashing, move to frame 1180 in the Timeline window and set the boss game object to the following Position and Rotation values in the Inspector window:
    • Position: X: 6390, Y: 0, and Z: 600
    • Rotation: X 60, Y: -90, and Z: 20
  2. Now, move to frame 1193 in the Timeline window and set the boss game object to the following Position and Rotation values in the Inspector window:
    • Position: X: 6390, Y: 0, and Z: 207
    • Rotation: X: 60, Y: 450, and Z: 0
  3. Now, move to frame 1215 in the Timeline window and set the boss game object to the following Position and Rotation values in the Inspector window:
    • Position: X: 5520, Y: 0, and Z: 50
    • Rotation: X: 60, Y: 90, and Z: -40
  4. Now, move to frame 1380 in the Timeline window and set the boss game object to the following Position and Rotation values in the Inspector window:
    • Position: X: 5510, Y: 0, and Z: 50
    • Rotation: X: 60, Y: 90, and Z: 0
  5. Now, move to frame 1400 in the Timeline window and set the boss game object to the following Position and Rotation values in the Inspector window:
    • Position: X: 5510, Y: 0, and Z: 50
    • Rotation: X: 60, Y: -70, and Z: -40
  6. Now, move to frame 1420 in the Timeline window and set the boss game object to the following Position and Rotation values in the Inspector window:
    • Position: X: 7540, Y: 0, and Z: 50
    • Rotation: X: 60, Y: -70, and Z: 0
  7. Press the record button next to the boss game object in the Timeline window to stop recording.

The following screenshot shows a bird's-eye view of each of these positions with their Timeline frame numbers:

Figure 12.39 – The Timeline frame numbers of where the 'boss' game object will be

Figure 12.39 – The Timeline frame numbers of where the 'boss' game object will be

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).

Figure 12.40 – Rotating the 'boss' game object

Figure 12.40 – Rotating the 'boss' game object

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:

Figure 12.41 – The 'boss' game object's radar is still detecting the boss

Figure 12.41 – The 'boss' game object's radar is still detecting the boss

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:

  1. Select the Timeline game object from the Hierarchy window.
  2. Select the Timeline tab to see the Timeline content along with our boss animation.
  3. Click and drag the boss's radarPoint object from the Hierarchy window into the Timeline window.
  4. The Timeline dropdown appears. This time, select Activation Track.
  5. Our Timeline window now has the radarPoint track.

Let's now add an activation clip to decide when the player should and shouldn't see the radarPoint object. Follow these steps:

  1. Right-click on the track of the radarPoint object and select Add Activation Clip, as shown in the following screenshot:

Figure 12.42 – In the Timeline window, add an activation clip for our 'radarPoint' game object

Figure 12.42 – In the Timeline window, add an activation clip for our 'radarPoint' game object

  1. An Active clip will appear. This Active clip allows us to choose how long in the Timeline track we want this game object to be active. We want the boss game object to be active on two occasions – once when the boss moves past the player in the open-space area of the environment, and once at the end when the boss approaches the player head-on.
  2. For the first occasion, we need to set the Active clip between 35 and 95 on the timeline. We can do this by clicking and dragging its bar down to the 95 mark, as shown in the following screenshot:
Figure 12.43 – Setting the activity of the 'radarPoint' game object between set frames

Figure 12.43 – Setting the activity of the 'radarPoint' game object between set frames

For the second occasion, we can set the boss object from around 1020 to 1420.

Repeat this process by doing the following:

  1. Right-click and create an activation clip on the radarPoint track.
  2. Scale the Active bar between the two Timeline points.
  3. Save the scene.

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.

Extending the timeline

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:

Figure 12.44 – Expanding the functionality of our Timeline will be done next

Figure 12.44 – Expanding the functionality of our Timeline will be done next

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.

Adding Default Playables to the project

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:

  1. Open your web browser and go to the Unity Asset Store: https://assetstore.unity.com/.
  2. At the top of the Asset Store window, in the search bar, type default playables and press Enter on your keyboard.
  3. Select the only selection from the thumbnail list – Default Playables.
  4. On the Default Playables shop screen, click on the Download button.
  5. Once downloaded, we can now import the asset into our project by clicking on the Add to My Assets button, as shown in the following screenshot:
Figure 12.45 – Downloading and importing the free Default Playables package into our project from the Asset Store

Figure 12.45 – Downloading and importing the free Default Playables package into our project from the Asset Store

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:

  1. In the Unity Editor, click Window, followed by Package Manager.
  2. Package Manager will load in a new window. At the top-left corner of the window, select Packages:, followed by My Assets from the dropdown, as shown in the following screenshot:
Figure 12.46 – Selecting "My Assets" from the dropdown in the Package Manager window

Figure 12.46 – Selecting "My Assets" from the dropdown in the Package Manager window

We will now be presented with the assets we own from the Asset Store.

  1. At the top-right corner of Package Manager, we have a search bar where we can type default playable to shorten the list on the left side, as shown in the following screenshot:
Figure 12.47 – The Package Manager search bar highlighted at the top-right corner

Figure 12.47 – The Package Manager search bar highlighted at the top-right corner

  1. Select Default Playables at the left side of the window, followed by clicking Download, and then the Import button (at the bottom-right corner of the window).

We will then be presented with a list of folders and files that will be imported into the project.

  1. Click the Import button, as shown in the following screenshot:
Figure 12.48 – The Default Playables files to be imported into the project

Figure 12.48 – The Default Playables files to 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.

Manipulating light components in the timeline

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:

  1. Make sure the Timeline window is still locked, which we set in the Preparing the boss for the timeline section.
  2. Right-click at the bottom-left open-space corner of the Timeline window and select Light Control Track, as shown in the following screenshot:
Figure 12.49 – Adding 'Light Control Track' to the Timeline

Figure 12.49 – Adding 'Light Control Track' to the Timeline

We now have an empty light component track added to our timeline.

  1. Next, we can add an Animation clip by right-clicking on the timeline's track line and selecting Add Light Control Clip, as shown in the following screenshot:
Figure 12.50 – Adding control to our lights with the control clip

Figure 12.50 – Adding control to our lights with the control clip

  1. We now have a LightControlClip object in our timeline. Click on this clip and look at the Inspector window. There are a few options here, but we are going to focus mainly on Color, Intensity, and Range.

These properties will directly change the values of the light that sits in the None (Light) parameter.

  1. Set your Light Control Clip values to the ones shown in the following screenshot in your Inspector window:
Figure 12.51 – Copying the values from your 'Light Control Clip' to the ones in this screenshot

Figure 12.51 – Copying the values from your 'Light Control Clip' to the ones in this screenshot

  1. Next, we will set the duration of this clip to 100. We can do this by either changing the value of the Duration parameter in the Inspector window or by clicking on and dragging LightControlClip to the 100 mark, as shown in the following screenshot:
Figure 12.52 – Setting the duration of the light control clip to 100

Figure 12.52 – Setting the duration of the light control clip to 100

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.

  1. Select LightControlClip and press Ctrl (command on the macOS) + D 25 times to spam the track line with light control clips.
  2. Select the second LightControlClip object from the left and change its Color property from white to red.
  3. Repeat this process for clips 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, and 26.
  4. Now, zoom into the second LightControlClip object and move it 50% of the way across to its previous clip to create a blend between the color of the light, as in the following screenshot:
Figure 12.53 – 1st light clip splicing into the 2nd light clip

Figure 12.53 – 1st light clip splicing into the 2nd light clip

  1. Continue moving each clip 50% of the way across to the previous clip's location to make the lights flash white to red until the level finishes. The following screenshot shows the position of the third clip:
Figure 12.54 – The 2nd light clip splicing into the 3rd

Figure 12.54 – The 2nd light clip splicing into the 3rd

Once we've merged the clips, we can now duplicate our light track asset so that more than one light can flash.

  1. Click on the track asset and press Ctrl (Command on macOS) + D four times. The following screenshot uses the * symbol to highlight where to click, along with the duplicates made:
Figure 12.55 – Duplicating the track asset

Figure 12.55 – Duplicating the track asset

As these are all the same types of tracks, we can put them into a track group to keep our timeline tidy.

  1. Right-click in the open-space area at the bottom left of the timeline and select Track Group from the dropdown.
  2. Our track group is made. Now, click and hold the top light track asset while holding down Shift, and click on the bottom light track asset to select all of the light track assets. Still holding down the left mouse button, drag these track assets into the track group.
  3. Click on the Track Group name and rename it as something relevant, such as Lights.

You can use the + button to expand and collapse the group.

The following screenshot shows the final result of the timeline lights:

Figure 12.56 – The track group called 'Lights' holding all our light track assets

Figure 12.56 – The track group called 'Lights' holding all our light track assets

  1. Now that you know how to make a track group, follow the same process for the boss object and its radarPoint object, and call the track group Boss.

The final step is to drag and drop the five lights that will flash red and white into the Game window.

  1. Either click on the small, round remote button next to None (light) or drag and drop light00, light01, light02, light03, and light04 into each parameter.
  2. Scrub or drag the Timeline indicator backward and forward on the timeline to see the selected lights flashing red.
  3. Save the scene.

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:

Figure 12.57 – The midway of level 3

Figure 12.57 – The midway of level 3

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:

  1. With the level3 scene saved, load up level1 from the Assets/Scene folder from the Project window.
  2. In the Hierarchy window, hold down Ctrl (Command on macOS) and select the Canvas and EventSystem game objects, and then press C to copy them.
  3. Load the level3 scene back up.
  4. Select the Canvas game object from the Hierarchy window and press Delete on your keyboard.
  5. Press Ctrl (Command on macOS) + V to paste EventSystem and Canvas from the level that contains the pause screen.
  6. Expand Canvas and then the LevelTitle game object in the Hierarchy window.
  7. Select the Level game object, and change the Text property value from level 1 to level 3 in the Inspector window.
  8. Save the scene.
  9. Repeat this process for level2.

The following screenshot shows level3 paused:

Figure 12.58 – The pause screen working for level 3

Figure 12.58 – The pause screen working for level 3

Let's move on to summarizing what we have covered in this chapter.

Summary

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.

Mock test

  1. You are developing a game where your player is inside an office with other staff workers around them. When your player walks to a particular point, a trigger event is called to move the staff into another room with the use of Playable Director.

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?

  1. Set the wrap mode to Hold.
  2. Set the update method to DSP.
  3. Set Initial Time to the current time (the time when the game is paused).
  4. Set the update method to Unscaled Game Time.
  1. We have a set of playables linked within our playable graph. We need to remove one of these playables and its inputs.

Which PlayerGraph function should we use?

  1. DestroyOutput
  2. DestroyPlayable
  3. DestroySubgraph
  4. Destroy
  1. You have developed an eight-ball pool game. One of the testers has come back to you, saying that the frame rate of the game drops too low when one of the players breaks the balls up at the start of a game. All of the balls have rigid body sphere colliders.

How can we improve the drop in the frame rate?

  1. Use a less expensive shader on the objects that are colliding with one another.
  2. Set the maximum allowed timestep to a range of 8–10 fps to account for this worst-case scenario.
  3. Change the rigid bodies so that they are kinematic.
  4. Use box and capsule colliders instead of sphere colliders.
  1. What does the NavMesh modifier do?
    1. A NavMesh modifier determines what stage of the build process the NavMesh is baked at.
    2. A NavMesh modifier describes the AI of each agent in the scene.
    3. A NavMesh modifier allows NavMesh baking to occur outside the main thread so that it can be dynamically baked at runtime.
    4. A NavMesh modifier adjusts how a game object behaves during NavMesh baking and can, for example, only affect certain agents.
  2. Why does it help to only have the necessary boxes checked in the layer collision matrix?
    1. Unchecking boxes will hide layers so that they're not rendered.
    2. Checking boxes will indicate which collisions can be ignored.
    3. Checking boxes will show which layers are colliding in the frame debugger.
    4. Unchecking boxes will reduce the number of layer collisions the physics system needs to check.
  3. When we create a Timeline asset for a game object, what component is created and added to our game object?
    1. PlayableBinding
    2. PlayableDirector
    3. PlayableOutput
    4. PlayableGraph
  4. In your first-person shooter, which you are testing, you notice that when the alarm is sounded, the enemy guards come running toward the player. When you observe the enemy guards advancing, you close the door on them but notice that their arms and heads are coming through the door that you have closed.

What setting do you need to increase to stop these arms and heads coming through objects that they shouldn't?

  1. Step Height
  2. Max Slope
  3. Agent Height
  4. Agent Radius
  1. You have got yourself involved in a classic Save the Mayor rescue game. Your player is a trained vigilante trying to eliminate potential attackers to harm the city's mayor. One of the attackers gets too close, and you take a shot to warn them off. The attacker runs away but returns shortly after, crawling toward the mayor.

Which NavMesh agent property can simulate this cautious behavior?

  1. Area Mask
  2. Auto Braking
  3. Stopping Distance
  4. Priority
  1. Congratulations – Save the Mayor was a massive success, and you have been asked to start development straight away on Save the Mayor 2! Your vigilante is back, and this time, he can jump and run and jump across building rooftops.

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?

  1. Uncheck Auto Traverse Off Mesh Link under the NavMesh agent component.
  2. Increase the Obstacle Avoidance Priority value in the NavMesh agent component.
  3. Uncheck the Height Mesh property in the Bake settings under Navigation.
  4. Increase the jump distance in the Bake settings under Navigation.
  1. We are working on a third-person game, and our character is using a finite state machine to react to their states. We currently have it set so that if we get too close to a particular character, they will attempt to run and hug us.

What finite state machine component is the programmer working on?

  1. Actions
  2. Transitions
  3. Events
  4. Rules
  1. Which of these tracks can the timeline not add without applying additional coding?
    1. Activation Track
    2. Animation Track
    3. Light Control Track
    4. Playable Track
  2. One of the 3D artists has supplied you with a series of three-dimensional models to be applied to one of the start up scenes for the project you are currently developing.

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?

  1. Calculate the normals to a particular smoothing angle value.
  2. Import the files through Unity instead of dragging and dropping the files.
  3. Apply materials to each three-dimensional model.
  4. Make sure there is lighting in the scene.
  1. What does LateUpdate do?
    1. Replaces the standard Update function when frames are overloaded.
    2. LateUpdate takes fewer resources to run, which makes it ideal for mobile platforms.
    3. An update is only called once on every frame. LateUpdate is called every three frames.
    4. LateUpdate is the last item in the execution order before rendering.
  2. What are the advantages of using GameObject.Find (if any)?
    1. There aren't any; it's slow and demanding.
    2. If not called on every frame, it makes coding useful for referencing.
    3. GameObject.Find is deprecated.
    4. GameObject.Find searches through library asset data outside of the Unity project.
  3. Do we have to import the UnityEngine library and MonoBehaviour with every script?
    1. No, as long as they are not applied to a game object.
    2. Yes, or the Unity engine rejects the script.
    3. Yes, as they act as a header to all scripts.
    4. Only MonoBehaviour must be inherited in all cases.
  4. When moving from one scene to another, you notice that the second scene is much darker, even though it uses the same art and lighting as the scene before it.

How do we make the lighting act how it should in the scene?

  1. Make sure all the lights are turned off before being turned on in the second scene.
  2. Keep all the lights on from the first scene when moving into the second scene.
  3. Duplicate the lights from the previous scene over to the new one when loaded up.
  4. Turn off Auto Generate in the lighting settings and manually generate the lights.
  1. What are the benefits of Debug.Log()?
    1. It is useful if developers want to know the value of a variable.
    2. It sends string values to each variable.
    3. There aren't any; it's deprecated, so we don't use it.
    4. It logs information into Unity's database.
  2. Is the Audio Mixer useful to developers?
    1. No, it's specifically built for audio users; developers use an AudioSource.
    2. Yes, as it can be used to hold all the sounds in one central point.
    3. Only if the developer is skilled in handling audio alone.
    4. Yes, it helps the performance of the audio.
  3. Why do some developers prefer JSON over PlayerPrefs?
    1. PlayerPrefs was released before JSON, which gives it a bigger following.
    2. JSON can be used with more data types and is a more compatible API.
    3. Both are good; it's just a matter of personal preference.
    4. JSON is owned by Unity, so it incorporates a lot of features.
  4. Why would we use a trigger box instead of a collider?
    1. Triggers and colliders carry out the same task.
    2. Triggers have more functionality and cost less to run than colliders.
    3. A trigger can call code when another collider/trigger enters it.
    4. Triggers have different colored boxes.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset