Creating a timer
Before we create the timer script, we'll create the object to which we'll attach the script:
- Create an empty game object by selecting GameObject | Create Empty.
- Name the new object
LevelTimer
:
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:
- Click in the Hierarchy search box and type
t:timer
. - 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: - 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.
- 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 aspublic 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 theSerializeField
attribute. Private variables prefixed with this attribute, such as theCountDown
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 fromMonoBehaviour
.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 theCountDown
class, it'll be used to keep track of time as it passes. More information on theUpdate
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
andLateUpdate
.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 theLateUpdate
call will always happen after every object has received anUpdate
event. There are numerous reasons why we would want to perform an action afterUpdate
, 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 thedeltaTime
will be0.5
. This is because, each second, there would be two frames, and thus each frame would be half a second. ThedeltaTime
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, thedeltaTime
variable is used in theUpdate
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'sbuildIndex
usingSceneManager.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.
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:
- Navigate to the Standard Assets | ParticleSystems | Prefabs folder.
- Drag and drop the Fireworks particle system in the Scene.
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:
- Select the Particle System object in the Scene.
- Disable the Play On Awake checkbox on the Particle System component in the Inspector:
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:
- Select the Fireworks object in the Hierarchy panel.
- In the Inspector, at the top, select the Tag drop-down menu.
- Select Add Tag…:
- Click the plus sign under the Tags heading.
- Type the name
Fireworks
and click Save:With the Tag created, we can assign it to the Fireworks object
- Once again, select the Fireworks object in the Hierarchy.
- You'll see our custom Tag in the Tag menu; select it:
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, anif
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 namedLevelTimer
. 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, useFindGameObjectsWithTag
.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, theOnDestroy
function finds all fireworks in the scene, gets theirParticleSystem
component, and plays the particle animation. It finds all objects using theGameObject.FindGameObjectsWithTag
function, which returns an array of objects with the Fireworks Tag. TheGetComponent
function is used to retrieve a reference to any specified component, giving you direct access to its public properties and methods. TheOnDestroy
function in the preceding code usesGetComponent
to retrieve a reference to theParticleSystem
component attached to the object.GetComponent
is an important function, which you'll often use as a Unity developer. More information onGetComponent
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.