Contador de puntos para Unity.

Casi todos los juegos tienen algún tipo de puntuación, pero los Arcades: todos! Si estamos haciendo un Arcade tenemos que tener un sistema de puntuación. Forma parte del Core de lo que son los Arcades. Siguiendo con el tutorial de Desarrollo de Unity vamos a crear un Contador para nuestro juego. Aunque como ya veréis, por la forma de crearlo, podremos aprovecharlo para cualquier juego.

Este post forma parte del tutorial de desarrollo de Unity. En los post anteriores hemos ido avanzando con el desarrollo, si quieres puedes obtener los ficheros necesarios para continuar con el tutorial desde este punto en este enlace: megafastball-coloresysegundosuelo.

Hay unas preguntas que nos tenemos que hacer al crear un contador:
¿A quien pertenece el contador? Es decir, tenemos que decidir donde vamos a guardarlo, si es un contador para el player, y tenemos solo uno, un buen sitio para guardar la puntuación podría ser en una variable del script que controla a este player. Puede darse el caso de que tengamos más de un player y hagan un contador conjunto, entonces no se lo podemos dar a tan solo uno de los players. En el caso de megafastball, solo tenemos un Player, que es nuestra bola, por lo que será el responsable de mantener el contador.

¿Como se obtienen los puntos? ¿Cuando nuestro player destroza objetos? ¿Cuando dos objetos externos chocan? Para seguir el ejemplo de nuestro juego, podríamos aumentar la puntuación cuando nuestro Player recoja las monedas, y también cuando consigamos que dos cubos choquen y se destrocen entre ellos. En el primer caso es muy sencillo, solo tenemos que capturar el evento OnTriggerEnter, o el que sea en el que detectemos la colisión, y variar el contenido de la variable donde guardamos la puntuación. En el caso de que la puntuación se pueda variar por causas que no tienen nada que ver con nuestro player tendríamos que ofrecer una función publica, responsable de modificar la puntuación, para que se pueda llamar desde otros objetos.

Como esto es un tutorial, y los tutoriales sirven para aprender vamos a usar las dos formas de actualizar la puntuación. Es importante, porque vamos a aprender como llamar funciones que están situadas en scripts de otros objetos de Unity. Esto lo vamos a usar mucho si continuamos programando en Unity.

Visualizando la puntuación.

La mostraremos en un text element, que lo creamos desde la sección Hierarcy, con la opción Create->UI->Text.

Creará nuestro elemento de texto dentro de un Canvas, y al incorporar un UI Element, también nos va a incorporar un EventSystem. Estos dos elementos los crea la primera vez que incorporamos un UI Element.

Le hemos cambiado el nombre a: Contador. Este será el elemento que mostrará la puntuación. Si nos fijamos, lo podemos ver en nuestra Scene, o incluso lo veremos mejor en la pantalla de juego. El texto aparece centrado, y con tinta negra. Cuesta de leer y la posición no es la mejor. Lo vamos a posicionar en la parte superior central de la pantalla, le cambiaremos el tamaño y el color para que se pueda ver mejor.
Posicionando el contador mediante Rect Transform

Seleccionamos el Contador y en su sección Inspector nos encontramos con el apartado Rect Transform. Al pulsarlo se despliegan las diferentes opciones. Pulsamos las teclas Shift y Alt a la vez y seleccionamos el lugar donde queremos que se muestre el texto. En mi caso he seleccionado la parte superior central.

Añadimos los puntos que hace el propio jugador.

Estos puntos los hace al recoger monedas, solo tendremos que modificar un Script, y el es que controla a nuestro Player: la Bola.

Para editarlo, seleccionamos nuestra bola FastBall, y hacemos doble click en el nombre del script. Con lo que se nos abrirá el editor de script por defecto de Unity: MonoDevelop.
Añadimos la líbreria que nos permite trabajar con los objetos UI de Unity.

 
using UnityEngine.UI;

Vamos a tener que crear dos variables, una para contener la puntuación, y otra representará el contador que vemos en la pantalla:

public Text textoContador;
private int puntuacion = 0;

La variable puntuación la hacemos privada, ya que no hace falta acceder a ella desde el editor, y la incializamos a 0. En cambio la variable textoContador si que es pública, ella representara al contador y en el editor le vamos a indicar que el objeto contador es el campo texto que hemos creado. En la imagen podéis ver que yo ya lo he asignado.

Tenemos que crear la función que modifique la puntuación cuando recolectamos una moneda. Para ello vamos a aprovechar el evento OnTrigerEvent, le añadimos al script la siguiente función:

void OnTriggerEnter (Collider other){
//Debug.Log (other.gameObject.tag);
if (other.gameObject.tag == "collectable"){
puntuacion = puntuacion + 1;
textoContador.text = puntuacion.ToString();
}
}

El código es muy sencillo de leer. En la función, a la que llama Unity al colisionar, recibimos el otro objeto que forma parte de la colisión. En la línea del if miramos si el objeto con el que hemos chocado es una moneda, o bueno, mejor dicho miramos si dispone del Tag collectable, esto nos permitiría añadir nuevos objetos a recoger a medida que avanzamos en el juego. En caso de que sea collectable incrementamos la variable puntuación y asignamos su contenido a la variable textoContador, que recordemos, contiene el campo text que hemos creado para mostrar la puntuación al usuario.

Es muy importante usar el campo Tag de los objetos en Unity, nos permite tratar objetos diferentes como si fueran de una clase. Por ejemplo nuestro Player podría recoger monedas, billetes, diamantes o lo que sea, mientras dispongan del tag collectable incrementaran el contador del usuario.

Con el script modificado y con el campo text asignado a la variable del script nuestro jugador ya sumaria puntos al recoger monedas. Pero queremos hacer algo mas, queremos sumar puntos cuando dos enemigos choquen entre ellos.

Sumando puntos cuando dos enemigos chocan.

Por ahora hemos visto como incrementar una variable contenida en el mismo script. Pero este caso es diferente, desde el enemigo vamos a tener que modificar la variable puntuación de nuestro Player. Para ello vamos a tener que modificar una variable de un script de Unity desde otro Script.

La interacción entre objetos es muy importante en Unity, y tenemos que saber que hay varios métodos de hacer llamadas de un script a otro. En este caso usaremos una función publica, pero también podríamos usar los métodos set y get de la variable… ¿Suena a chino? Pero ya tenéis donde empezar a buscar 😉

En el mismo script BallUserControl donde ya hemos realizado las modificaciones creamos una función pública que sea la responsable de modificar la puntuación.

public void updateScore(int points){
Debug.Log ("updateScore");
puntuacion = puntuacion + points;
textoContador.text = puntuacion.ToString();
}

Esta función recibe los puntos a incrementar y modifica la variable puntuación y el texto asociado de la variable que muestra el contador.

Ahora tenemos que modificar el script desde el que controlamos la explosión de los enemigos. Seleccionamos nuestro Prefab enemigo (En el que se han basado todos nuestros enemigos) y en su sección Inspector veremos el Script: Damage. Damos doble click sobre su nombre y vamos al editr MonoDevelop para modificar su contenido.

Si lo recordáis, en el post de las explosiones, ya modificamos la función OnCollisionEnter del script para que lanzara nuestra explosión, pues bien podemos hacerlo en la misma función, añadiendo estas líneas:

//Si chocamos con otro enemigo nos eliminanos a nosotros mismos. 
if (other.gameObject.tag == "Enemy") {
GameObject player = GameObject.Find ("FastBall");
if (player != null) {
player.GetComponent<BallUserControl> ().updateScore(5);
Destroy (gameObject);
}
}

Como podeís ver, si detectamos que colisionamos con otro enemigo lo primero que hacemos es crear una variable de tipo GameObject llamada Player, y le asignamos nuestra Bola, buscandola por su nombre. En el caso de que no la encuentre, por que haya explotado previamente, nos devolverá un null (es decir nada).
Si nos devuelve el objeto Player lo que hacemos es llamar a la función que hemos creado, pero atención, si os fijaís cuando hacemos la llamada a GetComponent hemos incorporado . Le estamos indicando que queremos acceder a ese script en concreto de todos los que tiene nuestro Player. A la función le pasamos el incremento que queremos se produzca en la puntuación del usuario!!! Ya esta!

Pongamos el código de los dos scripts:
Primero el BallUserControl, el script del Player que mantiene el contador. Recordemos que la mayor parte de este script ya nos ha venido dada por los Standard Assets de Unity, y que nosotros solo hemos introducido algunas modificaciones.

using System;
using UnityEngine;
using UnityEngine.UI;
using UnityStandardAssets.CrossPlatformInput;
namespace UnityStandardAssets.Vehicles.Ball
{
public class BallUserControl : MonoBehaviour
{
public Text textoContador;
private Ball ball; // Reference to the ball controller.
private Vector3 move;
// the world-relative desired move direction, calculated from the camForward and user input.
private Transform cam; // A reference to the main camera in the scenes transform
private Vector3 camForward; // The current forward direction of the camera
private bool jump; // whether the jump button is currently pressed
private int puntuacion = 0;
void OnTriggerEnter (Collider other){
//Debug.Log (other.gameObject.tag);
if (other.gameObject.tag == "collectable"){
puntuacion = puntuacion + 1;
textoContador.text = puntuacion.ToString();
}
}
private void Awake()
{
// Set up the reference.
ball = GetComponent<Ball>();
// get the transform of the main camera
if (Camera.main != null)
{
cam = Camera.main.transform;
}
else
{
Debug.LogWarning(
"Warning: no main camera found. Ball needs a Camera tagged \"MainCamera\", for camera-relative controls.");
// we use world-relative controls in this case, which may not be what the user wants, but hey, we warned them!
}
puntuacion = 0;
}
public void updateScore(int points){
Debug.Log ("updateScore");
puntuacion = puntuacion + points;
textoContador.text = puntuacion.ToString();
}
private void Update()
{
// Get the axis and jump input.
float h = CrossPlatformInputManager.GetAxis("Horizontal");
float v = CrossPlatformInputManager.GetAxis("Vertical");
jump = CrossPlatformInputManager.GetButton("Jump");
// calculate move direction
if (cam != null)
{
// calculate camera relative direction to move:
camForward = Vector3.Scale(cam.forward, new Vector3(1, 0, 1)).normalized;
move = (v*camForward + h*cam.right).normalized;
}
else
{
// we use world-relative directions in the case of no main camera
move = (v*Vector3.forward + h*Vector3.right).normalized;
}
}
private void FixedUpdate()
{
// Call the Move function of the ball controller
ball.Move(move, jump);
jump = false;
}
}
}
Ahora el script <em>Damage</em>, de nuestra creación, en el que realizamos las acciones cuando dos objetos chocan, entre ellas la de incrementar el contador del player cuando los que chocan son dos enemigos. 
using UnityEngine;
using System.Collections;
using UnityStandardAssets.Vehicles.Ball;
public class Damage : MonoBehaviour {
//public GameObject explosionPlayer;UnityStandardAssets.Vehicles.Ball
void OnCollisionEnter(Collision other)			
{
//Si chocamos con un Player lo eliminamos a el. 
if (other.gameObject.tag == "Player") {
//if (explosionPlayer!=null) {
//	Instantiate (explosionPlayer, other.gameObject.transform);
//}
Destroy (other.gameObject);
}
//Si chocamos con otro enemigo nos eliminanos a nosotros mismos. 
if (other.gameObject.tag == "Enemy") {
GameObject player = GameObject.Find ("FastBall");
if (player != null) {
player.GetComponent<BallUserControl> ().updateScore(5);
Destroy (gameObject);
}
}
}			
}

¿Que hemos aprendido?

Pues una de las cosas más importantes ha sido la de hacer llamadas entre scripts de diferentes Objetos. Os parecerá una chorrada, pero a medida que los juegos se complican es uno de los skills mas usados.
Hemos trabajado con un Objeto UI, un texto, y lo hemos alineado usando su Rect Transform.
Hemos creado un contador, algo imprescindible en casi todos los juegos!!!!

Next: Incorporamos un GameOver!

En este enlace podéis encontrar el proyecto con todas las modificaciones realizadas. MegafastBall con puntuacion.

Comentarios.