¿Como incorporar un Game Over?

Todo juego tiene un final, y como ya sabes el final de un Juego se llama: Game Over! Es ese momento odiado por los jugadores, en los que se indica que o bien has sido eliminado, o bien te has acabado el juego. Para incorporar un Game Over en Unity tenemos que empezar a pensar en crear un GameManager. No os preocupéis si es la primera vez que lo oís, un GameManager es el script que gestiona la transición entre estados del juego. Lo que vamos a hacer es presentar la pantalla de Game Over cuando muere nuestro jugador, y darle al usuario la posibilidad de volver a jugar.

Este post forma parte del tutorial de desarrollo de Unity. En los post anteriores hemos creadoun arcade que permite recolectar monedas, y existen unos enemigos que acaban con nosotros. Si quieres puedes obtener los ficheros necesarios para continuar con el tutorial desde este punto en este enlace: megafastball-puntuacion.

En el post anterior, en el que incorporamos la puntuación, creamos un Canvas para mostrar el contador. Ahora vamos a crear un Canvas nuevo para mostrar el mensaje de Game Over. Este Canvas estará inactivo mientras dure la partida y se activara cuando el Player, nuestra bola, explote.

Creando el Canvas en Unity.
Creando el Canvas en Unity.

Para crear el Canvas lo hacemos desde el menú Create de nuestra sección Hierarchy. Una vez creado le cambiamos el nombre a: GameOverCanvas. Seleccionamos el Canvas recién creado para editar algunos valores en el Inspector:
Inspector Canvas Game Object
El primer cambio es indicar el el Canvas no esta activo, desmarcando el check que aparece en la parte superior al lado del nombre. También hemos modificado el modo de escalar su imagen, para que se adapte a cualquier pantalla.

Vamos a crear dos elementos hijos del Canvas, un texto, que contendrá la puntuación final del usuario, y un botón que le permitirá volver a jugar.

Para ello hacemos clik con el botón derecho encima del canvas y en UI Elements escogemos primero Text y luego Button, o al revés. Lo importante es que ya tendremos un Text y un Button como hijos del canvas GameOverCanvas. Repetimos la operación para crear un objeto de tipo texto que sea hijo del Botón.

El Canvas Game Over con sus objetos hijos.
El Canvas Game Over con su árbol de objetos.

Vamos a ver como hemos informado el objeto GameOverText.

Inspector del texto del GAmeOver

En este texto mostraremos la puntuación obtenida por el usuario. Las únicas modificaciones han sido visuales. Yo he informado un texto muy grande y de color blanco.

Antes de informar los otros objetos, vamos a crear algunos scripts que serán necesarios.

Los scripts que gestionan el juego.

La gestión de estados del juego se realiza en varios scripts, el principal es uno llamado GameManager, que será el primero que crearemos, pero también hay modificaciones en otros Scripts, que se comunican con el GameManager, o de los que el GameManager recupera información.

Creando el GameManager.

El GameManager, principalmente, es un Script, pero necesitamos ponerlo en algún lugar en Unity. Para ello crearemos un objeto vacío con el menú Create de la sección Hierarchy, escogiendo la opción Create Empty. Renombramos el objeto recién creado a GameManager, y mediante el botón Add Component de su Inspector le añadimos un nuevo Script, con el siguiente contenido: 

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public static GameManager gm;
public GameObject player;
public enum gameStates {Playing, Death, GameOver};
public gameStates gameState = gameStates.Playing;
public GameObject mainCanvas;
public Text textScoreMainCanvas;
public GameObject gameOverCanvas;
public Text textScoreGameOver;
private isLive live; 
void Start () {
if (gm == null) 
gm = gameObject.GetComponent<GameManager>();
if (player == null) {
player = GameObject.FindWithTag("Player");
}
live = player.GetComponent<isLive> ();
gameState = gameStates.Playing;
// Desactivamos el Canvas gameOver, just in case. 
gameOverCanvas.SetActive (false);
}
void Update () {
switch (gameState)
{
case gameStates.Playing:
if (live.live == false)
{
gameState = gameStates.GameOver;
mainCanvas.SetActive (false);
gameOverCanvas.SetActive (true);
textScoreGameOver.text = textScoreMainCanvas.text;
} 
break;
case gameStates.GameOver:
// nothing
break;
}
}
}

Vamos a destripar por partes el script.

......
public GameObject player;
public enum gameStates {Playing, Death, GameOver};
public gameStates gameState = gameStates.Playing;
public GameObject mainCanvas;
public Text textScoreMainCanvas;
public GameObject gameOverCanvas;
public Text textScoreGameOver;
private isLive live; 
.....

Primero definimos unas cuantas variables públicas, para que le podamos indicar al GameManager, cual es el Canvas principal del juego, y cual es el canvas del GameOver. El GameManager se encargará de mostrar uno u otro, por eso necesita saber cuales son. Ya de paso también le informamos de los textos de puntuación de los dos canvas, esto nos facilitará copiar la puntuación del Canvas principal al Canvas GameOver cuando mostremos este último. La variable private live, la usaremos para almacenar el estado del jugador, para saber si esta vivo o ha muerto.

...
//Se ejecuta una vez, al iniciar el script. 
void Start () { 
if (player == null) { //Si no se ha informado el Player busca un objeto con el Tag Playe. 
player = GameObject.FindWithTag("Player");
}
live = player.GetComponent<isLive> (); //Recuperamos el script isLive del player y lo guardamos en una variable. 
gameState = gameStates.Playing; //Cambiamos el estado a Playing. 
// Desactivamos el Canvas gameOver, just in case. 
gameOverCanvas.SetActive (false);
}
...

Esta función se ejecuta cada vez que se inicia el objeto. Empieza comprobando si el Player esta informado, en caso de que no lo esté, busca un objeto con el Tag Player para asociarlo.
Una vez tenemos el objeto Player informado, recuperamos la variable live. Esta variable esta en un script llamado isLive (después lo vemos), que hemos asociado al player, pero que podríamos asociar a cualquier elemento, y indica si el elemento que lo tiene esta vivo o no. Lo asociamos en la función Start, y no en la Update, porque esta segunda se ejecuta varias veces por segundo y tenemos que evitar sobrecargarla.
Acto seguido ponemos el estado del juego a Playing, y desactivamos el canvas del GameOver.

Vamos a ver la función Update:

...
//Se ejecuta en cada frame, varias cveces por segundo. 
void Update () {
switch (gameState)
{
case gameStates.Playing:
if (live.live == false) // si la variable live es false el jugador ha muerto. 
{
gameState = gameStates.GameOver; //Cambiamos el estado del juego a GameOver
mainCanvas.SetActive (false); //Quitamos el mainCanvas. 
gameOverCanvas.SetActive (true); //Activamos el gameOver Canvas. 
textScoreGameOver.text = textScoreMainCanvas.text; //Copiamos el texto con la puntuación del MainCanvas al GameOver. 
} 
break;
case gameStates.GameOver:
// en el caso de que tengamos un GameOver no hacemos nada!
break;
}
}
...

Esta función se llama cada vez que se modifica un frame, es decir, por cada movimiento que se produce en nuestro escenario. Solo tenemos que hacer comprobaciones cuando el juego esta en marcha, en el estado Playing. En este estado miramos si se ha modificado el estado de live, y en el caso de que sea false, se realizan las acciones necesarias: Cambiar el estado de juego a GameOver, activar el canvas de GameOver y copiar el texto del Contador del MainCanvas al contador del GameOverCanvas.

Configuración del GameManager en el Inspector.

El script para cargar niveles.

Pero si solo tenemos un nivel!!!! Pues sí! Pero eso no significa que no vayamos a tener más, y este script nos servirá para que el usuario pueda cargar el primer nivel una vez que ha finalizado la plantilla.
El Script se lo vamos a asignar al testo del botón PlayAgain del GameOverCanvas. Para crearlo lo haremos desde el menú Create de la sección Project, con la opción C# Script.

El script lo llamamos CargarNivel:

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
public class CargarNivel : MonoBehaviour {
public string LevelToLoad; //En esta variable indicarewmos (desde el editor)el nombre del nivel a cargar
public void loadLevel() {
//Carga el nivel que contiene la variable publica- 
SceneManager.LoadScene(LevelToLoad);
}
}

Como se puede ver el script es realmente sencillo. Tiene una variable publica a la que le asignaremos el nombre del nivel a cargar, que coincide con el nombre de la Scene, en nuestro caso de llama Level1, a no ser que lo hayáis cambiado. Creamos una función que llama a la función LoadScene del SceneManager pasandole el nombre del nivekl a cargar.

Este script lo asignamos al componente Text hijo del botón PlayAgain.

Script asignado al componente Text del botón PlayAgain.

Pero esta asignación no es suficiente para ejecutar el Script, tenemos que realizar unas acciones en el botón PlayAgain.

Asignamos el text al boton para que pueda recuperar el script.
Asignación del script al botón.

Para incorporarlo pulsamos el simbolo + del corner derecho inferior, arrastramos el texto a la casilla, y ya podremos seleccionar la función loadLevel del combo. Ahora ya tendría que cargar el nivel al pulsar el botón PlayAgain.

Controlando la vida del Player.

Ahora ya tenemos el GameManager, el Canvas del GameOver y el script que carga el nivel, pero no aparece nada, simplemente, por que nuestra bola, nuestro player, no muere nunca, solo desaparece.
Vamos a crear un Script que sirva para indicar que un objeto esta vivo.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class isLive : MonoBehaviour {
public bool live = true;
void onDestroy(){
live = false;
}
}

Increíblemente sencillo, el script modifica la variable live y le asigna el valor false cuando le llega el evento onDestroy. Le asignamos este script al Player y ya lo tenemos!

Resumiendo el GameManager sabe quien es el Player, busca en el un script llamado isLive, y se guarda el valor de la variable live. Este script cambia el valor de la variable a false cuando se destruye. El GameManager se da cuenta cuando se le llama a su función update, es decir, casi en el mismo momento.

De verdad, es mucho mas sencillo de lo que parece. Ya casi lo tenemos, solo hace falta una cosa para que acabemos nuestro juego, ponerle niveles. Es verdad, podríamos incorporarle mil cosas más, pero una de las cosas que se tiene que aprender es a para y a gestionar las expectativas. Es mejor sacar el juego e ir incorporando características,  que el posponer eternamente la salida de nuestro juego a que incorpore todo lo que creemos que tiene que llevar.

 

Next: Incorporamos niveles a nuestro juego.

 

En este enlace podéis encontrar el proyecto de Unity con todas las modificaciones realizadas hasta ahora: megafastball-gameover.

Comentarios.