Chapter 3: Managing Scripts and Taking a Mock Test

In this chapter, we are going to continue structuring our game by applying a Singleton design pattern to our GameManager script. This will allow our game to move on to another scene while keeping the script managers functioning and preventing them from being wiped (thereby preserving our data). We will then make a start on other details of our script and observe how information (such as the player's lives) travels through the game's frameworks. If and when the player dies, a life is deducted. If and when the player loses all of their lives, the game over scene will be triggered.

We will be extending our original code and introducing enemy points so that when we hit our enemies with bullets, the enemy will disappear as usual, but will also generate points. This scoring mechanism will be handled by a new score manager that we will be creating.

We'll also be adding sound to the player's bullets, which is a straightforward task. This will introduce us to extending and tweaking our audio sources, which we'll proceed with in a later chapter.

Finally, we will be quizzing ourselves with a couple of questions that suit the theme of this book, preparing you for the exam. The questions will cover what we have already learned, and if you have been following along with this book, you'll have a strong chance of passing.

By the end of this chapter, we will have extended our game's framework, added more features to our game, and tested our knowledge with some Unity exam questions.

In this chapter, we will be covering the following topics:

  • Adding a Singleton design pattern
  • Setting up our ScenesManager script
  • Creating lives for the player
  • Scoring enemy hits
  • Creating sounds for the player's bullets
  • Mock test

The next section will introduce the core exam skills that are covered in this chapter.

The core exam skills covered in this chapter

Programming core interactions:

  • Implementing and configuring game object behavior and physics

Programming for scene and environment design:

  • Determining scripts for implementing audio assets
  • Identifying methods for implementing game object instantiation, destruction, and management

Working in professional software development teams:

  • Recognizing 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_03.

You can download the entirety of each chapter's project files at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition.

All content for this chapter is held in the chapter's unitypackage file, including a Complete folder that holds all of the work we'll be carrying out in the chapter.

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

Adding a Singleton design pattern

As you will recall, back in Chapter 1, Setting Up and Structuring Our Project, we spoke about design patterns and how useful they are for maintaining our code. One of the design patterns we briefly covered was the Singleton pattern. Without repeating ourselves, the Singleton pattern gives us global access to code that can then be obtained at a point in our game. So, where can we see the benefits of using the Singleton design pattern? Well, we could use it so that Unity always keeps certain scripts accessible, no matter what scene we are in. We have already added a lot of structuring to our game framework and we still have a couple of manager scripts to add, such as ScoreManager and ScenesManager.

Now is a good time to give all of the manager scripts global access to all other scripts in the game. Managers give a general overview of what is going on and steer which way the game needs to go without getting caught up in the details of the other scripts that are running during gameplay.

In our current setup, when we run the testLevel scene, our GameManager object is in the Hierarchy window. We also have—and will be adding—more manager scripts to this game object. Currently, when we change scenes, our GameManager script, which sets up our scene's camera and lights, is no longer present.

To stop our GameManager game object and script from being wiped, we are going to add a Singleton design pattern so that our GameManager script will always be in the scene. This design pattern will also make it so that there is only one GameManager script (which is where this design pattern gets its name from).

In the following instructions, we will extend our original GameManager code to work as a Singleton script. Double-click on the GameManager script and let's make a start:

  1. At the beginning of the class, we need to add a static variable and a public static property, both referring to our GameManager script:

    static GameManager instance;

    public static GameManager Instance

    {

      get { return instance; }

    }

The reason we do this is that static means there is only one type of game manager. This is what we want; we don't want to have multiple instances of the same manager.

  1. Next, we need to check and assign our instance variable with the GameManager class when the script begins with the Awake function.

The Awake function ends with a Unity function called DontDestroyOnLoad. This will make sure the game object holding our GameManager class will not be destroyed if the scene changes.

Tip

If the player dies and loses all their lives, we can move from the level scene we are on to the gameOver scene, but we won't wipe the GameManager game object from the scene as this holds the main core methods to run the game.

  1. Add an else loop to prevent any possible duplicate GameManager game objects. We can see these two steps in the following code block:

      void Awake()

      {

        if(instance == null)

        {

          instance = this;

          DontDestroyOnLoad(this);

        }

        else

        {

          Destroy(this.gameObject);

        }

        

      }

  2. To make our code easier to identify, wrap the code we just typed out in the Awake function and put it in a method called CheckGameManagerIsInTheScene.
  3. Call the method from the Awake function.

    Tip

    A similar method to DontDestroyOnLoad is MoveGameObjectToScene, which can be used to carry a single game object over to another scene. This could be useful for moving a player from one scene to another: https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.MoveGameObjectToScene.html.

That's it, our Singleton design pattern is done! The following screenshot shows a snippet of what our GameManager script should look like:

Figure 3.1 – Singleton code pattern in our GameManager script

Figure 3.1 – Singleton code pattern in our GameManager script

  1. Finally, save the GameManager script.

We have created a Singleton design pattern that will not be wiped away when we alternate through the scenes in our game, giving us global control of our game no matter which scene we are in.

Now, we can jump into adding the ScenesManager script and attaching it to the same game object as GameManager (in its Inspector window).

Setting up our ScenesManager script

We will take some responsibility away from the GameManager script by making another manager script to be more consistent with the data and methods it holds. ScenesManager will take and send information to and from GameManager. The following diagram shows how close to GameManager our ScenesManager script is within the framework when only communicating with GameManager:

Figure 3.2 – ScenesManager location in the Killer Wave UML

Figure 3.2 – ScenesManager location in the Killer Wave UML

The purpose of ScenesManager, apart from taking the workload off GameManager, is to deal with anything related to creating or changing a scene. This doesn't mean we only focus on adding and removing game levels; a scene can also consist of a start up logo, a title screen, a menu, and a game over screen, all of which are part of the ScenesManager script's responsibility.

In this section, we will be setting up a scene template and two methods. The first method will be responsible for resetting the level if the player dies (ResetScene()); the second will be the game over screen (GameOver()).

Let's make a start by creating a new script in the same way that we did in Chapter 2, Adding and Manipulating Objects. Follow these steps:

  1. Name the script ScenesManager.
  2. Add the script to the GameManager game object. If you need further details on adding a script to a game object, check out the Adding our script to a game object section of the previous chapter.
  3. With our GameManager game object selected from the Hierarchy window, go to the Inspector window. We should now have the GameManager and ScenesManager scripts attached, as shown in the following screenshot:
Figure 3.3 – GameManager game object holding two scripts (GameManager and ScenesManager)

Figure 3.3 – GameManager game object holding two scripts (GameManager and ScenesManager)

Let's open the ScenesManager script and start coding:

  1. Because we are obviously going to be closing and loading scenes, we are going to need to import an extra library into our ScenesManager script that supports these operations:

    using UnityEngine.SceneManagement;

    using UnityEngine;

  2. We will have a public class in our script name, followed by the usual MonoBehaviour being inherited:

    public class ScenesManager : MonoBehaviour

    {

Now, we need to create a list of references for our scenes, as mentioned earlier. I currently have the following scenes labeled:

  • bootUp: Credits to game
  • title: Name of the game with an instruction to start
  • shop: Buy upgrades before starting the game
  • level1: First level
  • level2: Second level
  • level3: Final level
  • gameOver: Game over—delays until going back to the title scene

We will be labeling these scenes as enumerations (which are denoted as enum in the C# language). These values stay consistent.

Tip

If you would like to know more about enumeration, check out https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum.

  1. Enter the following code into the ScenesManager script:

      Scenes scenes;

      public enum Scenes

      {

        bootUp,

        title,

        shop,

        level1,

        level2,

        level3,

        gameOver

      }

We will be making and adding these scenes in their respective order in the Unity Editor later on in the book. Before we do so, let's add two methods, starting with the ResetScene() method, which is typically used when the player dies and the current level is reloaded. The other method, GameOver(), is typically called when the player loses all of their lives or when the game is complete.

Adding the ResetScene() method

The ResetScene() method will be called when the player loses a life but still has another remaining. In this short method, we will set its accessibility to public and it returns nothing (void).

Within this method, we will refer to Unity's SceneManager script (not to be confused with our ScenesManager class), followed by Unity's LoadScene method. We now need to provide a parameter to tell LoadScene which scene we are going to load.

We use Unity's SceneManager script again, but this time we use GetActiveScene().buildIndex, which basically means getting the value number of the scene. We send this scene number to SceneManager to load the scene again (LoadScene):

  public void ResetScene()

  {

    SceneManager.LoadScene(SceneManager.GetActiveScene().    buildIndex);

  }

A small but effective method, this can be called whenever we need the scene to reset. Let's now move on to the GameOver() method.

Adding the GameOver() method

This method, as you may expect, is called when the player has lost all of their lives and the game ends, which means we need to move the player on to another scene.

In this method, we continue adding to the ScenesManager script:

  public void GameOver()

  {

    SceneManager.LoadScene("gameOver");

  }

}

Similar to the previous method, we refer to this method as public with void return. Within the method, we call the same Unity function, SceneManager.LoadScene, but this time, we call the SceneManager Unity function, followed by the name of the scene we want to load by name (in this case, gameOver).

More Information

SceneManager.LoadScene also offers a LoadSceneMode function, which gives us the option of using one of two properties. By default, the first property is Single, which closes all the scenes and loads the scene we want. The second property is Additive, which adds the next scene alongside the current one. This could be useful when swapping out scenes, such as a loading screen, or keeping the previous scene's settings. For more information about LoadScene, check out https://docs.unity3d.com/ScriptReference/SceneManagement.LoadSceneMode.html.

That's our GameOver() method made, and when used in the same way as our ResetScene() method, it can be called globally. GameOver() can be called not only when the player loses all their lives but also when the user completes the game. It can also be used if, somehow, the game crashes, and as a default reset, we proceed to the gameOver scene.

The next method to bring into our ScenesManager script is BeginGame(). This method is called when we need to start playing our game.

Adding the BeginGame() method

In this short section, we will add the BeginGame() method to our ScenesManager script as this will be called to start playing our game after visiting the shop scene, which we will cover in Chapter 5, Creating a Shop Scene for Our Game.

With the ScenesManager script still open from the previous section, add the following method:

   public void BeginGame()

  {

    SceneManager.LoadScene("testLevel");

  }

The code that we have just entered makes a direct call to run the testLevel scene, which we play our game in already. However, as our game begins to grow, we will use more than one scene.

The next thing to do is to create our scenes and add them to the Unity build menu, so let's do that next. Remember to save the ScenesManager script before returning to the Unity Editor.

Adding scenes to our Build Settings window

Our game will consist of multiple scenes through which the player will need to navigate before they can fly their spaceship through the levels. This will result in them either dying or completing each level and the game, and then being taken back to the title scene. This is also known as a game loop. Let's start by going back to Unity and, in the Project window, creating and adding our new scenes. Follow these steps:

  1. Go to the Assets/Scene folder that we created at the beginning of the previous chapter.
  2. Inside the Scene folder, in the open space, right-click so that the dropdown appears, and then click Create, followed by Scene, as shown in the following screenshot:
Figure 3.4 – Creating an empty scene in the Unity Editor

Figure 3.4 – Creating an empty scene in the Unity Editor

  1. A scene file will appear. Rename it bootUp.
  2. Repeat this process for the shop, level1, level2, level3, gameOver, and title scene files.

Once we have made all of our scenes, we need to let Unity know that we want these scenes to be recognized and applied to the project build order. This is a similar process to what we did in the last chapter when adding testLevel to the Build Settings window. To apply the other scenes to the list, do the following:

  1. From the top of the Unity Editor, click on File | Build Settings.
  2. The Build Settings window will open, and you should have testLevel in the list already. If you don't, fear not as we will be adding all our scenes to the Scenes In Build list.
  3. From the Project window, click and drag each scene into the Build Settings | Scenes in Build open space.

Once we have added all the scenes, order them as follows:

  • bootUp
  • title
  • shop
  • testLevel
  • level1
  • level2
  • level3
  • gameOver

    Tip

    Note that each scene automatically has a camera and a light by default in its Hierarchy window. This is fine and we will customize them later on in this book.

The Build Settings window should now look as follows:

Figure 3.5 – Build Settings current Scenes In Build list order

Figure 3.5 – Build Settings current Scenes In Build list order

The reason why we are putting our scenes in this order is so that there is a logical progression in the levels. As you can see at the far right of each scene in the previous screenshot, the scenes are counted in increments. So, the first level to load will be the bootUp scene.

Now that we have added multiple scenes to our game, we can consider the fact that we may not want our camera and light setup methods in our GameManager method to run in every scene of our game. Let's briefly return to our GameManager script and update our LightSetup and CameraSetup methods, as well as a few other things.

Updating our GameManager script

In this section, we are going to return to the GameManager script and make it so that the CameraSetup and LightSetup methods are called when we are controlling our spaceship only.

To update our GameManager script to support various scenes for our lights and camera, we need to do the following:

  1. In the Unity Editor, navigate to Assets/Script from the Project window.
  2. In the GameManager script, scroll down to the Start function and remove the LightSetup(); and CameraSetup(); methods.
  3. Next, we will enter two static global variables at the top of the GameManager script with the rest of the global variables:

    public static int currentScene = 0;

    public static int gameLevelScene = 3;

    bool died = false;

    public bool Died

    {

    get {return died;}

    set {died = value;}

    }

currentScene is an integer that will keep the number of the current scene we are on, which we will use in the following method. The second variable, gameLevelScene, will hold the first level we play, which we will use later on in this chapter.

  1. Still in the GameManager script, create an Awake function and enter the following code:

    void Awake()

    {

        CheckGameManagerIsInTheScene();

        currentScene = UnityEngine.SceneManagement.SceneManager.

            GetActiveScene().buildIndex;

        LightAndCameraSetup(currentScene);

    }

In the code we just entered, we store the buildIndex number (the numbers we have to the right of each scene in our Build Settings window from the previous section) in the currentScene variable. We then send the currentScene value to our new LightandCameraSetup method.

  1. The last piece of code to add to our GameManager script is the LightandCameraSetup method, which takes an integer parameter:

      void LightAndCameraSetup(int sceneNumber)

      {

        switch (sceneNumber)

        {

          //testLevel, Level1, Level2, Level3

          case 3 : case 4 :case 5: case 6:

          {

            LightSetup();

            CameraSetup();

            break;

          }

        }

      }

In the code we just wrote, we ran a switch statement to check the value of the sceneNumber variable, and if it falls into the 3, 4, 5, or 6 values, we run LightSetup and CameraSetup.

  1. Save the GameManager script.

To reflect on this section, we have created a structure of empty scenes that will each serve a purpose in our game. We have also created a ScenesManager script that will either reset a scene when the player wins or dies and/or move to the game over scene.

Now that we have our scenes in place and the start of the ScenesManager script has been built, we can focus on the player's life system.

Creating lives for the player

In this section, we are going to make it so that the player has a set number of lives. If and when the player collides with an enemy, the player will die, the scene will reset, and a life will be deducted from the player. When all the lives are gone, we will introduce the game over scene.

We will be working with the following scripts in this section:

  • GameManager
  • SceneManager
  • Player

Let's start by revisiting the GameManager script and setting up the ability to give and take the player's lives:

  1. Open the GameManager script and enter the following code:

    public static int playerLives = 3;

At the top of the script, just after entering the class and inheritance, enter a static (meaning only one) integer type labeled playerLives, along with the value 3.

Next, we need to create a new method for our GameManager script that will ensure the player loses a life. After we make this new method, the Player script will call it when it makes contact with an enemy.

Let's continue with our GameManager script.

  1. To create the LifeLost method, enter the following code in our GameManager class:

    public void LifeLost()

    {

We need this to be a public method so that it can be accessed from outside of the script. It's set to void, meaning nothing is returned from the method, and it's followed by the name of the method with empty brackets as it isn't taking any arguments.

  1. So, within the LifeLost() method, we will check the player's lives with an if statement with the following code:

        //lose life

        if (playerLives >= 1)

        {

          playerLives--;

          Debug.Log("Lives left: "+playerLives);

          GetComponent<ScenesManager>().ResetScene();

        }

After reviewing the if statement code we have entered, we will make a start by adding a comment to let ourselves or other developers know what this condition is doing (//lose life). We will then add the if statement condition, checking whether the player has more than or equal to one life left. If the player does have one or more lives left, we will deduct the player's lives by 1 with the -- operator, which is just a quicker way of saying playerLives = playerLives - 1;.

The line of code following on from the deduction of the player's lives isn't required, but it will notify us, in the Unity Editor Console window, with an information box telling us how many lives the player has left (for debugging purposes), as shown in the following screenshot:

Figure 3.6 – Console window displaying how many lives the player has left

Figure 3.6 – Console window displaying how many lives the player has left

Following on from displaying how many lives the player has left in the Console window, we will refer to the ScenesManager script, which is attached to the GameManager game object. We can use GetComponent to access the ScenesManager script's ResetScene method, which will reset our scene.

  1. We will now enter the else condition, which indicates that the player has died:

        else

        {

          playerLives = 3;

          GetComponent<ScenesManager>().GameOver();

        }

    }

If our player doesn't have any more lives left, that means the if statement condition isn't met, so we can then offer an else condition. Within the scope of our else statement, we reset our player's lives back to 3.

We then access the GameOver() method from the ScenesManager class, which will take us from the scene we are on over to the gameOver scene.

Lastly, all that we need to do now is to make our Player script call the LifeLost method when the player has collided with the enemy or the enemy's bullets:

  1. Save the GameManager script.
  2. From the Project window, navigate to the Player script (Assets/Script).
  3. Scroll down to its Die method.
  4. Starting from above the destroy line (Destroy(this.gameObject);), enter the following code:

    GameManager.Instance.LifeLost();

Note that we can call the GameManager script directly without finding the game object in the scene by using code such as GetComponent to acquire a script. This is the power of using the Singleton design pattern, calling directly to the LifeLost method.

  1. Save the Player script.
  2. Press Play in the Unity Editor and collide with an enemy.

The level should reset with a message in the Console window showing that we have a particular number of lives left. Repeat this three more times. When the third life is lost, our scene should change from testLevel to gameOver.

The following screenshot shows the Console window tab selected and logging the lives that are lost; also, above the Console section is the Hierarchy window, showing that our game has gone from testLevel to the gameOver scene:

Figure 3.7 – Players lives being depleted and the gameOver scene being loaded

Figure 3.7 – Players lives being depleted and the gameOver scene being loaded

With minimal code, we have now made it so that our player has a number of lives. We have introduced a ScenesManager script into our game framework that talks directly to GameManager, regardless of restarting and changing scenes.

As a side note, you might have noticed that when we changed to the gameOver scene, our GameManager game object was carried over into the gameOver scene. If you recall the Adding a Singleton design pattern section, we set up the CheckGameManagerIsInTheScene method, which is called in the Awake function. This means that just because we are in a different scene, it doesn't mean the Awake function is called again.

Information

Remember, the Awake function will only run when the script is active and will only run once, even if the script is attached to a game object and is carried through scenes.

This is because our gameOver scene only carried the GameManager game object over to the gameOver scene. It wasn't activated, which means the Awake function wasn't called.

We have our basic lives and scene structure, and we have also used the Console window to help us acknowledge the changes.

Before we move on, you may notice that when the player dies, the lights get darker in the scene. The following screenshot shows what I mean:

Figure 3.8 – Lights have darkened in our game

Figure 3.8 – Lights have darkened in our game

As you can see in the previous screenshot, on the left is the scene we start with, and on the right is the scene when the player has died. To fix this, we just need to make it so that we generate our lighting manually instead of it being autogenerated by Unity.

To prevent our lighting from going dark between scenes, we need to do the following:

  1. At the top of the Unity Editor, click on Window | Lighting | Settings.
  2. The Lighting Settings window will appear. At the bottom of the window, uncheck Auto Generate and click on the button next to it, Generate Lighting. Use the following screenshot for reference:
Figure 3.9 – Unchecked Auto Generate box and the Generate Lighting button

Figure 3.9 – Unchecked Auto Generate box and the Generate Lighting button

  1. This will take a minute as Unity will be setting up the new light settings. Once this is done, save the Unity project and that should fix it.

Note that we will likely need to set the lighting manually for other scenes, such as the other levels and the shop scene, later on in this book.

Let's now turn our focus to the enemy and add some functionality so that when it is destroyed by the player, we can add a score to ScoreManager, which is a new script that we will be making next.

Scoring enemy hits

As with most games, we need a scoring system to show how well the player has done at the end of the game. Typically, with side-scrolling shooter games, the player is rewarded for each kill they make. If we turn to our game framework diagram, we can see that ScoreManager is hooked up to GameManager like ScenesManager was:

Figure 3.10 – Killer Wave UML

Figure 3.10 – Killer Wave UML

Our code for adding a scoring system will once again be minimal. We also want flexibility so that different enemies are worth different points. We also want it so that when we add another enemy to our game with a different scoring point, we can avoid altering our code each time.

We will be working with the following scripts in this section:

  • EnemyWave
  • ScoreManager
  • ScenesManager
  • SOActorModel

Since the scoring system is an integral factor in our game, it would make sense to add a simple integer to SOActorModel that injects common values into our game objects. This trend will then follow on to other scripts. Let's start adding some code to our already-made scripts before we introduce ScoreManager.

Preparing the code for the ScoreManager script

If you recall Chapter 1, Setting Up and Structuring Our Project, we spoke about the SOLID principles and how important it is to add to our code rather than change it, or we risk errors and our code may start mutating and eventually become unfit for purpose. In order to prepare, we will add code to the scripts that we have already made to fit our ScoreManager script into place. Let's start with SOActorModel first. Follow these steps:

  1. Open the SOActorModel script from the Project window.
  2. Anywhere within our list of variables in the SOActorModel script, add the following code, which will be used to contain the enemy's score:

    public int score;

  3. Save the SOActorModel script.

Before we add more code to the other scripts to fit ScoreManager into our game, we need to acknowledge that we have made a change to our ScriptableObject template.

Let's check our BasicWave Enemy scriptable object in the Unity Editor. Follow these steps:

  1. From the Project window, navigate to the Assets/ScriptableObject folder.
  2. Click once on BasicWave Enemy and you will see that the Inspector window has a Score input field.
  3. Give the Score field of BasicWave Enemy a value of your choice. I'm giving it a value of 200. It really doesn't matter what value you give it as long as it's more than 0. The following screenshot shows the BasicWave Enemy section with its updated Score value:
Figure 3.11 – enemy_wave score property and value added

Figure 3.11 – enemy_wave score property and value added

We have updated the BasicWave Enemy scriptable object. We now need to focus on the EnemyWave script to create and receive this new variable.

  1. Open the EnemyWave script.
  2. At the top of the script, where we have our health, travelSpeed, and other global variables, add an extra variable to the list:

    int score;

We now need to update the score variable from the ScriptableObject value.

  1. In the EnemyWave script, scroll down until you find the ActorStats method, then add the following extra line of code:

    score = actorModel.score;

The EnemyWave script now has a score variable that is set from the value given to it by SOActorModel. The last thing we need to do is send the score value to ScoreManager when the enemy dies due to the actions of the player. Before we do that, let's create and code our ScoreManager script.

Setting up our ScoreManager script

The purpose of the ScoreManager script is to total up the score of the player during their game, concluding when they arrive at the gameOver scene. We could also give the ScoreManager script other score-related functionality, such as the ability to store our score data on the device that we are playing the game on or to send the score data to a server for an online scoreboard. For now, we will keep things simple and just collect the player's score.

We can create and add our ScoreManager script to the game framework, as follows:

  1. Create and attach a script called ScoreManager to the GameManager game object, similar to how we did with ScenesManager.

If you can't remember how to do this, then check out the Setting up our ScenesManager script section of this chapter. The following screenshot shows ScoreManager attached to the GameManager game object in the Inspector window:

Figure 3.12 – ScoreManager script added to the GameManager game object

Figure 3.12 – ScoreManager script added to the GameManager game object

Next, we are going to open the ScoreManager script and add code that will hold and send score data. Open the ScoreManager script and enter the following code:

using UnityEngine;

By default, we require the UnityEngine library, as previously mentioned.

  1. Continue by checking and entering the name of the class:

    public class ScoreManager : MonoBehaviour

    {

This is a public class, with ScoreManager inheriting MonoBehaviour to increase the functionality of the script.

  1. Next, we add our variables and properties to our script. The only value we are concerned about is playerScore, which is private to the script (because we don't want other classes to have access). This variable is also set to static, meaning we don't need duplicate references for this variable.

Following on from this is our public property, which gives outside classes access to the playerScore variable. As you'll notice, the PlayerScore property returns an integer. Within this property, we use the get accessor to return our private playerScore integer. It is a good habit to keep our variables private, or you risk exposing your code to other classes, which can result in errors. The following code shows you how to complete this step:

    static int playerScore;

    public int PlayersScore

    {

        get

        {

            return playerScore;

        }

    }

Accessors

To find out more about accessors, check out https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/get.

  1. We will now move on to the SetScore method; it is public and doesn't return a value (void), with the SetScore name taking in an integer parameter named incomingScore. Within this method, we use incomingScore to add to the playerScore script (as its total score):

      public void SetScore(int incomingScore)

        {

            playerScore += incomingScore;

        }

  2. The last method to add is the ResetScore method. Enter the following code:

    public void ResetScore()

      {

        playerScore = 00000000;

      }

    }

We can call this method at the beginning or end of a game to stop the score from carrying on into the next game.

  1. Save the script.

As mentioned earlier, we can now return to the EnemyWave script to send the value of the enemy's score points to the ScoreManagers method, SetScore, thereby adding them to the player's total score:

  1. Open the EnemyWave script from the Project window and scroll down to the OnTriggerEnter Unity function.
  2. Within the scope of the if statement labeled if (health <= 0), enter the following line of code at the top of its scope:

    GameManager.Instance.GetComponent<ScoreManager>().SetScore(score);

When this particular enemy dies as a result of the player, this line of code will send the enemy's score value directly to the playerScore variable and increment it toward its total until the player loses all of their lives.

  1. Finally, to confirm the score has totaled correctly, let's do what we did before with the playerLives integer in the LifeLost method of the GameManager script and add a Debug.Log message to the Console window.
  2. In the ScenesManager script under the GameOver() method, add the following line of code at the top within its scope:

    Debug.Log("ENDSCORE: " +

      GameManager.Instance.GetComponent<ScoreManager>

         ().PlayersScore);

This code will tell us how much the player has scored because it directly accesses ScoreManager and grabs the PlayerScore property when the game is over. The following screenshot shows an example of a totaled score:

Figure 3.13 – Game over score value displayed in the Console window

Figure 3.13 – Game over score value displayed in the Console window

  1. Finally, save all the scripts.

In this section, we introduced the ScoreManager script with its basic working structure of totaling up our end score and displaying the final count in the Console window. We have also added more code to a selection of scripts without deleting and changing any of their content. Next, we will be doing something different that doesn't involve any coding but gets us more familiar with Unity's sound components.

Creating sounds for the player's bullets

Up until now, our game has been silent, but sound is an important factor in any game. In this section, we will be introducing our first sound component. We will make a start by creating sound effects for when our player fires a bullet.

Feel free to add your own type of bullet sound if you wish. You can add sound to your player's standard bullets as follows:

  1. In the Unity Editor, navigate to the Project window and create a new folder inside the Resources folder. Name the new folder Sound.
  2. Drag and drop the Player_Bullet prefab from the Project panel into the Hierarchy panel.
  3. With Player_Bullet still selected, click on the Add Component button in the Inspector panel.
  4. In its dropdown, start typing (and select) Audio Source.
  5. Drag and drop the PlayerLaser.mp3 file into the AudioClip section of the Audio Source component. The following screenshot shows Player_Bullet selected. The audio file at the bottom left needs to be dragged into the Audio Source component at the right:
Figure 3.14 – Adding sound file to the Player_Bullet's Audio Source game object in the 
Inspector window

Figure 3.14 – Adding sound file to the Player_Bullet's Audio Source game object in the Inspector window

  1. Play on Awake is automatically ticked. As you can imagine, as soon as Player_Bullet is instantiated, the sound will play.
  2. If the volume is too high, simply lower it in the Audio Source component of the Inspector window.

    Information

    As well as the Volume option in the Audio Source component, there is Pitch to change the sound of our bullet and Stereo Pan to make the sound more dominant in the left or right speaker. Finally, because this is a two-dimensional game, we don't want the sound to be affected by how close our camera is to the bullet. So, we slide the Spatial Blend toggle all the way to the left to make sure it is not affected by its distance.

  3. Finally, click on Overrides | Apply All in the top-right corner to save and update the Player_Bullet prefab and remove the bullet from the Hierarchy window.
  4. Play the scene and start firing. You will hear laser noises, and, in the scene view, you will see speaker symbols now attached to the player's bullets.

That brings us to the end of this short section on audio, but we will cover more on audio throughout this book. Don't forget that if you get stuck at any point, check the Complete folder for this chapter and compare the scenes and code to make sure nothing is missing.

Summary

In this chapter, we have extended our game framework structure by implementing and reinforcing the GameManager script by extending its code. This means that it will never be deleted, regardless of scene changes. We have also introduced the score and scenes managers, which were originally planned in our game framework. These two additional managers take responsibility away from the game manager and add additional features to your game. We ensured these scripts don't mutilate our original code (removing, overflowing, or compensating for our game manager). Your game now has a working scoring system, as well as multiple scenes that can be restarted and changed with very little code. We also introduced sound, which we'll implement in more detail in later chapters.

In the next chapter, we'll focus less on code-heavy content and instead concern ourselves with the art of the game. Even though we are programmers, we need to understand how to manipulate assets and how to animate with Unity's API. With just a little bit of coding, this will allow us to understand the connection between the Editor and our script. We'll also touch on some particle effects.

Well done—you've done and covered a lot. Before we move on, have a go at the following questions. They resemble what you will encounter in your programmer exam.

Mock test

This is your first mini mock test. These tests represent sections of your final Unity exam. This first mini mock test consists of just five questions. Later on in this book, we'll introduce more mini mock tests with more questions.

Fortunately, you will only be tested on what we have covered so far:

  1. You have been asked to develop a horror survival game where your player relies on a pocket torch. Here is what you've coded so far:

    void Start()

    {

        Light playersTorch = GetComponent<Light>();

        playersTorch.lightMapBakeType = LightMapBakeType.    Mixed;

        playersTorch.type = LightType.Area;

        playersTorch.shadows = LightShadows.Soft;

        playersTorch.range = 5f;

    }

You notice, however, that the player's torch isn't casting any light or shadows. What should you change for this code to work as desired?

  1. Set playersTorch.lightBakeType to LightmapBakeType.Realtime.
  2. Set playersTorch.range to 10.
  3. Set playersTorch.shadows to LightShadows.Hard.
  4. Set playersTorch.type to LightType.Point.
  1. You have started creating your first indie game, Super Moped Racer 64. You have coded your input controls to work with a joystick and started testing your moped around corners. You've noticed that after taking the moped around the first corner, the moped continues turning even after you've let go of the joystick.

You've checked your code and the joystick and both seem to be working fine, suggesting the issue is with the input manager.

What change should you make within the input manager?

  1. Increase the gravity.
  2. Set Snap to true.
  3. Increase Deadzone.
  4. Decrease Sensitivity.
  1. You have started to template a game framework with pen and paper. You have drawn up several manager scripts that will all lead to the creation of a single GameManager script. You only require one GameManager script, which will always be in your scene.

Which design pattern suits having a GameManager script in a persistent instance role?

  1. Prototype
  2. Abstract Factory
  3. Singleton
  4. Builder
  1. You have been requested to create a prototype for a side-scrolling game where your player throws rocks at their enemies. The game works well and the camera moves from left to right until the level is over. To throw a rock, your code instantiates a prefab of a rock, which is then given a force (Rigidbody.AddForce) to launch the rock to give the illusion of the rock being thrown.

Your lead developer says that your method is costing too much in-memory performance and wants you to store a maximum of 10 rocks from within an array of rocks using a design pattern. Once a rock is used, instead of being destroyed, it should return to the array.

What design pattern is the developer referring to?

  1. Abstract Factory
  2. Object Pool
  3. Dependency Injection
  4. Builder

That's the end of your first mini mock test. To check your answers, refer to the Appendix section at the back of this book. How did you do? To review any incorrect answers, I suggest flicking back through the last couple of chapters to the relevant section and refreshing your memory where needed. Sadly, exams can be a bit of a memory game. Everyone's memory is different, and the majority of people that pass these exams have failed on certain sections before passing.

Either way, the more you complete these tests, the stronger you will become at them. Just stay focused and you'll get through it!

..................Content has been hidden....................

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