Chapter 7: Creating a Game Loop and Mock Test

In the previous chapter, we moved from the testLevel scene (where we controlled the player ship) to a shop scene (buying and calibrating the player's ship). In this chapter, we will be following a similar trend of stretching out to the rest of the other game scenes in our Scene folder (found in the Project window in the Assets folder).

As part of scene management, all games we play have something called a "Game Loop" – if you're not familiar with the term, it basically means our game will have alternative routes to take. Each route will load a particular scene. We will need to cater for either outcome at each stage of the game.

Eventually, all game loops will loop back to somewhere near the beginning. The following image shows what our game loop will look like by the end of this chapter:

Figure 7.1 – Killer Wave's Game Loop

Figure 7.1 – Killer Wave's Game Loop

Referring to the game loop image still, each name in its rectangular box represents a scene in Unity that we have in our Scene folder inside the Project window. The flow of each scene goes in one overall direction, starting at the top with BOOTUP. This is when the game is launched for the first time. The flow will fall through each scene until the player beats all three levels or dies. Either way, they will eventually reach the GAMEOVER scene, which will then loop back up to the TITLE scene to create a loop.

By the end of this chapter, we will be in the position to run our game from the bootUp scene, where it will automatically move onto the title scene. From there, the player will press the fire button or tap the screen to then load up the shop scene, which is where purchases can be made.

Once the player presses the START button in the shop scene, the first level (level1) will start. If the player dies, the level will restart, whereas if the player completes the level, they will move onto the next level.

The final outcome from all of this will be that if the player dies three times, they will be taken back to the title scene, whereas if the player completes the level3 scene, then the game will be over, and the player will be taken to the gameOver scene.

Finally, we will cover a few mock test questions related to what we have covered so far.

In this chapter, we will cover the following topics:

  • Transitioning our player ship
  • Expanding our ScenesManager script
  • Preparing to loop our game
  • Mock test

Core exam skills covered in this chapter

The following are the core exam skills that will be covered in this chapter:

Programming core interactions:

  • Implement and configure game object behavior and physics
  • Implement and configure camera views and movement

Working in the art pipeline:

  • Understand materials, textures, and shaders, and write scripts that interact with Unity's rendering API
  • Understand 2D and 3D animation, and write scripts that interact with Unity's animation API

Programming for scene and environment design:

  • Identify methods for implementing Game Object instantiation, destruction, and management
  • Recognize techniques for structuring scripts for modularity, readability, and reusability

Technical requirements

The project content for this chapter can be found at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition/tree/main/Chapter_07.

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 this chapter's unitypackage file, including a Complete folder that holds all of the work we'll carry out in this chapter.

Check out the following video to see the Code in Action: https://bit.ly/37Rie3B.

Transitioning our player ship

Currently, our levels can only be completed when the player dies, causing the level to restart, or when the player loses all three lives. Only then are we taken to the game over screen. We now need to start thinking of how a player starts and ends a level. Currently, the player just appears at the start of a level.

In this section, we are going to write some code that animates our player into the scene and, when the level completes, we will have the player ship exit the camera view.

So, let's make a script the same way we did all the other scripts (Chapter 2, Adding and Manipulating Objects, if you need a reference). Name the script PlayerTransition and make sure we have the file in our Script folder in the Unity Editor Project window.

We now need to attach the PlayerTransition script to our player_ship prefab:

  1. Load up the testLevel scene from Assets/Scene in the Project window.
  2. Then, navigate to the Assets/Prefab/Player folder, and select the player_ship prefab.
  3. Finally, drag and drop the PlayerTransition script into an empty area of the player_ship Inspector window. Then make sure the PlayerTransition component in the Inspector window is turned off by unticking its box. If this box is left ticked, the PlayerTransition component will start animating player_ship in the shop scene.

Now that our script has been created and attached, we can go into it and start setting up the following code:

  • Adding variables to our PlayerTransition script
  • Adding methods/functions to our PlayerTransition script
  • Adding if statement checks
  • Adding content to PlayerMovement IEnumerator
  • Moving the player ship out of the screen

Let's take a look.

Adding variables to our PlayerTransition script

In this section, we are going to make a start by setting up our PlayerTransition script. We'll do this by adding global variables so that these can be used to position the player ship.

To start adding our global variables, follow these steps:

  1. Open our newly created PlayerTransition script.
  2. At the top of the script, make sure we have the following libraries added:

    using UnityEngine;using System.Collections;

By default, our script should be automatically named along with its default inherit MonoBehaviour as it's a requirement regarding the Unity Editor and other functionalities. The System.Collections library will be used for our StartCoroutine. Without this library, we can't create coroutines; we will explain more about this when we come to coding it in.

  1. Check/enter the following code for our PlayerTransition script, which holds the script's default name and MonoBeaviour inheritance for added functionality:

    public class PlayerTransition : MonoBehaviour {

  2. Within the PlayerTransition class, enter the following global Vector3 variables:

        Vector3 transitionToEnd = new Vector3(-100,0,0);    Vector3 transitionToCompleteGame = new Vector3(7000,0,0);    Vector3 readyPos = new Vector3(900,0,0);    Vector3 startPos;

The startPos and readyPos variables are used to measure the distance from where our player ship is and where we want it to travel to.

Tip

At this point, be sure that the _Player game object's Transform Position property values are set to zero on its X, Y, and Z axes in the Inspector window. Otherwise, the player ship may animate into the wrong position when entering the level.

The transitionToEnd variables will be used as the coordinates where we want our player game object ship to travel to at the start of the level, as well as when the player's ship is about to leave a level. transitionToCompleteGame is only used when the player completes the third and final level and is used to alter the player's ending animation.

  1. Continue entering the following float variables in our PlayerTransition script:

      float distCovered;  float journeyLength;

distCovered will hold time data that will be used later to measure between two Vector3 points (we will talk about this in more detail when we make PlayerMovement IEnumerator).

journeyLength will hold the distance between the two Vector3 points mentioned previously (startPos and readyPos).

  1. The final set of variables are the bools to be added to our PlayerTransition script:

    bool levelStarted = true; bool speedOff = false; bool levelEnds = false; bool gameCompleted = false; public bool LevelEnds {    get {return levelEnds;}    set {levelEnds = value;} } public bool GameCompleted {    get {return gameCompleted;}    set {gameCompleted = value;} }

levelStarted is the only bool set to true as it confirms that the level has started and will only be set to false after the transition of the player's animation has finished. speedOff will be set to true when we want the player's ship to leave the level.

levelEnds is set to true when the level has come to the end and the player ship will then move to its exit position. The last bool is for when the whole game has been completed. This is used to change the ending animation. The two properties are used for accessing the levelEnds and gameCompleted variables from outside of the script.

That's our variables added to our script. Now, let's continue to the PlayerTransition methods and functions.

Adding methods/functions to our PlayerTransition script

As we continue through our PlayerTransition script, we will add Unity's Start function and create our own Distance method to position the player's ship in the correct location:

  1. Starting with the Start function, continue entering the following code for our PlayerTransition script:

        void Start()    {        this.transform.localPosition = Vector3.zero;        startPos = transform.position;        Distance();    }

The Start function gets called as soon as this script is enabled. In this function, we will reset the position of the player's ship to its parent game object, which is the PlayerSpawner game object.

We will then assign the player ship's beginning world space position to one of the vectors we created earlier in this section (startPos). We will use this in the Distance method, which we will talk about next.

  1. Enter the Distance method and its content in the PlayerTransition class:

        void Distance()    {        journeyLength = Vector3.Distance(startPos, readyPos);    }

Vector3.Distance is a ready-made Unity function that will measure the distance between two vector points and gives the answer in the form of a float that we will be storing in journeyLength. The reason for this is that we will want to know the length between where our player ship is and where it needs to go (which we'll cover later in this chapter).

In the next section, we will move into Unity's Update function, where we will check for when the level has ended so that we can exit (move) our player ship out of the screen.

Adding if statement checks

In this section, we are going to make use of Unity's frame update function, Update, so that we can run checks to see what state our game is at within the level.

Within our Update function, we will have three if statements. levelStarted is from one of the bool variables that we introduced earlier on in this section, which is already set to true. So, this if statement is going to be called instantly. Let's take a look:

  1. Let's start by entering the first if statement in the PlayerTransition script's Update function:

        void Update()    {        if (levelStarted)        {            PlayerMovement(transitionToEnd, 10);         }

Within the first if statement is a method called PlayerMovement, which also takes two parameters. With regards to what this method does, we will review its content after we have covered the entirety of the Update function.

Now, let's continue with the second if statement in the Update function.

This if statement checks to see if the levelEnds variable is true, which, as you may recall, we set to false by default. This bool is accessed outside of the PlayerTransition class, which we will cover later, but for now, all we need to know is that it becomes true at the end of a level.

Inside the if statement, there are several lines that prepare our player's ship to begin the end of the level, starting with disabling the Player script by setting its enabled bool setting to false. This will knock out the player's controls so that we can animate the player ship into position for the end of the level.

Next, we disable the player ship's SphereCollider so that if an enemy or one of its bullets comes into contact with the player's ship, it won't destroy the ship while it's preparing to end the level.

  1. Enter the second following if statement inside the PlayerTransition Update function:

    if (levelEnds)  {    GetComponent<Player>().enabled = false;    GetComponent<SphereCollider>().enabled = false;    Distance();    PlayerMovement(transitionToEnd, 200);  }

Then, we measure the distance between where the player's ship was at the start of the level and where it needs to go with the Distance method.

Finally, within the if statement, we have the same method that we mentioned earlier, with the only difference being that the argument value is set to 200. These values will be explained after the fourth if statement for this Update function.

While we're still within the Update function, we can enter the if statement that covers when the player completes the third and final level:

  if (gameCompleted)    {      GetComponent<Player>().enabled = false;      GetComponent<SphereCollider>().enabled = false;      PlayerMovement(transitionToCompleteGame, 200);    }

If the gameCompleted bool is true, we fall into the if statements condition. Inside, we turn off the Player script to disable the player's controls. The second line disables the player's collider to avoid any collisions with any enemy-related game objects, while the third line makes the player ship translate from its current position to the value of transitionToCompleteGame.

  1. Enter the fourth if statement in our PlayerTransition Update function:

    if (speedOff)    {       Invoke("SpeedOff",1f);    }}

In the fourth if statement, we run a check to see if the speedOff bool holds the value of true. If it does, we run Unity's own Invoke function, which delays the execution of the SpeedOff method with a 1-second delay.

Further Information

More about Invoke can be found on the Unity Scripting reference site: https://docs.unity3d.com/ScriptReference/MonoBehaviour.Invoke.html.

In the next section, we will write some code so that the player is moved from where they are to where they need to be. The two cases where this will need to be achieved are as follows:

  • When the player starts the game, we animate them into the scene.
  • When the player has completed the level, they need to move into a position to leave the level.

We will be covering two new Unity functions (Mathf.Round and Vector3.Lerp).

Adding content to the PlayerMovement method

The PlayerMovement method holds the responsibility of animating our player ship in the near center of the screen so that it can begin and also exit the level.

Let's go into more detail to fully understand this:

  1. Enter the following code for our PlayerMovement method, along with its two parameters:

    void PlayerMovement(Vector3 point, float transitionSpeed)    {

As mentioned previously, our PlayerMovement takes two parameters: a Vector3 with the reference name point and a float with the reference name transitionSpeed. As you can imagine, transitionSpeed is the speed of the player ship moving from one point to another.

If we trace back to what the value of point is, it's coming from a variable that we've already initialized, transitionToStart, with a Vector3 at the beginning of this script with a value of (-100,0,0).

So, effectively, transitionToStart and point are the same – they're just different in terms of their names, for the sake of keeping their references separate. Anyway, coming back to point, this value is for our player's ship position. The following screenshot shows our player ship with Transform Position set to -100,0,0:

Figure 7.2 – Player Ship's position

Figure 7.2 – Player Ship's position

So, when a level begins, our player ship will be on the far left, outside of the screen, and animate into the position we have marked in the previous screenshot.

Carrying on with the PlayerMovement method, we begin with an if statement that checks when a series of conditions are met.

  1. Enter the following if statement, along with its four conditions:

       if (Mathf.Round(transform.localPosition.x) >= readyPos.x -5 &&               Mathf.Round(transform.localPosition.x) <= readyPos.x +5 &&               Mathf.Round(transform.localPosition.y) >= -5f &&                         Mathf.Round(transform.localPosition.y) <= +5f)                        {

In the previous code, we've run a check on four occasions to see if the player is in the correct position before executing the rest of the code. Each line of code checks the following:

  • If the player ship's X position is more than or equal to the value that is stored in readyPos variable's X position, minus 5.
  • If the player ship's X position is less than or equal to the value that is stored in readyPos variable's X position, plus 5.
  • If the player ship's Y position is more than or equal to the value that is stored in readyPos variable's Y position, minus 5.
  • If the player ship's Y position is less than or equal to the value that is stored in readyPos variable's Y position, plus 5.
  1. Still within our PlayerMovement method and within the previous if statement, enter the following two if statements:

       if (levelEnds)    {      levelEnds = false;      speedOff = true;    }   if (levelStarted)     {       levelStarted = false;       distCovered = 0;       GetComponent<Player>().enabled = true;      }   }

In the previous code block, we have two if statements (levelEnds and levelStarted) that check that each of the bool conditions are true. Let's go through both of their content:

  • levelEnds: if levelEnds becomes true, we apply false to the levelEnds bool and apply true to the speedOff bool.
  • levelStarted: if levelStarted is given the value true, we apply false to the levelStarted bool, set the distCovered float to 0, and we set the Player script to true.
  1. Lastly, in our PlayerMovement method, enter the following else condition that sits outside the main if statement:

       else      {       distCovered += Time.deltaTime * transitionSpeed;       float fractionOfJourney = distCovered /     journeyLength;       transform.position = Vector3.Lerp(transform.    position, point,         fractionOfJourney);      }   }

Referring to the else condition code block, we add time and multiply it by transitionSpeed, which, as you may recall, is one of the two arguments this method takes. Make sure this else statement is relating to the Mathf.Round if statement and not the two bool checking if statements.

We then divide the distCovered variable by the journeyLength variable, which, as you may recall, is a measurement between two points. We store the division in a float variable called fractionOfJourney.

The last thing we do in this else condition is use one of Unity's pre-made functions called Lerp, which linearly interpolates our player's ship between two points. Lerp takes three arguments: point A, point B, and the time scale it's going to move between these two points. transform.position is our player's ship, the second is the Vector3 point, which is the other variable we brought into IEnumerator, and the third is the active float fractionOfJourney.

Information

It's also possible to Lerp colors over time with Material.Lerp.For more information about changing one color into another, check out https://docs.unity3d.com/ScriptReference/Material.Lerp.html.

We now need to add a single line of code in the PlayerSpawner script to turn the PlayerTransition script on after the player leaves the shop scene. As mentioned earlier in the chapter, if the PlayerTransition was left on in the shop scene, the player_ship would animate across the screen.

So, to turn on the PlayerTransition script at the start of the level1 scene, we need to do the following:

  1. In the Project window, navigate to Assets/Script and open the PlayerSpawner script.
  2. Scroll down to the following line of code:

    playerShip.transform.position = Vector3.zero;

  3. Enter the following line of code just after it:

    playerShip.GetComponent<PlayerTransition>().enabled = true;

This line of code will make our player ship animate into the level1 scene.

The last change we need to make in the PlayerSpawner script is to remove the ability to enable the Player script in the PlayerSpawner Start function, we will enable this in the ScenesManager script.

  1. In the PlayerSpawner script, remove the following line in the Start function:

    GetComponentInChildren<Player>().enabled = true;

  2. Save the PlayerSpawner script.

Now, let's move onto the last bit of code, where we'll be moving our player ship out of the screen at the end of the level.

Moving the player ship out of the screen

The last method we need to cover in the PlayerTransition script is the SpeedOff method. This method simply makes our player's ship jet off, out of the screen, when the level is completed. Let's take a look:

  1. Enter the following code in our PlayerTransition script:

    void SpeedOff() { transform.Translate(Vector3.left * Time.deltaTime*800); }

This code block uses Unity's pre-made Translate function, which takes a Vector3.left multiplied by time, with 800 being used to make the player ship move a little faster.

  1. Save the script.

That is the end of the PlayerTransition script. Now, our game has an introduction and an ending for our player ship. Originally, our player would just be present at the start of the level and when it was classed as being completed, the next level would load. We also technically covered three new functions, as follows:

  • Vector3.Distance, which measures between two Vector3 points
  • Vector3.Lerp, which moves the player ship, smoothing between two Vector3 points
  • MathF.Round, which rounds off a number

We combined these new skills to make our player ship move into position to start the level and, when completed, no matter where the player was on the screen, we moved them into position. Finally, our player zooms off the screen.

In the next section, we are going to revisit the ScenesManager script and apply some code so that there's a time limit, counting down to when the level is over.

Expanding our ScenesManager script 

In this section, we are going to make our ScenesManager script recognize levels 2 and 3 from our scenes folder (Assets/Scene). We will then add these levels to the game loop. Also, we will be adding a game timer for each level. When the timer reaches its limit, we can then trigger the player leaving the level with an animation that will play out. Lastly, we will add a few common methods to move the game onto the next level.

Let's start by opening the ScenesManager script (Assets/Script/Scenesmanager.cs) and adding some variables to assist with what we were talking about. Follow these steps:

  1. At the top of the ScenesManager script, add the following variables:

      float gameTimer = 0;  float[] endLevelTimer = {30,30,45};  int currentSceneNumber = 0;  bool gameEnding = false;

The gameTimer variable timer will be used as our current counter to time how long the level has left until it is over. The following variable is an array that holds the time until each level ends. So, we have three levels in total, but the question is, how long do we want each level to last? We need to enter a value that represents the seconds until the level ends, so I've chosen 30 seconds for levels 1 and 2. Level 3, however, will last 45 seconds. This is because we will be building a special level in , NavMesh, Timeline, and a Mock Test. We will go into more detail about this when we reach that chapter.

As you can imagine, currentSceneNumber will hold the number that denotes which scene our player is currently on. Lastly, we have the gameEnding bool, which will be used to trigger the end of the level animation for the player's ship. We will cover these variables in more detail later in this section, let's start with currentSceneNumber.

Following on from the global variables we just set, let's make sure that the ScenesManager script is always aware of what scene our player is on during the game. This will help our code know which scene the player is on and what scene they will be going to next.

  1. Add the Update function, which will be called on every frame to check which scene we are at. Do this by entering the following code in the ScenesManager script:

      void Update()  {    if (currentSceneNumber !=       SceneManager.GetActiveScene().buildIndex)    {      currentSceneNumber = SceneManager.GetActiveScene().buildIndex;      GetScene();    }    GameTimer();  }

Inside the Update function, we use an if condition to check if the currentSceneNumber variable is not equal to the buildIndex we are grabbing from the active scene we are in.

If it is not equal, we update currentSceneNumber with the current scene's buildIndex, followed by the GetScene method. The GetScene method is a small method that is worth covering now instead of later as it relates to everything we've just said.

Inside the GetScene method is a single line of code that updates the scene's variable. This is an instance from the Scenes enum that holds the names for each scene in our game. Also, the code in the GetScene method is casting currentSceneNumber to an enum, which is why the Scenes type is in brackets. More about casting can be found at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/casting-and-type-conversions.

  1. Enter the following code for our ScenesManager script:

      void GetScene()  {    scenes = (Scenes)currentSceneNumber;  }

We can put the GetScene method anywhere in the ScenesManager class, as long as it's not within another method.

Coming back to the Update function, after calling the GetScene method, we close the if conditions brackets. The last thing we do before closing the Update function is run the GameTimer method, which will keep track of our game's time and set up some basic methods that will start, reset, and end our game levels.

In the following sections, we will cover the following topics:

  • Adding a timer to each game level. When the timer is up, that notifies the player has completed the level.
  • Make it so that when a level is completed, the ScenesManager script knows what to do next; that is, load a level, which level, and so on.

Let's get started.

Adding a game level timer

In the ScenesManager script, we will set up a method that will be responsible for acknowledging the level has ended. The GameTimer method serves the purpose of adding time to a gameTimer variable and checking to see if it has reached its limit, depending on the endLevelTimer it's comparing to. Finally, if the game has been triggered to end the player ship's animation, it is set to start and the next level is loaded after 4 seconds.

With your ScenesManager script still open, add the following method to your code:

void GameTimer() {   switch (scenes)   {     case Scenes.level1 : case Scenes.level2 : case Scenes.level3 :     {         if (gameTimer < endLevelTimer[currentSceneNumber-3])        {           //if level has not completed            gameTimer += Time.deltaTime;         }         else          {         //if level is completed          if (!gameEnding)         {            gameEnding = true;            if (SceneManager.GetActiveScene().name != "level3")          {            GameObject.FindGameObjectWithTag("Player").    GetComponent                 <PlayerTransition>().LevelEnds = true;          }          else           {            GameObject.FindGameObjectWithTag("Player").    GetComponent                <PlayerTransition> ().GameCompleted = true;                                                                                                                     }             Invoke("NextLevel",4);         }       }      break;     }    } }

Inside the GameTimer method, we run a switch statement holding the scenes instance that will contain all of the enum names of each level. We run a check on three possible cases: level1, level2, and level3. If the scenes instance is set to either of the three possibilities, we will fall into an if condition that will then compare whether the gameTimer variable is less than what the endLevelTimer array has been set to.

We only need to know what build index number levels 1, 2, and 3 are on. So, to avoid the first three scenes, we must subtract by 3.

The following screenshot shows the Build Settings window (File | Build Settings), which contains the scenes and their build numbers in your project on the right-hand side:

Figure 7.3 – Build Settings – scene order

Figure 7.3 – Build Settings – scene order

If gameTimer is less than levelTimer, we will continue to increment gameTimer with the Time.deltaTime fixed function that Unity has pre-made for us. More information about Time.deltaTime can be found here: https://docs.unity3d.com/ScriptReference/Time-deltaTime.html.

If gameTimer is equal to or more than levelTimer, we will move into the else condition, which checks the condition of the if statement of the gameEnding bool being false. If the condition is false, we fall into the content of the if statement, which first sets the gameEnding bool to true. This will stop the if statement from repeating in the Update function's frame cycle.

The last if statement checks which level our game is on. If we are not on "level3", we set the LevelEnds property in the PlayerTransition script to true. Otherwise, we must have completed the game. So, in the else condition, we set the GameComplete property to true in the PlayerTransition script.

In this section, we created a method in the ScenesManager script that made our game aware of how long each level will last before classing the level as completed.

We will now continue with the ScenesManager script by adding methods that will start, reset, and move our player onto the next level when triggered by the GameTimer method.

Beginning, resetting, and skipping levels

ScenesManager will have the responsibility of starting a level, resetting it when the player dies, and moving onto the next level when the current one has been completed. Thankfully, these require minimal work thanks to Unity's SceneManagement library.

Let's start by revisiting the ResetScene method we have already started, but now, we will simplify it even more:

  1. Replace the content from our ResetScene method with the following code in our ScenesManager script:

      public void ResetScene()  {    gameTimer = 0;    SceneManager.LoadScene(GameManager.currentScene);  }

Inside the ResetScene method, we reset the gameTimer variable to zero, followed by replacing its parameter from the current SceneManager.LoadScene buildIndex to GameManager.currentScene, which we coded back in Chapter 3, Managing Scripts and Taking aMock Test. This is basically just holding the current build index as a static integer so that any script can access it.

With ResetScene updated, we can now move onto the next method, which is very similar to what we have just done, but it is separate to ResetScene in order to support the expansion of our code.

When a player completes a level, the NextLevel method runs, which will reset the gameTimer variable. The gameTimer bool will be set back to false and the same SceneManager.LoadScene command will be used to increment the GameManager currentScene integer by 1.

  1. Enter the following method in the ScenesManager script:

      void NextLevel()  {    gameEnding = false;    gameTimer = 0;    SceneManager.LoadScene(GameManager.currentScene+1);  }

The last method we need to change in our ScenesManager script is our BeginGame method, which will be called when the player is in the shop scene and pressing the "START" button.

  1. Enter the following code for our ScenesManager script:

    public void BeginGame(int gameLevel) {   SceneManager.LoadScene(gameLevel); }}

The BeginGame method will take an integer parameter called gameLevel. Inside this method is the same SceneManager.LoadScene we have already used, but this time, it will load the gameLevel integer we are providing it.

  1. Save the script.

If you remember, back in the previous chapter we set the PlayerSpawner script to temporary call the GameManager.Instance.CameraSetup();

This call is no longer required and can now be removed. Let's remove it

  1. In the Project window, navigate to Assets/Script.
  2. Load the PlayerSpawner script and remove the line from the Start function GameManager.Instance.CameraSetup();
  3. Save the script.

Because we have changed the BeginGame method to now take a parameter, we must update our PlayerShipBuild script, which has a StartGame method that runs the BeginGame method with, currently, no parameter value. To update the PlayerShipBuild StartGame method, we need to do the following:

  1. In the Unity Editor, navigate to the Assets/Script/PlayerShipBuild.cs folder in the Project window and open it.
  2. Scroll down to the StartGame method and find this line of code:

    UnityEngine.SceneManagement.SceneManager.LoadScene("testLevel");

  3. Now, change the preceding line of code to this:

    GameManager.Instance.GetComponent<ScenesManager>   ().BeginGame(GameManager.gameLevelScene);

This code change will now call the level1 scene directly.

With that, we have reached the end of this section. So far, we have covered the following:

  • Our game is now aware of how long a level will take until it is classed as completed.
  • The ScenesManager script can now call methods that will start, reset, and move the player onto the next level.

The majority of our code was created with the use of switch statements and an enum to call when the scenes need to be changed. To load the scenes themselves, we used Unity's own SceneManager class, which is fundamental to loading any scene in a Unity Project.

In the next section, we will prepare the rest of the scenes in our project that aren't game levels (the bootUp scene, the title scene, and the gameOver scene).

Preparing to loop our game

In this section, we are going to move away from the testLevel scene and introduce three other levels (level1, level2, and level3) to demonstrate the game loop.

By the end of this section, our game loop will be complete. We will be able to start our game from the bootUp scene. From there, we will be able to progress through each scene.

Let's start by removing the placeholder levels in the Unity Editor. Go to the Project window and the Assets/Scene location. Follow these steps:

  1. Make sure your player_ship is saved (Overrides | Apply All) and deleted from the testLevel scene. Next, Delete level1, level2, and level3.
  2. Select testLevel, hold the Left Ctrl (Command on the Mac) key on the keyboard, and press D twice. We should now have three testLevel instances.
  3. Rename testLevel to level1.
  4. Rename testLevel 1 to level2.
  5. Rename testLevel 2 to level3.

We now need to check the Build Settings window to check on the order of our scenes.

  1. At the top of the Unity Editor, click File | Build Settings.

Our order should look like the one shown in the following screenshot. If it doesn't, select and move the scenes into the correct position by clicking and dragging them in the Build Settings window and by selecting and deleting any extra scenes in the list:

Figure 7.4 – Build Settings – complete scene order

Figure 7.4 – Build Settings – complete scene order

We have duplicated our first level twice to test that our levels can be completed and move forward. Next, we will go back to the first scene in our project list and set it up so that it's ready to act like a boot up scene.

Because we have removed our testLevel scene, we need to update our GameManager script with regards to the LightandCameraSetup method to keep its Switch statement in sync with the levels we need to light up, as well as set up our camera.

To make it so our camera and lights work correctly for each scene, we need to do the following:

  1. In the Project window, navigate to the Assets/Script folder.

Double-click on the GameManager script.

  1. Scroll down to the LightandCameraSetup method's content and make it so that each case number follows this pattern:

    case 3 : case 4 : case 5:

Each case represents the levels the player is going to play.

  1. Save the script.

In the next few sections, we will be customizing a placeholder look for each nonlevel scene (basic but informative). These scenes are as follows:

  • bootUp
  • title
  • gameOver

Each of these scenes will also require basic coding so that the player either presses a button to continue or a timer will be issued. This timer will count down until the next scene is loaded.

Setting up the bootUp scene

When we run a game, typically, the game doesn't start straight away – there's normally a splash screen to show who developed/published the game. Sometimes, it's used as a loading screen, but for us, it will be used to get our game started. In this section, we are going to take away the typical Unity sky background and replace it with a neutral grey color background with a text title that states what screen has loaded up.

Let's make a start and open the bootUp scene in the Unity Editor:

  1. In the Project window, navigate to the level1 scene by going to Assets/Scene.
  2. Double-click on level1, drag and drop the GameManager game object into the prefab folder if it isn't already.
  3. Next, double-click on the bootUp scene file from Assets/Scene.
  4. Drag and drop the GameManager prefab from the Project window location, Assets/Prefab, to the Hierarchy window.
  5. Create an empty game object in the Hierarchy window. If you have forgotten how to do this, refer to Chapter 2, Adding and Manipulating Objects.
  6. Name the newly created game object BootUpText.
  7. Create another empty game object as before and name that BootUpComponent.

The following screenshot shows the components on the left-hand side of the Hierarchy window. These are as follows:

  • Main Camera
  • Direction Light
  • GameManager
  • BootUpComponent
  • BootUp Text
Figure 7.5 – Game object Hierarchy window order

Figure 7.5 – Game object Hierarchy window order

On the right-hand side of the preceding screenshot, we have our GameManager game object selected showing its three main component scripts:

  • Game Manager
  • Scenes Manager
  • Score Manager

As you may recall, our GameManager script will always remain in a scene, even if the scene is replaced with another, so it's vital we have these components in our Game Manager prefab.

Next, we are going to change the background from sky to grey, as mentioned previously. To do this, select Main Camera from the Hierarchy window. Now, follow these steps:

  1. In the Inspector window, click the Clear Flags selection and change it from Skybox to Solid Color.
  2. Just below Clear Flags, click to change the Background selection and replace whatever the Hex Color value is with 32323200.
  3. This will change the RGB values to 50,50,50 with an alpha setting of zero.

Use the following screenshot as a reference for the location for Clear Flags, Background, and Hex Color:

Figure 7.6 – Changing the background color of our scene

Figure 7.6 – Changing the background color of our scene

This will change the background in the Game window to gray.

Next, we will select BootUpText and add a Text Mesh that will be at the bottom center of the screen. Follow these steps:

  1. Select the BootUpText game object in the Hierarchy window.
  2. Then, in the Inspector window, click the Add Component button.
  3. In the drop-down, type Text Mesh until you see it in the drop-down list.
  4. Select Text Mesh from the drop-down.

With the BootUp Text game object still selected, change its Transform Position to the following:

Now that our text is in the correct position, we need to fill out the Text Mesh component in Inspector. Follow these steps:

  1. In the Text section of Text Mesh, type BootUp.
  2. Set Anchor to Middle center.
  3. Set Alignment to Center.
  4. Open the Game window (shortcut: Ctrl (command on Mac) + 2). Now, we should have a gray screen with white text so that we can easily identify the scene we are in. The following screenshot shows the "BootUp" text's settings, along with its Inspector properties for reference:
Figure 7.7 – Basic 'BootUp' scene

Figure 7.7 – Basic 'BootUp' scene

The last thing we need to do for this bootUp scene is to make it function like most bootUp screens.

When the bootUp screen appears, it stays there for a couple of seconds and then moves onto the next scene.

To make it so the bootUp screen loads onto the next screen after a few seconds, we will need to create a script and add it to the BootUpComponent game object.

  1. When we make the script, we need to store it with our other scripts in the Project window (Assets/Script).

If you have forgotten how to make a script, check out the Updating our camera properties via script section in Chapter 2, Adding and Manipulating Objects.

  1. Name the script LoadSceneComponent.

The following screenshot shows what the BootUpComponent game object should look like when it's selected in the Hierarchy window:

Figure 7.8 – 'BootUpComponent' game object and script

Figure 7.8 – 'BootUpComponent' game object and script

  1. Double-click the grayed-out field of LoadSceneComponent in the Inspector window to open the file.

The following code is similar to the code that we entered previously for loading a level, just in a shorter form. The basic principle is that we load in UnityEngine.SceneManagement to inherit Unity's SceneManager class.

Our game's score gets reset at the start of the script to stop any previous scores being carried over.

Then, we create a timer and increment the time in Unity's Update function. Once the timer goes over 3 seconds, SceneManager will load whatever we have put in the loadThisScene public variable, which in our case is "title".

The following screenshot shows the LoadSceneComponent script in Inspector with a field where we can enter the scene we wish to load:

Figure 7.9 – Load Scene Component will load the 'title' scene

Figure 7.9 – Load Scene Component will load the 'title' scene

It's as simple as that – we don't need to worry about anything else as the bootUp scene isn't part of the game loop. The bootUp scene is only played once when the game starts.

  1. Enter the following code into LoadSceneComponent:

    using UnityEngine.SceneManagement;using UnityEngine;public class LoadSceneComponent : MonoBehaviour { float timer = 0; public string loadThisScene; void Start(){  GameManager.Instance.GetComponentInChildren      <ScoreManager>().ResetScore();} void Update() {     timer += Time.deltaTime;     if (timer > 3)     {         SceneManager.LoadScene(loadThisScene);     } }}

  2. Once you're done, save the script.
  3. Go back into the Unity Editor and type title into the loadThisScene variable field in the Inspector window, as shown in the preceding screenshot.
  4. Save the bootUp scene and press Play in the Unity Editor. The bootUp scene should load up and then, after 3 seconds, load up the title scene.

We can now repeat the majority of what we've done in the bootUp scene and duplicate this for the title and gameOver scenes. We will do this next.

Setting up the title and gameOver scenes

The way we set the bootUp scene in the previous section is similar to how we want the title and gameOver scenes to look and act before we add any new art and custom functionality.

Thankfully, with Unity, we don't have to repeat the entire process of making these two scenes from scratch. We can copy, paste, and rename the game objects we have already created in the bootUp scene's Hierarchy window and paste them into the title and gameOver scenes.

To copy the gray background and white Text Mesh text from the bootUp scene, do the following:

  1. With the bootUp scene still active in the Unity Editor, select all of the 5 game objects from the Hierarchy window (click the top or the bottom of the list, hold Shift, then click either end of the list to select all).
  2. Press Left Ctrl (Command on Mac) + C to copy these 5 game objects.
  3. Open the title scene from the Project window (Assets/Scene).
  4. Select and delete all game objects in the Hierarchy window.
  5. Click anywhere in the open space of the Hierarchy window and hold Left Ctrl (Command on Mac) + V to paste the bootUp game objects.
  6. Select BootUpText in the Hierarchy window and rename it TitleText.
  7. With the TitleText game object still selected, change the Text field in the Text Mesh component to Title.
  8. Select BootUpComponent in the Hierarchy window and rename it to TitleComponent.
  9. With the TitleComponent game object still selected, click the three small dots in the Inspector window next to LoadSceneComponent (Script).
  10. A drop-down will appear; click Remove Component from it.

We now need to make a script for the TitleComponent game object so that when the player taps or clicks the mouse button, the shop scene will load up next.

  1. Repeat the same process of making and attaching a script as we did with BootUpComponent, but this time, name the script TitleComponent (also, as with the TitleComponent script, make sure it is moved into the correct folder in the Project window, Assets/Script) and paste in the following code:

    using UnityEngine.SceneManagement;using UnityEngine;public class TitleComponent : MonoBehaviour { void Update() {     if (Input.GetButtonDown("Fire1"))     {         SceneManager.LoadScene("shop");     } }  void Start()  {    GameManager.playerLives = 3;  }}

The difference between this TitleComponent script and the previous BootUpComponent script is that TitleComponent will move onto the next scene (shop scene) when a mouse button (or a finger on a touch screen) is pressed and released in Play mode. And as a temporary solution, it will make sure, as a failsafe, that the player starts with three lives. This is unlike BootUpComponent, which is dependent on a timer to increment the past 3 seconds to load the next scene, where its failsafe is to reset the game's score if the player completes the game.

  1. Save the TitleComponent script and title scene.

The following screenshot shows what the title scene should look like in the Unity Editor:

Figure 7.10 – Hierarchy 'title' scene game object order

Figure 7.10 – Hierarchy 'title' scene game object order

We now need to repeat the exact same process for the gameOver scene.

Open the gameOver scene from the Project window (Assets/Scene) and repeat the process of pasting and renaming the game objects. Do the following:

  1. In the Hierarchy window, change the name of the BootUpComponent game object to GameOver.
  2. Still in the Hierarchy window, rename BootUpText to GameOverText.
  3. Select the GameOver component in the Hierarchy window. Then, in the Inspector window, click the Add Component button and type LoadSceneComponent until we see it in the list. Then, select it if we don't have the component added already..

The following screenshot shows the GameOver component with the same LoadSceneComponent script where I added "title" to the loadThisScene variable field:

Figure 7.11 – Load Scene Component loading the 'title' scene

Figure 7.11 – Load Scene Component loading the 'title' scene

  1. Save the gameOver scene.

Our Unity Project is now ready to run its full game loop. We will talk about the game loop in more detail in the next section.

Demonstrating that the game loop is complete

In this final section, we will confirm what we have achieved in this chapter. Our game now has a game loop, so if we load up the bootUp scene and press Play in the Unity Editor, the sequence will be as follows:

  • bootUp: The scene runs for 3 seconds and then moves to the title scene.
  • title: If the player presses the mouse button, the shop scene will load.
  • shop: The player presses the START button to load level1.
  • level1: The player completes the level after 30 seconds (45 seconds for level 3) or dies. If the player dies more than 3 times, they will be presented with the gameOver scene.
  • level2: The same rules apply as the ones present for level1.
  • level3: The same rules apply as the ones present for level1, but if the player completes the level, they will be presented with the gameOver scene.
  • gameOver: The scene runs for 3 seconds and then moves to the title scene.

The following image shows the process of our game loop moving through each scene, then going back to the title scene:

Figure 7.12 – Killer Wave's game loop

Figure 7.12 – Killer Wave's game loop

Tip

Remember that if any of our scenes look darker than usual, we will need to bake its lights manually, as we did back in Chapter 3, Managing Scripts and Taking a Mock Test.

With this, we have created a series of scenes that carry their own individual responsibilities. When a scene comes to its end, either by its own choice or prompted to by the player, the next scene in the sequence will load. Eventually, by the player either completing all three levels or losing all their lives, our game will reach the gameOver scene. From the gameOver scene, we send the player back to the title scene. This is our game loop, and this is what every game will have. Game loops are a fundamental requirement for game development, and it's also possible that this will be mentioned in the exam.

This concludes this section and this chapter, where we have created and managed our scenes in order to create a game loop.

Summary

In this chapter, we created a game loop; these are fundamental to game development, and sometimes application development. To create a game loop for our project, we needed multiple scenes that served their own purposes. We also needed to know when a scene started and when it should end. A scene ends when the player presses a button to continue, such as the 7 scene, or when the bootUp title automatically moves onto the next scene after so many seconds.

Apart from making our game loops, we also learned some new vector math components on the way, including Mathf.round, which is used to round off figures Vector3.distance, which is used to measure the distance between two Vector3 points; and Vector3.lerp, which is used to interpolate between two Vector3 points.

These are useful components in game development and will also likely be mentioned in the exam.

In the next chapter, we will be adding some polish to our placeholder scenes with custom fonts, creating our own images, and applying some UI animation in the Unity Editor.

Mock test

  1. What would be the best way for a UI menu system to be worked on from a programmer's perspective, but at the same time in a way that doesn't interfere with an artist working on the same workflow?
    1. Make it so that each UI component has its own class so that any art changes won't affect either outcome.
    2. Give each UI component a separate material so that any changes in the code will be isolated.
    3. Use prefabs for each UI component so that any artist can modify them individually. 
    4. Have a separate script that sweeps through all UI components to check any changes that are made so that they're known to everyone.
  2. An Image component has a sprite in its Source Image parameter and its Image Type is set to Filled. What does Filled do?
    1. Fills open spaces in the sprite.
    2. It offers various ways to fill in the sprite.
    3. Makes it so no other sprite can override it.
    4. Inverts the color of the sprite.
  3. What component does CrossPlatformInputManager replace?
    1. anyKey
    2. Input 
    3. mousePosition
    4. acceleration
  4. When testing a top-down shooter game you have just developed, you want the controls to have an "Arcade" feel. To make the controls snap into position when moving the player, which property would help create what is required?
    1. GetAxisRaw 
    2. GetJoystickNames
    3. InputString
    4. gyro
  5. When writing code such as variable names, which is the correct naming convention to use?
    1. Pascal case
    2. Lower case
    3. Cake case
    4. Camel case
  6. You are working with a team to create a realistic simulation for the military that includes a series of explosions. You have been asked to take over from the previous developer who has, so far, created a framework that issues a series of explosions from a bank of prefabs. The prefabs are updated on a regular basis by one of the artists on the team. As impressive as this looks, the program has gotten quite big and the artist will need to have the option to update, swap out, replace, and delete prefabs from the framework.

What solution can you offer the team that keeps this framework from not going against SOLID principles and is accessible to the artist in the team?

  1. Create a series of prefabs that hold a cluster of prefabs that randomize on each occasion when they're used in the Unity scene.
  2. Create a single scriptable object that holds an array of prefabs that holds a reference to either script.
  3. Create a non-procedural particle system that creates its own explosions.
  4. Hold all the explosions in the scene at runtime but off-camera and then bring in those required using a random selection script.
  1. Which collider is the fastest for the Unity physics system to calculate?
    1. Hinge
    2. Sphere
    3. Mesh
    4. Box
  2. Which is the cheapest MinMaxCurve to use?
    1. Optimized Curve
    2. Random between two constants
    3. Random between two curves
    4. Constant
  3. Which property needs to be accessed through code to create a strobe effect for a nightclub scene?
    1. color.a
    2. spotAngle
    3. range
    4. intensity
..................Content has been hidden....................

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