#7 Timer para cuenta regresiva en Unity

Por GameDevTraum

Introducción

En este artículo vamos a ver cómo hacer un timer en Unity, el funcionamiento será contar hacia atrás desde los valores iniciales de minutos y segundos que indiquemos y además el Timer se encargará de actualizar la información en la interfaz de usuario creada en el video 5 de la serie del laberinto.

En el artículo en el que creamos la interfaz de usuario, lo que hicimos fue crear algunos elementos para el Canvas, que permitirán al usuario comenzar a jugar y ver información sobre el juego.

Ir a la página principal del proyecto

Antes de comenzar te invito a ver el video de este artículo.

Descripción del objetivo

El prototipo es un juego en primera persona, en el cual el escenario es un laberinto. Dentro del laberinto habrá un objeto escondido que se debe encontrar antes de que se acabe el tiempo.

Ahora vamos a resolver la parte en la que el tiempo se acaba. Crearemos un Script que se encargará de llevar la cuenta del tiempo que transcurre.

El Timer comenzará la cuenta atrás desde un valor de minutos y segundos que indicaremos en el inspector, esto quiere decir que cada segundo de reloj debemos actualizar la información.

El Script Timer además se encargará de modificar el valor del elemento texto del Canvas (parte superior en figura 1).

Cuando la cuenta del tiempo llegue a cero, el Script Timer ejecutará el método EndGame del Script GameControl, de esta forma el juego terminará y regresaremos al menú principal.

escena en unity, en la parte superior un objeto texto que mostrara el tiempo
Fig. 1: Se busca controlar el texto de la parte superior para que muestre el tiempo restante.

Resolución

Comenzamos creando el Script Timer y lo abrimos en el editor. Vamos a asegurarnos de incluir el namespace UnityEngine.UI en la parte superior del Script (figura 3). De esta forma podremos acceder a los objetos y métodos para implementar una interfaz de usuario.

nuevo script c# llamado timer en unity
Fig. 2: Se crea un nuevo Script C# de nombre Timer.

paquetes importados en el script timer
Fig. 3: Nos aseguramos de importar UnityEngine.UI para acceder a los métodos de la interfaz de usuario.

Campos y variables

Para resolver la funcionalidad del Timer en c# necesitamos contar con una serie de variables.

En la figura 4 vemos que se utiliza [SerializeField], esto simplemente hará que podamos ver y editar estos campos en el inspector.

Los dos enteros serializados (minutes y seconds) son los valores iniciales en los que empezará la cuenta regresiva.

Mientras que los dos enteros privados (m y s) los usaremos para llevar la cuenta del tiempo.

campos definidos en script timer
Fig. 4: Definimos algunos campos para que el timer pueda cumplir su función.

Además necesitamos un objeto tipo Text para guardar la referencia de la componente Text del Canvas que tenemos que modificar.

Seleccionamos el objeto Control de la jerarquía y le asignamos el Script Timer. Podemos observar los campos serializados del Script Timer, minutes, seconds y timerText.

unity ventana inspector mostrando componentes de gameobjects
Fig. 5: Asignamos el Script Timer al GameObject Control de la jerarquía.

jerarquia de un proyecto en unity
Fig. 6: Tomamos el GameObject Timer y lo llevamos al campo TimerText de la figura anterior.

Vamos a escribir un minuto y cinco segundos para el inicio de la cuenta regresiva. Luego tomamos el GameObject Timer de la jerarquía (que contiene una componente Text, figura 6) y lo arrastramos al campo timerText.

unity ventana inspector mostrando componentes de gameobjects
Fig. 7: Escribimos valores para inicializar las variables.

En la figura 7 observamos que en el campo timerText se ha reconocido una componente tipo Text.

Métodos a implementar

Teniendo en mente cómo funciona un Timer, surgen algunas ideas de posibles métodos. Primero para que el Timer comience a funcionar ejecutaremos un método llamado StartTimer. Luego otro método que se ejecute en intervalos de a un segundo que es el que se va a encargar de los cálculos, lo llamaremos UpdateTimer. Un tercer método para detener el Timer, que se ejecutará cuando este termine de contar, este será StopTimer.

como hacer un timer en unity con invoke
Fig. 8: Eliminamos el método Update y creamos nuevos métodos para resolver el problema.

Por último un método que se encargue de modificar lo que se está mostrando en la interfaz de usuario, este método se llamará WriteTimer y tomará como parámetros dos enteros para los minutos y segundos que debe escribir en la interfaz gráfica.

Si no estás muy familiarizado con qué es un método en programación en mi canal hay un video que podría ser útil, también escribí un artículo para la sección de aprender a programar.

metodo en c# para escribir un timer
Fig. 9: Agregamos también un método para escribir el Timer.

Noten que los parámetros que requiere este método tienen exactamente el mismo nombre que los dos enteros definidos previamente, que se encargan de llevar la cuenta del Timer.

Esto está hecho así a propósito para hablar un poco sobre contextos, probablemente para más adelante en otro artículo.

Instrucciones para los métodos

Método UpdateTimer

Hemos propuesto algunos métodos de manera intuitiva considerando a grandes rasgos el funcionamiento del Timer, ahora debemos rellenar esos métodos con instrucciones C# que son las que harán el trabajo.

El método UpdateTimer se encargará de restar un segundo, chequear si el tiempo no se agotó, y modificar los minutos apropiadamente. Finalmente utilizará el método WriteTimer pasándole los valores de minutos y segundos. En la figura 10 vemos un ejemplo de cómo implementar esto.

como hacer un timer en unity con invoke
Fig. 10: Instrucciones correspondientes al método UpdateTimer.

En la primera instrucción del método disminuimos en una unidad la variable segundos, luego chequeamos si esta variable es negativa, es decir si previamente valía 0 y ahora vale -1.

Si esto pasa nos fijamos si la variable de los minutos vale 0, si es verdadero esto significa que el tiempo del Timer se agotó (dejo el comentario //endGame en esa región). Si los minutos no son iguales a cero, disminuimos en una unidad la variable minutos y hacemos que la variable segundos valga 59.

Finalmente ejecutamos el método WriteTimer pasando las variables m y s como parámetros.

Método WriteTimer

Este método se va a encargar de modificar el objecto Text que habíamos definido. Dependiendo de si la variable tiene un solo dígito (es decir es menor a 10) o tiene dos dígitos (mayor o igual a 10), vamos a ejecutar las instrucciones que se ven en la figura 11.

Lo que se hace es concatenar una serie de strings o cadenas de texto, en el caso de que s sea menor a 10, concatenamos un 0 delante de los segundos para que conserve el formato de dos números.

Hay otras formas de hacer esto como establecer un formato para las cadenas de texto, pero de esta forma aprovechamos para ver cómo fácilmente podemos concatenar texto usando el operador suma.

seria de instrucciones en c# para refrescar el valor de un timer
Fig. 11: Instrucciones correspondientes al método WriteTimer.

Método StartTimer

Este método realizará la inicialización del Timer, es decir tomamos las variables de minutos y segundo que aparecían en el inspector con los valores iniciales y las asignamos a las variables m y s que llevan la cuenta.

Se ejecuta el método para escribir los valores en la interfaz gráfica.

Finalmente utilizamos el método Invoke, dando como parámetros el string “updateTimer”, el cual es el nombre del método y el segundo parámetro es 1f que representa un segundo. Esto hará que el método updateTimer se ejecute luego de un segundo.

seria de instrucciones en c# para iniciar un timer
Fig. 12: Instrucciones correspondientes al método StartTimer. Invocación de updateTimer.

Vamos a agregar esta misma instrucción al final del método updateTimer, de esta forma el método se va a auto invocar cada un segundo. Esto también se puede hacer utilizando el método InvokeRepeating en lugar de Invoke.

como hacer un timer en unity con invoke
Fig. 13: El método updateTimer se invoca a sí mismo cada un segundo.

Para ver más sobre el método Invoke te invito a leer el artículo de la serie fundamental de Unity en el que hacemos que una máquina expendedora nos entregue una lata luego de un tiempo, también puedes ver el video aquí.

Interacciones entre Scripts

El Timer es como un aparato que cumple su función, podemos configurarlo, ponerlo a correr y detenerlo. Teniendo esto en mente vamos a hacer que el Script GameControl sea el que controle el Timer, pudiendo iniciarlo y detenerlo utilizando sus métodos públicos.

Primero vamos a definir un objeto tipo Timer para usar en el Script GameControl.

Luego en el método Start encontramos la referencia de Timer que está asignado al mismo GameObject Control de la jerarquía (jerarquía en figura 6).

script para controlar el juego
Fig. 14: Agregamos un objeto tipo Timer a los campos del Script GameControl. Encontramos referencia en método Start.

En la figura 14 vemos subrayadas, la declaración de este objeto Timer y cómo encontramos la referencia en el método Start.

Ahora el Script GameControl tiene la referencia de la instancia del Script Timer, esto significa que podrá acceder a sus métodos y campos públicos usando el operador punto.

En el método StartGame de GameControl vamos a hacer que el Timer comience a trabajar.

metodo start game del script que controla el juego
Fig. 15: En el método startGame ejecutamos el método startTimer de Timer.

Paralelamente, también queremos que el Timer tenga alguna manera de avisarle al Script GameControl que se acabó el tiempo, por lo tanto también necesitamos la referencia del objeto GameControl dentro del Script Timer.

En la figura 16 vemos la declaración de este objeto y cómo se encuentra la referencia dentro del método Start.

fragmento del timer en c#
Fig. 16: Agregamos un objeto tipo GameControl en Timer y encontramos la referencia en Start.

Por ahora vamos a hacer que cuando se acabe el tiempo el juego termine y regresemos al menú principal, es decir lo mismo que ocurriría si apretamos la tecla Escape. Esto podemos lograrlo ejecutando el método EndGame de GameControl.

En videos anteriores lo habíamos declarado con visibilidad privada, lo que implica este método no puede ser ejecutado desde un contexto externo, por ejemplo el Script Timer.

Para que podamos ejecutarlo desde el Script Timer cambiamos a visibilidad pública. Ver figuras 17 y 18.

visibilidad privada de un metodo, no accesible desde otro contexto
Fig. 17: Observamos que la visibilidad del método EndGame es privada, por eso no podemos ejecutarlo desde Timer.

visibilidad publica de un metodo, accesible desde otro contexto
Fig. 18: Cambiamos la visibilidad a público para que sea accesible desde otro contexto.

Ahora si, en la región de UpdateTimer en la que detectamos que la cuenta llega a cero (habíamos dejado el comentario //endGame), ejecutamos el método EndGame del Script GameControl y luego la sentencia return para terminar la ejecución del método UpdateTimer.

como hacer un timer en unity con invoke
Fig. 19: Identificamos la región del código en la que se ha agotado el tiempo y ejecutamos el método EndGame.

En el método EndGame del Script GameControl vamos a detener el Timer, como se observa en la figura 20.

¿Por qué no hicimos que el método UpdateTimer produzca la detención del Timer cuando la cuenta llega a cero?

Se pudo haber hecho de muchas formas distintas, en su momento opté por hacerlo así teniendo en mente que el Timer solo cumple la función de llevar la cuenta del tiempo y avisar cuando el tiempo se acaba, luego el Script GameControl que utiliza el Timer decide qué hacer con él.

metodo c# unity que se encarga de detener el juego
Fig. 20: En el método EndGame ejecutamos la acción para detener el Timer.

Método StopTimer

En este método sólo vamos a cancelar todas las invocaciones que puedan estar pendientes, también podríamos resetear los valores del temporizador a los valores iniciales.

Como observación, en el código de la figura 19 si el Timer llega a cero, se ejecuta el método EndGame y posteriormente return, quiere decir que no hay ninguna invocación de updateTimer pendiente y en ese caso la instrucción CancelInvoke() no sería necesaria.

Pero podría ser que ejecutemos el método EndGame debido a otras razones, por ejemplo apretar la tecla Escape o el jugador gana la partida, en ese caso si es necesario cancelar las invocaciones de UpdateTimer.

metodo para detener un timer en c#
Fig. 21: En el método StopTimer cancelamos las invocaciones que puedan estar pendientes.

Pruebas finales

Voy a asegurarme de que funcione como se espera, primero voy a colocar como valores iniciales 0 minutos 5 segundos y entro en el modo juego.

unity ventana inspector mostrando componentes de gameobjects
Fig. 22: Configuro los valores iniciales para observar rápidamente los resultados.

Al entrar en el modo juego el Timer comienza con 5 segundos, hace la cuenta regresiva y al llegar a 0 vemos en la figura 24 que vuelve al menú principal.

escena en unity que funciona como menu principal, boton para empezar el juego
Fig. 23: Entramos en el modo juego para probar el funcionamiento de la cuenta regresiva.

escena en unity, en la parte superior un objeto texto que muestra el valor del timer en c#
Fig. 24: Al agotarse el tiempo, el resultado es el mismo que apretar la tecla Escape, se ejecuta el método EndGame y volvemos al menú principal.

Ahora verificamos que se respete el formato de dos números para los segundos y que se decrementen correctamente los minutos cuando los segundos pasan de cero a 59. Iniciamos el Timer con 10 minutos 5 segundos y entramos en el modo juego.

Observamos que se respeta el formato de dos dígitos para los segundos y además los minutos se decrementan correctamente. El ejercicio está terminado.

escena en unity, en la parte superior un objeto texto que muestra el valor del timer en c#
Fig. 25: Probamos nuevamente con el timer en 10 minutos 5 segundos.
escena en unity, en la parte superior un objeto texto que muestra el valor del timer en c#
Fig. 26: Se respeta el formato de la cuenta regresiva.

Conclusión

Hemos visto cómo crear un temporizador simple en Unity, que cumple la función de llevar la cuenta del tiempo, mostrarla en la interfaz de usuario y cuando el tiempo se agota informar al Script GameControl para que decida qué hacer.

Este temporizador se pudo haber implementado de varias formas distintas, en este caso usamos el método Invoke porque me pareció una alternativa bastante simple.

Vimos como podemos modificar fácilmente una componente texto del Canvas siempre que contemos con su referencia, por lo tanto podemos utilizar esto para hacer otras cosas como mostrar el puntaje, mensajes de información, etc.

En este ejercicio están implícitos los conceptos de clase y objeto de programación, el Script Timer es una clase que contiene campos y métodos para resolver un problema en particular, cuenta con la referencia a otros objetos como GameControl para poder enviarle mensajes cuando lo necesite.