In this article we are going to see how to program a timer in C#, the operation will be counting backwards from the initial values of minutes and seconds that we indicate and in addition the Timer will be in charge of updating the information in the user interface created in the video 5 of the series of the labyrinth. Here is the article in which we created the user interface, in short we created some elements for the Canvas, which will allow the user to start playing and see information about the game.
Before we begin, I invite you to watch the video of this article.
The prototype is a first-person game in which the stage is a maze. Inside the maze, there will be a hidden object that must be found before time runs out.
Now we’re about to solve the part where time runs out. In this article we’ll create a script that is going to work as a countdown timer.
The Timer will start the countdown from a value of minutes and seconds that we will indicate in the inspector, this means that every second of clock we must update the information.
The Script Timer will also modify the value of the Canvas text element (top of figure 1).
When the time count reaches zero, the Script Timer will execute the EndGame method of the GameControl Script, this way the game will end and we will return to the main menu.
Step by Step
We start by creating the Timer Script and open it in the editor. In order to access to the UI components, like on-screen texts and buttons, we need to include the UnityEngine.UI namespace at the top of the script (figure 3).
To make the countdown timer work we’ll need a couple variables.
In figure 4 you can see a “[SerializeField]” instruction above certain variables, this simply makes the variables and fields to appear in the inspector.
The two serialized integers (minutes and seconds) are the initial values of the countdown timer.
While the two private integers (m and s) will be used to keep track of time.
We also need a Text type object to have the reference of the Text component of the Canvas.
Select the “Control” object from the hierarchy and assign the Timer Script. We see in the inspector the serialized fields of the Script Timer: minutes, seconds and timerText.
Let’s write one minute and five seconds for the initial values of the countdown timer. Then take the Timer GameObject from the hierarchy (the one that contains the Text component, figure 6) and drag it to the timerText field.
In figure 7 we can see that in the timerText field a Text type component has been recognized.
Methods to implement
Having in mind how a Countdown timer works, some ideas of possible methods emerge. First for the Timer to start working we’ll create a method called StartTimer(). Then another method that runs at intervals of a second that is going to take care of incrementing the time value, we’ll call it UpdateTimer(). A third method to stop the Timer, that will be executed when it finishes counting, this will be StopTimer().
Finally, a method that modifies what is being shown in the user interface, this method will be called WriteTimer() and will take as parameters two integers for the minutes and seconds.
Note that the parameters required by this method have exactly the same name as the two integers previously defined, which are in charge of keeping track of the time.
This is done so on purpose to talk a little about contexts in programming, probably for later in another article.
Programming the functions of the countdown timer
We have proposed some methods intuitively considering roughly the functioning of the Timer, now we must fill those methods with C# instructions that are the ones that will do the job.
The UpdateTimer method will take care of subtracting a second, checking if the time has not run out, and modifying the minutes accordingly. Finally, it will use the WriteTimer method, passing you the values of minutes and seconds. Figure 10 shows an example of how to implement this.
In the first instruction of the method we decrease by one unit the variable seconds, then we check if this variable is negative, i.e. if previously it was worth 0 and now it is worth -1.
If this happens we notice if the minutes variable is worth 0, if it is true this means that the time of the Timer is exhausted (I leave the comment //endGame in that region). If the minutes are not equal to zero, we decrease the minutes variable by one unit and make the seconds variable worth 59.
Finally we execute the WriteTimer method passing the variables m and s as parameters.
This method will take care of modifying the Text object that we had defined. Depending on whether the variable has a single digit (i.e. less than 10) or has two digits (greater than or equal to 10), we are going to execute the instructions seen in figure 11.
What is done is to concatenate a series of strings or text strings, in the case that s is less than 10, we concatenate a 0 in front of the seconds so that it conserves the format of two numbers.
There are other ways to do this such as setting a format for text strings, but in this way we take the opportunity to see how easily we can concatenate text using the sum operator.
This method will perform the initialization of the Timer, that is, we take the minutes and seconds variables that appeared in the inspector with the initial values and assign them to the variables m and s that carry the count.
The method to write the values in the graphical interface is executed.
Finally we use the Invoke method, giving as parameters the string “updateTimer”, which is the name of the method and the second parameter is 1f that represents a second. This will make the updateTimer method run after one second.
We are going to add this same instruction at the end of the updateTimer method, this way the method will invoke itself every second. This can also be done using the InvokeRepeating method instead of Invoke.
Interactions between Scripts
The Timer is like a device that fulfills its function, we can configure it, put it to run and stop it. With this in mind, we are going to make the GameControl Script the one that controls the Timer, being able to start and stop it using its public methods.
First we are going to define a Timer type object to use in the GameControl Script.
Then in the Start method we find the Timer reference that is assigned to the same GameObject Control hierarchy (hierarchy in figure 6).
In figure 14 we see underlined, the statement of this object Timer and how we find the reference in the Start method.
Now the GameControl Script has the reference of the Script Timer instance, this means that you will be able to access its methods and public fields using the dot operator.
In GameControl’s StartGame method we’re going to make the Timer start working.
At the same time, we also want the Timer to have some way of telling the GameControl Script that time is up, so we also need the reference to the GameControl object within the Timer Script.
In figure 16 we see the declaration of this object and how the reference is found within the Start method.
For now we are going to make that when the time runs out the game ends and we return to the main menu, that is to say the same thing that would happen if we press the Escape key. This can be done by running GameControl’s EndGame method.
In previous videos we had declared it with private visibility, which implies this method can not be executed from an external context, for example the Script Timer.
So that we can execute it from the Timer Script we change to public visibility. See figures 17 and 18.
Now if, in the UpdateTimer region where we detected that the account reaches zero (we had left the //endGame comment), we execute the EndGame method of the GameControl Script and then the return sentence to finish the execution of the UpdateTimer method.
In the EndGame method of the GameControl Script we are going to stop the Timer, as can be seen in figure 20.
Why didn’t we make the UpdateTimer method stop the Timer when the count reaches zero?
It could have been done in many different ways, at the time I chose to do so bearing in mind that the Timer only serves the function of keeping track of time and warn when the time runs out, then the GameControl Script that uses the Timer decides what to do with it.
In this method we will only cancel all invocations that may be pending, we could also reset the timer values to the initial values.
As an observation, in the code of figure 19, if the Timer reaches zero, the EndGame() method is executed and then the instruction return, which means that the program is forced to exit the current method, therefore updateTimer() will not be invoked again and the CancelInvoke() instruction would not be necessary.
But we could execute the EndGame method due to other reasons, for example pressing the Escape key or the player wins the game, in those cases it is necessary to cancel the UpdateTimer invocations.
I’m going to make sure it works as expected, first I’m going to set 0 minutes 5 seconds as initial values and enter the game mode.
When entering the game mode the Timer starts with 5 seconds, makes the countdown and when it reaches 0 we see in figure 24 that returns to the main menu.
Now we verify that the format of two numbers for seconds is respected and that the minutes are correctly decremented when the seconds go from zero to 59. We start the Timer with 10 minutes 5 seconds and enter the game mode.
We observe that the format of two digits is respected for the seconds and in addition the minutes are decremented correctly. The exercise is finished.
We have managed to complete our Timer in C#, which serves the function of keeping track of time, displaying it in the user interface and when the time runs out notify the GameControl Script to decide what to do.
This timer could have been implemented in several different ways, the Invoke method was chosen in this case, because I considered a quite simple alternative.
We saw how easily we can modify a text component of the Canvas as long as we have its reference, so we can use this for other things like scoring, information messages, etc..
We began to introduce little by little the concept of objects, how each has a specific function and have references to other objects to make use of their methods and public parameters. This is an extremely interesting and useful topic for creating object-oriented solutions.