Contenido

Los gestores

En esta fase crearemos los puntos de generación de los tanques (SpawnPoints) y un Canvas para los mensajes de información. Además, introduciremos el uso de GameManagers y pondremos todo a funcionar.

Creación de SpawnPoints

  • Crea 2 Empty GameObjects y llámalos SpawnPoint1 y SpawnPoint2
  • Modifica el Transform de SpawnPoint1 para que sea Position -3, 0, 30 y Rotation 0, 180, 0
  • Modifica el Transform de SpawnPoint2 para que sea Position 13, 0, -5 y Rotation 0, 0, 0
  • EN el Inspector window, haz clic en el icono lado del nombre del objeto SpawnPoint1 y cámbialo al icono azul
  • Para el SpawnPoint2, cámbialo al icono rojo
  • Si pulsas la vista de escena 2D, deberías tener algo similar a esto:

Creación de texto para los mensajes

  • Crea un nuevo UI > Canvas y llámalo MessageCanvas
  • Si habías vuelto a vista 3D, cambia a vista 2D en la escena
  • Con el MessageCanvas seleccionado, pon el cursor sobre la escena y pulsa F para centrarnos en el objeto.
  • Haz clic derecho en MessageCanvas de la Hierarchy window y crea un nuevo UI > Text.
  • Cambia el RectTransform de Text para que los Anchors de los ejes X e Y sean Min 0.1 y Max 0.9
  • Ajusta también Left, Right, Top, Bottom y Pos Z a 0.
  • En el componente Text, escribe ¡TANQUES!
  • Cambia la fuente con el círculo selector a BowlbyOne-Regular
  • Cambia la alineación del texto vertical y horizontal a centrada.
  • Habilita Best Fit
  • Ajusta el tamaño de fuente máximo a 60
  • Ajusta el color de fuente a White RGB 255, 255, 255.
  • Haz clic en Add Component y añade un Shadow al Text
  • Ajusta el Effect Color a marrón RGBA: 114, 71, 40, 128.
  • Ajusta Effect Distance a -3, 3.
  • Antes de quitar el modo 2D en la vista de escena, deberías ver algo como esto:

Script de control de cámara

  • Selecciona el objeto CameraRig y en su script Camera Control ajusta Targets a 0.
  • Abre el script Camera Control y descomenta el texto de la línea 8 [HideInInspector]
  • Guarda el script y vuelve a Unity

Scripts de Game Manager y Tank Manager

Crea un nuevo EmptyObject y llámalo GameManager Arrastra desde Scripts > Managers el script GameManager al objeto GameManager Abre el script GameManager, así como el script TankManager. Modifícalos según los mostrados abajo e intenta entenderlos fijándote en los comentarios.

using System;
using UnityEngine;

[Serializable] //Hace que los atributos aparezcan en el inspector (si no los escondemos)
public class TankManager
{
    //Esta clase gestiona la configuración del tanque junto con el GameManager
    //gestiona el comportamiento de los tanques y si los jugadores tienen control sobre el tanque
    //en los distintos momentos del juego

    public Color m_PlayerColor;   //Color para el tanque
    public Transform m_SpawnPoint; //Posición y direción en la que se generará el tanque
    [HideInInspector] public int m_PlayerNumber; //Especifica con qué jugador está actuando el Game Manager
    [HideInInspector] public string m_ColoredPlayerText; //String que reprsenta el color del tanque
    [HideInInspector] public GameObject m_Instance; //Refernecia a la instancia del tanque cuando se crea
    [HideInInspector] public int m_Wins; //Número de victorias del jugador


    private TankMovement m_Movement;  //Referencia al script de movimiento del tanque. Utilizado para deshabilitar y habilitar el control
    private TankShooting m_Shooting; //Referencia al script de disparo del tanque. Utilizado para deshabilitar y habilitar el control
    private GameObject m_CanvasGameObject; //Utilizado para deshabilitar el UI del mundo durante als fases de inicio y fin de cada ronda


    public void Setup()
    {
        //Cojo referencias de los componentes
        m_Movement = m_Instance.GetComponent<TankMovement>();
        m_Shooting = m_Instance.GetComponent<TankShooting>();
        m_CanvasGameObject = m_Instance.GetComponentInChildren<Canvas>().gameObject;

        //Ajusto los número de jugadores para que sean iguales en todos los scripts
        m_Movement.m_PlayerNumber = m_PlayerNumber;
        m_Shooting.m_PlayerNumber = m_PlayerNumber;

        //Creo un string usando el color del tanque que diga PLAYER 1, etc.
        m_ColoredPlayerText = "<color=#" + ColorUtility.ToHtmlStringRGB(m_PlayerColor) + ">PLAYER " + m_PlayerNumber + "</color>";

        //Cojo todos los renderers del tanque
        MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer>();

        //Los recorro...
        for (int i = 0; i < renderers.Length; i++)
        {
            //..y ajusto el color del material al del tanque
            renderers[i].material.color = m_PlayerColor;
        }
    }

    //Usado durante la fases del juego en las que el jugador no debe poder controlar el tanque
    public void DisableControl()
    {
        m_Movement.enabled = false;
        m_Shooting.enabled = false;

        m_CanvasGameObject.SetActive(false);
    }

    //Usado durante la fases del juego en las que el jugador debe poder controlar el tanque
    public void EnableControl()
    {
        m_Movement.enabled = true;
        m_Shooting.enabled = true;

        m_CanvasGameObject.SetActive(true);
    }

    //Usado al inicio de cada ronda para poner el tanque en su estado inicial
    public void Reset()
    {
        m_Instance.transform.position = m_SpawnPoint.position;
        m_Instance.transform.rotation = m_SpawnPoint.rotation;

        m_Instance.SetActive(false);
        m_Instance.SetActive(true);
    }
}

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
    public int m_NumRoundsToWin = 5; //Número de rondas que un jugador debe ganar para ganar el juego
    public float m_StartDelay = 3f; //Delay entre las fases de RoundStarting y RoundPlaying
    public float m_EndDelay = 3f;  //Delay entre las fases de RoundPlaying y RoundEnding
    public CameraControl m_CameraControl; //Referencia al sccript de CameraControl
    public Text m_MessageText; //Referencia al texto para mostrar mensajes
    public GameObject m_TankPrefab; //Referencia al Prefab del Tanque
    public TankManager[] m_Tanks; //Array de TankManagers para controlar cada tanque


    private int m_RoundNumber;  //Número de ronda
    private WaitForSeconds m_StartWait; //Delay hasta que la ronda empieza
    private WaitForSeconds m_EndWait; //Delay hasta que la ronda acaba
    private TankManager m_RoundWinner; //Referencia al ganador de la ronda para anunciar quién ha ganado
    private TankManager m_GameWinner; //Referencia al ganador del juego para anunciar quién ha ganado


    private void Start()
    {
        //Creamos los delays para que solo se apliquen una vez
        m_StartWait = new WaitForSeconds(m_StartDelay);
        m_EndWait = new WaitForSeconds(m_EndDelay);

        SpawnAllTanks(); //Generar tanques
        SetCameraTargets(); //Ajustar cámara

        StartCoroutine(GameLoop()); //Iniciar juego
    }


    private void SpawnAllTanks()
    {
        //Recorro los tanques...
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            //...los creo, ajusto el número de jugador y ls referencias necesarias para controlarlo
            m_Tanks[i].m_Instance =
                Instantiate(m_TankPrefab, m_Tanks[i].m_SpawnPoint.position, m_Tanks[i].m_SpawnPoint.rotation) as GameObject;
            m_Tanks[i].m_PlayerNumber = i + 1;
            m_Tanks[i].Setup();
        }
    }


    private void SetCameraTargets()
    {
        //Creo un array de Transforms del mismo tamaño que el número de tanques
        Transform[] targets = new Transform[m_Tanks.Length];

        //Recorro los Transforms...
        for (int i = 0; i < targets.Length; i++)
        {
            //...lo ajusto al transform del tanque apropiado
            targets[i] = m_Tanks[i].m_Instance.transform;
        }

        //Estos son los targets que la cámara debe seguir
        m_CameraControl.m_Targets = targets;
    }

    //llamado al principio y en cada fase del juego después de otra
    private IEnumerator GameLoop()
    {
        //Empiezo con la corutina RoundStarting y no retorno hasta que finalice
        yield return StartCoroutine(RoundStarting());

        //Cuando finalice RoundStarting, empiezo con RoundPlaying y no retorno hasta que finalice
        yield return StartCoroutine(RoundPlaying());

        //Cuando finalice RoundPlaying, empiezo con RoundEnding y no retorno hasta que finalice
        yield return StartCoroutine(RoundEnding());

        //Si aún no ha ganado ninguno
        if (m_GameWinner != null)
            {
                //Si hay un ganador, reinicio el nivel
                SceneManager.LoadScene(0);
            }
            else
            {
                //Si no, reinicio lsa corutinas para que continúe el bucle
                //En este caso sin yiend, de modo que esta versión del GameLoop finalizará siempre
                StartCoroutine(GameLoop());
            }
        }


    private IEnumerator RoundStarting()
    {
        // Cuando empiece la ronda reseteo los tanques e impido que se muevan.
        ResetAllTanks ();
        DisableTankControl ();

        // Ajusto la cámara a los tanques resteteados.
        m_CameraControl.SetStartPositionAndSize ();

        // Incremento la ronda y muestro el texto informativo.
        m_RoundNumber++;
        m_MessageText.text = "ROUND " + m_RoundNumber;

        // Espero a que pase el tiempo de espera antes de volver al bucle.
        yield return m_StartWait;
    }


    private IEnumerator RoundPlaying()
    {
        // Cuando empiece la ronda dejo que los tanques se muevan.
        EnableTankControl ();

        // Borro el texto de la pantalla.
        m_MessageText.text = string.Empty;

        // Mientras haya más de un tanque...
        while (!OneTankLeft())
        {
            // ... vuelvo al frame siguiente.
            yield return null;
        }
    }


    private IEnumerator RoundEnding()
    {
         // Deshabilito el movimiento de los tanques.
        DisableTankControl ();

        // Borro al ganador de la ronda anterior.
        m_RoundWinner = null;

        // Miro si hay un ganador de la ronda.
        m_RoundWinner = GetRoundWinner ();

        // Si lo hay, incremento su puntuación.
        if (m_RoundWinner != null)
            m_RoundWinner.m_Wins++;

        // Compruebo si alguien ha ganado el juego.
        m_GameWinner = GetGameWinner ();

        // Genero el mensaje según si hay un gaandor del juego o no.
        string message = EndMessage ();
        m_MessageText.text = message;

        // Espero a que pase el tiempo de espera antes de volver al bucle.
        yield return m_EndWait;
    }

    // Usado para comprobar si queda más de un tanque.
    private bool OneTankLeft()
    {
        // Contador de tanques.
        int numTanksLeft = 0;

        // recorro los tanques...
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            // ... si está activo, incremento el contador.
            if (m_Tanks[i].m_Instance.activeSelf)
                numTanksLeft++;
        }

        // Devuelvo true si queda 1 o menos, false si queda más de uno.
        return numTanksLeft <= 1;
    }


    // Comprueba si algún tanque ha ganado la ronda (si queda un tanque o menos).
    private TankManager GetRoundWinner()
    {
        // Recorro los tanques...
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            // ... si solo queda uno, es el ganador y lo devuelvo.
            if (m_Tanks[i].m_Instance.activeSelf)
                return m_Tanks[i];
        }

        // SI no hay ninguno activo es un empate, así que devuelvo null.
        return null;
    }


    // Comprueba si hay algún ganador del juegoe.
    private TankManager GetGameWinner()
    {
        // Recorro los tanques...
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            // ... si alguno tiene las rondas necesarias, ha ganado y lo devuelvo.
            if (m_Tanks[i].m_Wins == m_NumRoundsToWin)
                return m_Tanks[i];
        }

        // Si no, devuelvo null.
        return null;
    }


    // Deveulve el texto del mensaje a mostrar al final de cada ronda.
    private string EndMessage()
    {
        // Pordefecto no hya ganadores, así que es empate.
        string message = "EMPATE!";

        // Si hay un ganador de ronda cambio el mensaje.
        if (m_RoundWinner != null)
            message = m_RoundWinner.m_ColoredPlayerText + " GANA LA RONDA!";

        // Retornos de carro.
        message += "\n\n\n\n";

        // Recorro los tanques y añado sus puntuaciones.
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            message += m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins + " GANA\n";
        }

        // Si hay un ganador del juego, cambio el mensaje entero para reflejarlo.
        if (m_GameWinner != null)
            message = m_GameWinner.m_ColoredPlayerText + " GANA EL JUEGO!";

        return message;
    }


    // Para resetear los tanques (propiedaes, posiciones, etc.).
    private void ResetAllTanks()
    {
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            m_Tanks[i].Reset();
        }
    }

    //Habilita el control del tanque
    private void EnableTankControl()
    {
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            m_Tanks[i].EnableControl();
        }
    }

    //Deshabilita el control del tanque
    private void DisableTankControl()
    {
        for (int i = 0; i < m_Tanks.Length; i++)
        {
            m_Tanks[i].DisableControl();
        }
    }

}
Es el momento de asignar los objetos a las variables de los scripts:
  • Arrastra el objeto CameraRig a la variable Camera Control del script GameManager
  • Arrastra el Text del MessageText a la variable MessageText
  • Arrastra el prefab Tank de la carpeta Prefabs a la variable Tank Prefab
  • Expande el array de Tanks y ajusta Size a 2.
  • Para Element 0, cambia Player Color a azul (RGB 42, 100, 178)
  • Arrastra SpawnPoint1 a la variable SpawnPoint del Element 0.
  • Para Element 1, cambia Player Color a rojo (RGB 229, 46, 40)
  • Arrastra SpawnPoint2 a la variable SpawnPoint del Element 1.
Con esto ya deberías tener el juego funcionado para dos jugadores. El primero de ellos con AWSD y barra espaciadora para disparar, y el segundo con las flechas y Enter para disparar. ¡Busca un amigo y prueba!

Explicación de la estructura de los GameManager

Si has estudiado con detenimiento los scripts GameManager y TankManager, habrás comprendido lo siguiente:
  • GameManager inicializa el juego (genera los tanques y ajusta los targets de cámara)
  • GameManager ejecuta los estados del juego (empezar, jugar finalizar)
  • Para cada tanque, GameManager usa un TankManager
  • Cada TankManager tiene su control de disparo, movimiento y UI
  • Las co-rutinas del GameManager nos permiten saltar entre estados del juego cuando se cumplan ciertas condiciones (yield)
  • A modo de resumen, se muestra la siguiente captura extraída de las diapositivas del curso original:
Fuente: Unite 2015