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:
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:
The following are the core exam skills that will be covered in this chapter:
Programming core interactions:
Working in the art pipeline:
Programming for scene and environment design:
The project content for this chapter can be found at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition/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.
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:
Now that our script has been created and attached, we can go into it and start setting up the following code:
Let's take a look.
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:
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.
public class PlayerTransition : MonoBehaviour {
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.
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).
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.
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:
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.
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.
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:
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.
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.
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:
We will be covering two new Unity functions (Mathf.Round and Vector3.Lerp).
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:
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:
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.
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 (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:
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:
playerShip.transform.position = Vector3.zero;
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.
GetComponentInChildren<Player>().enabled = true;
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.
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:
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.
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:
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.
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:
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.
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.
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:
Let's get started.
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:
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.
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:
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.
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.
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.
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
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:
UnityEngine.SceneManagement.SceneManager.LoadScene("testLevel");
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:
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).
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:
We now need to check the Build Settings window to check on the order of our scenes.
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:
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:
Double-click on the GameManager script.
case 3 : case 4 : case 5:
Each case represents the levels the player is going to play.
In the next few sections, we will be customizing a placeholder look for each nonlevel scene (basic but informative). These scenes are as follows:
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.
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:
The following screenshot shows the components on the left-hand side of the Hierarchy window. These are as follows:
On the right-hand side of the preceding screenshot, we have our GameManager game object selected showing its three main component scripts:
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:
Use the following screenshot as a reference for the location for Clear Flags, Background, and Hex Color:
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:
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:
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.
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.
The following screenshot shows what the BootUpComponent game object should look like when it's selected in the Hierarchy window:
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:
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.
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); } }}
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.
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:
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.
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.
The following screenshot shows what the title scene should look like in the Unity Editor:
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:
The following screenshot shows the GameOver component with the same LoadSceneComponent script where I added "title" to the loadThisScene variable field:
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.
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:
The following image shows the process of our game loop moving through each scene, then going back to the title scene:
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.
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.
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?