Book Image

Unity 2020 By Example - Third Edition

By : Robert Wells
Book Image

Unity 2020 By Example - Third Edition

By: Robert Wells

Overview of this book

The Unity game engine, used by millions of developers around the world, is popular thanks to its features that enable you to create games and 3D apps for desktop and mobile platforms in no time. With Unity 2020, this state-of-the-art game engine introduces enhancements in Unity tooling, editor, and workflow, among many other additions. The third edition of this Unity book is updated to the new features in Unity 2020 and modern game development practices. Once you’ve quickly got to grips with the fundamentals of Unity game development, you’ll create a collection, a twin-stick shooter, and a 2D adventure game. You’ll then explore advanced topics such as machine learning, virtual reality, and augmented reality by building complete projects using the latest game tool kit. As you implement concepts in practice, this book will ensure that you come away with a clear understanding of Unity game development. By the end of the book, you'll have a firm foundation in Unity development using C#, which can be applied to other engines and programming languages. You'll also be able to create several real-world projects to add to your professional game development portfolio.
Table of Contents (16 chapters)

Creating a timer

Before we create the timer script, we'll create the object to which we'll attach the script:

  1. Create an empty game object by selecting GameObject | Create Empty.
  2. Name the new object LevelTimer:
Figure 2.27 – Renaming the timer object

Figure 2.27 – Renaming the timer object

Important note

Remember that the player cannot see empty game objects because they have no Mesh Renderer component. Invisible objects are especially useful to create functionality and behaviors that don't correspond directly to physical and visible entities, such as timers, managers, and game logic controllers.

Next, create a new script file named Timer.cs and add it to the LevelTimer object in the scene. Make sure that the timer script is only added to one object, and no more than one. Otherwise, there will effectively be multiple, competing timers in the same scene. You can always search a scene to find all components of a specified type by using the Hierarchy panel. To do this, take the following steps:

  1. Click in the Hierarchy search box and type t:timer.
  2. Press Enter on the keyboard to confirm the search. This will display all objects with a Timer component attached in the Hierarchy panel. The t prefix in the search string indicates a search by type operation:
    Figure 2.28 – Searching for objects with a component of a matching type

    Figure 2.28 – Searching for objects with a component of a matching type

  3. You can easily cancel a search and return the Hierarchy panel to its original state by clicking on the small cross icon to the right-hand side of the search field, as shown in Figure 2.28.
  4. Now we've added the script to an object, we need to complete the script by writing the code for the timer:
    public class Timer : MonoBehaviour
    {
    	//Maximum time to complete level (in seconds)
    	public float MaxTime = 60f;
    	
    	[SerializeField] private float CountDown = 0;
    	
    	void Start () 
    	{
    		CountDown = MaxTime;
    	}
    	void Update () 
    	{
    		//Reduce time
    		CountDown -= Time.deltaTime;
    		//Restart level if time runs out
    		if(CountDown <= 0)
    		{
    			//Reset coin count
    			Coin.CoinCount=0;
    		      SceneManager.LoadScene(SceneManager.                   GetActiveScene().buildIndex);
    		}
    	}
    }

The following points summarize the code sample:

  • In Unity, class variables declared as public (such as public float MaxTime) are displayed as editable fields in the Inspector of the Unity editor. These fields enable developers to monitor and set public variables directly from the Inspector without the need to recompile the code for every change. Private variables, in contrast, are hidden from the Inspector by default. However, you can force them to be visible, if needed, using the SerializeField attribute. Private variables prefixed with this attribute, such as the CountDown variable, will be displayed in the Inspector just like a public variable, even though the variable's scope remains private.
  • The Update function is a native Unity event supported by all classes derived from MonoBehaviour. Update is invoked automatically once per frame for all active GameObjects in the scene, notifying them of frame change events. Update is usually called many times per second; the game's FPS is a general indicator of how many times each second, but the actual number of calls will vary in practice. Update is especially useful to animate, update, and change objects over time. In the case of the CountDown class, it'll be used to keep track of time as it passes. More information on the Update function can be found here: http://docs.unity3d.com/ScriptReference/MonoBehaviour.Update.html.

    Important note

    In addition to the Update function, Unity also supports two other related functions, namely: FixedUpdate and LateUpdate. FixedUpdate is typically used for physics calculations, as we'll see later, and is called a fixed number of times per frame. LateUpdate is called once per frame for each active object, but the LateUpdate call will always happen after every object has received an Update event. There are numerous reasons why we would want to perform an action after Update, and a few of them will be explored later in this book.

  • The static Time.deltaTime floating-point variable describes the amount of time (in seconds) that has passed since the previous frame ended. For example, if your game has a frame rate of 2 FPS (a very low frame rate!), then the deltaTime will be 0.5. This is because, each second, there would be two frames, and thus each frame would be half a second. The deltaTime is useful in our game because, if added over time, it tells us how much time has elapsed or passed since the game began. For this reason, the deltaTime variable is used in the Update function to subtract the elapsed time from the countdown total. More information can be found online at http://docs.unity3d.com/ScriptReference/Time-deltaTime.html.
  • The static SceneManager.LoadScene function can be called anywhere to change the active scene at runtime. It causes Unity to terminate the active scene, destroying all its contents, and load a new scene. It can also be used to restart the active scene by retrieving the current scene's buildIndex using SceneManager.GetActiveScene().buildIndex. SceneManager.LoadScene is most appropriate for games with clearly defined levels that have distinct beginnings and endings.

Once you have created the timer script, select the LevelTimer object in the scene, and, using the Inspector, set the maximum time (in seconds) that the player is allowed to complete the level, as shown in Figure 2.29. I've set the total time to 60 seconds. If the player takes longer than this to collect all of the coins, the level is reloaded.

Figure 2.29 – Setting the level's total time

Figure 2.29 – Setting the level's total time

Great work! You now have a completed level with a countdown that works. You can collect coins, and the timer can expire. Overall, the game is taking shape, but there is still no win condition. We'll address this now.

Creating a win condition

The coin collection game is nearly finished. Coins can be collected, and a timer expires, but the win condition itself is not yet handled. When all coins are collected before the time expires, nothing happens to show the player that they've won; the countdown still proceeds and the level even restarts as though the win condition hadn't been satisfied at all. When the win scenario happens, we should delete the timer object to prevent further countdown and show visual feedback to signify that the level has been completed. For the visual feedback, we'll add some fireworks!

Adding fireworks

You can add these from the Unity Particle System packages:

  1. Navigate to the Standard Assets | ParticleSystems | Prefabs folder.
  2. Drag and drop the Fireworks particle system in the Scene.
Figure 2.30 – Adding two Fireworks prefabs

Figure 2.30 – Adding two Fireworks prefabs

As you can see in Figure 2.30, I've added a couple of Fireworks prefabs to the scene. By default, all Fireworks particle systems will play when the level begins. You can test this by pressing play on the toolbar. We only want the fireworks to play when the win condition has been satisfied. To disable playback on level startup, do the following:

  1. Select the Particle System object in the Scene.
  2. Disable the Play On Awake checkbox on the Particle System component in the Inspector:
Figure 2.31 – Disabling Play On Awake

Figure 2.31 – Disabling Play On Awake

Disabling Play On Awake prevents particle systems from playing automatically at level startup. This is fine, but if they are ever to play at all, something must manually start them at the right time. We can achieve this through code. However, we'll first mark all firework objects with an appropriate Tag. The reason for this is that, in code, we'll want to search for all firework objects in the scene and trigger them to play when needed. To isolate the firework objects from all other objects, we'll use Tags. So, let's create a new Fireworks Tag and assign it to the Fireworks objects in the Scene:

  1. Select the Fireworks object in the Hierarchy panel.
  2. In the Inspector, at the top, select the Tag drop-down menu.
  3. Select Add Tag…:
    Figure 2.32 – Opening the Tags & Layers settings

    Figure 2.32 – Opening the Tags & Layers settings

  4. Click the plus sign under the Tags heading.
  5. Type the name Fireworks and click Save:
    Figure 2.33 – Adding a new Tag

    Figure 2.33 – Adding a new Tag

    With the Tag created, we can assign it to the Fireworks object

  6. Once again, select the Fireworks object in the Hierarchy.
  7. You'll see our custom Tag in the Tag menu; select it:
Figure 2.34 – Assigning the custom Tag

Figure 2.34 – Assigning the custom Tag

The firework object now has a custom Tag that we can use in our code to find the object in the scene, which is exactly what we'll do next to play the fireworks at the right moment.

Lighting the fireworks

With the firework objects now tagged, we can refine the Coin.cs script class to play the fireworks particle system when the player has collected all of the coins:

public class Coin : MonoBehaviour
{
	…
    void OnDestroy()
    {
        --Coin.CoinCount;
    if(Coin.CoinCount <= 0)
    {
        GameObject Timer = GameObject.Find("LevelTimer");         Destroy(Timer);
        GameObject[] FireworkSystems =           GameObject.FindGameObjectsWithTag("Fireworks");          if (FireworkSystems.Length <= 0) { return; }
        foreach(GameObject GO in FireworkSystems)
        { 
           GO.GetComponent<ParticleSystem>().Play();
        }
    }
}
}

Let's summarize the preceding code:

  • The OnDestroy function is called automatically whenever the GameObject is destroyed. In our game, this occurs when a coin is collected. In the function, an if statement is used to determine when all coins are collected (the win scenario).
  • When a win scenario happens, the GameObject.Find function is called to search the complete scene hierarchy for any active object named LevelTimer. If found, the object is deleted. This prevents the countdown from progressing. If the scene contains multiple objects with a matching name, then only the first object is returned. This is one reason why the scene should have one, and only one, timer.

    Tip

    Try to avoid using GameObject.Find wherever possible as it's relatively slow. Instead, use FindGameObjectsWithTag. GameObject.Find has been used here only to demonstrate its existence and purpose. Sometimes, you'll need to use it to find a single, miscellaneous object that has no specific Tag.

  • In addition to deleting the LevelTimer object, the OnDestroy function finds all fireworks in the scene, gets their ParticleSystem component, and plays the particle animation. It finds all objects using the GameObject.FindGameObjectsWithTag function, which returns an array of objects with the Fireworks Tag. The GetComponent function is used to retrieve a reference to any specified component, giving you direct access to its public properties and methods. The OnDestroy function in the preceding code uses GetComponent to retrieve a reference to the ParticleSystem component attached to the object. GetComponent is an important function, which you'll often use as a Unity developer. More information on GetComponent can be found online at https://docs.unity3d.com/ScriptReference/GameObject.GetComponent.html.

    Important note

    As mentioned, each GameObject in Unity is really made from a collection of attached and related components. An object is the sum of its components. For example, a standard cube (created using GameObject | 3D Object | Cube) is made from a Transform component, a Mesh Filter component, a Mesh Renderer component, and a Box Collider component. These components together make the cube what it is and behave how it does.

You've now completed your first game in Unity! It's time to take it for a test run and then finally to compile an executable that can be run from outside of Unity.