tutorial

magbo system

Unity 3D – Cómo hacer un cuadro de selección como el de un RTS

¡Buenas tardes!

Hace más de dos semanas, antes de largarme al III Encierro en las Cumbres (una concentración de guionistas en las Hoces del Duratón dirigida por Valentín Fernández-Tubau) y las posteriores fiestas de mi pueblo, escribí un artículo que no me dio tiempo a colgar aquí.

Después también he tenido algo de lío y no he podido hacerlo hasta hoy. Espero que la espera haya merecido la pena:

Como ya he comentado un par de veces, el principal proyecto en el que me encuentro trabajando ahora mismo es Bellum, un juego de estrategia a tiempo real.

Uno de los retos a los que hemos tenido que enfrentarnos es la realización de un cuadro de selección de unidades. No es que sea demasiado difícil, pero no hemos encontrado absolutamente ningún tutorial de Unity 3D que indique cómo hacerlo en toda la red. Así que lo que voy a ofreceros probablemente sea una exclusiva de Internet (o quizás sea que soy un patán con Google, que también es posible).

Muchos de vosotros os preguntaréis para qué os sirve un cuadro de selección si los juegos de estrategia os importan un pimiento. Pues antes de que dejéis de leer os aviso de que, aunque vuestro juego no incluya este elemento, a lo largo del tutorial veremos cosas tan básicas e imprescindibles como tirar «rayos» desde la cámara para ver qué tenemos delante de nuestro puntero (la base de cualquier shooter) o cómo pasar coordenadas de mundo a coordenadas de pantalla.Así, en este tutorial vamos a aprender a:

– Enfrentarnos a un proyecto real, utilizando las distintas herramientas que nos ofrece Unity 3D.
– Construir un código ordenado que responda a nuestros requerimientos.
– Crear GameObjects desde el código a partir de Prefabs.
– Utilizar los valiosos «Raycast» para detectar elementos de nuestra escena presentes en un determinado vector.
– Pasar coordenadas de mundo a coordenadas de pantalla.
– ¡Ah, sí! Y a crear un cuadro de selección :P.

Aunque voy a reutilizar algo del código de Bellum, para no liaros he decidido crear un nuevo proyecto.

En un alarde de originalidad, lo he llamado «Tutorial_SelectionBox».

Game loop: el origen de todo

¡Volví de vacaciones!

Y lo hago con un tutorial que considero básico e imprescindible: cómo funciona un videojuego. A la hora de enfrentarnos a cualquier cosa, lo más complicado suele ser saber cómo empezar, cuál es el origen de todo lo que vamos a hacer después. Y creo que esta preocupación ocurre tanto a la hora de programar, como de enfrentarse a una hoja en blanco o al pedir nuestra primera subida de sueldo.

Con los videojuegos algunos motores nos darán esta base, y a partir de ahí podemos mirar un montón de tutoriales para seguir haciendo cosas, pero si no sabemos qué es lo que hace funcionar a nuestro juego, o en qué parte actúa el API que estemos utilizando, muchas veces iremos a ciegas. Y como todos ya nos conocemos el refrán «ojos que no ven, ostiazo que te pegas» vamos a intentar arrojar un poco de luz sobre todo esto.

Pues bien, un juego no es más que un bucle continuo llamado game loop:

  1. void run()
  2. {
  3.     bool playing = true;
  4.     while (playing)
  5.     {
  6.         ReadInput(); //Leemos las entradas del teclado
  7.         Update(); //Actualizamos el estado de los objetos
  8.         Render(); //Los dibujamos en pantalla
  9.     }
  10. }

Todo juego tiene una estructura similar, pero tenemos que tener en cuenta el API sobre el que estamos trabajando.

ReadInput()
Aquí comprobamos el estado de los distintos dispositivos de entrada (ratón y teclado sobre todo, pero podría ser un joystick o un joypad, una pantalla táctil en el caso de un smartphone, o incluso un micrófono). Es altamente recomendable separar la lógica del ReadInput de la del Update(), y utilizar flags que indiquen el estado del input para que el Update actúe en consecuencia.

También es ideal que almacenemos las teclas en variables, de este modo podremos cambiar fácilmente el mando del juego.

No recomendado:

  1. void run()
  2. {
  3.     bool playing = true;
  4.     while (playing)
  5.     {
  6.         ReadInput(); //Leemos las entradas del teclado
  7.         Update(); //Actualizamos el estado de los objetos
  8.         Render(); //Los dibujamos en pantalla
  9.     }
  10. }
  11. void ReadInput()
  12. {
  13.     //Cada API tiene su función para leer el Input.
  14.     if (isKeyPressed(Key.Space))
  15.     {
  16.         Jump();
  17.     }
  18. }

Recomendado:

  1. void run()
  2. {
  3.     bool playing = true;
  4.     //Input Keys
  5.     const Key KEY_JUMP = Key.Space;
  6.     //Input State
  7.     bool jump;
  8.     while (playing)
  9.     {
  10.         ReadInput(); //Leemos las entradas del teclado
  11.         Update(); //Actualizamos el estado de los objetos
  12.         Render(); //Los dibujamos en pantalla
  13.     }
  14. }
  15. void ReadInput()
  16. {
  17.     //Cada API tiene su función para leer el Input.
  18.     if (IsKeyPressed(KEY_JUMP))
  19.     {
  20.         jump = true;
  21.     }
  22. }
  23. void Update()
  24. {
  25.     if (jump)
  26.     {
  27.         Jump(); //Esta función terminaría con un jump=false;
  28.     }
  29. }

 

De este modo, separaremos de forma lógica ambas funciones y tendremos un código más ordenado, lo que nos ayudará enormemente cuando tengamos un Update complejo en el que tengamos que tener en cuenta un montón de variables.

Update()
Aquí desarrollaremos toda la lógica de juego, pero no os abruméis. Un juego no es más que unos cuantos objetos con una serie de estados, que realizan funciones. Así, en función de su estado, cambiaremos su posición en la pantalla, pero también deberemos de acordarnos de controlar la vida, la munición, los puntos o el resto de variables que estemos utilizando.

Dentro del Update deberíamos tener en cuenta también la física, pero por lo general nos la manejará automáticamente nuestro motor, por lo que no tendremos que preocuparnos. Si no lo hace, hoy en día existen motores físicos para absolutamente todas las plataformas que seguro podremos utilizar. Unity 3D, por ejemplo, tiene su propio manejador de físicas, por lo que sólo nos tendremos que preocupar de establecer en nuestro objeto una serie de propiedades físicas.

Los cálculos físicos suelen ser los últimos que se realizan en el Update

Render()
Cuando trabajamos con un motor gráfico, lo que hace básicamente es manejarnos automáticamente la función Render() (aunque luego pueda resolvernos más cosas, como la física), ya que es la que trabaja directamente sobre las librerías gráficas (DirectX u OpenGL), y muchas veces en nuestros juegos nos bastará con añadir nuestros elementos a un Graphic Layer (una capa que se encarga de dibujar todo lo que haya en ella), o heredar nuestros objetos de alguna clase que contenga toda la lógica de renderizado.

¿Dónde encaja todo esto en Unity 3D?

Efectivamente, en Unity 3D nosotros no vemos este bucle por ningún lado.

Antes que nada, hay que comprender que Unity 3D sigue una arquitectura basada en componentes, que es ligeramente distinta a la más familiar POO (Programación Orientada a Objetos). La diferencia entre un objeto y un componente es que los primeros necesitan ser instanciados, es decir, partimos de una clase que define un tipo de objeto que nos servirá de molde para crear objetos similares (por ejemplo, la claseEnemy nos podría servir para crear multitud de enemigos). Mientras tanto, los componentes se asemejan más a scripts, trozos de código que dan una funcionalidad determinada al objeto al que se anexan, y cuando uitilicemos la palabra clave «this«, no referenciaremos al componente, sino al objeto sobre el que está alojado.

De este modo, si en Unity creas un GameObject (que es cualquier cosa que actúe en el juego), tendrá toda la funcionalidad básica de un objeto del juego, pero si además le añades, por ejemplo, un componente RigidBody, el GameObject tendrá física, y si también le pones un componente Player, en el que defines la lógica del jugador, esteGameObject ahora responderá a las acciones del jugador.

Ambas arquitecturas pueden combinarse, y de hecho lo hacen. Tú, por ejemplo, puedes crear un componente cuya lógica utilice instancias de clases, y después agregarlo como componente a un GameObject.

Muy bonito todo este inciso, pero seguimos sin ver el game loop del que os he hablado al principio. La razón de esto es que Unity lo está manejando de forma transparente, sin que nosotros nos demos cuenta. Cada vez que Unity da una vuelta al bucle, llama a una serie de funciones contenidas en el MonoBehaviour. Por eso, cuando creamos un script en C# (el lenguaje recomendado), veremos que hereda de esta clase. Así, lo único que tenemos que hacer cuando creamos un script es definir estas funciones. Por defecto Unity nos creará Start() y Update().

Unity 3D nos ofrece la documentación de las funciones que utiliza en esta web: http://unity3d.com/support/documentation/Manual/Execution%20Order.html

Es imprescindible que tengáis en cuenta que cada vuelta al loop puede tomar un tiempo distinto en función de la carga del procesador, por lo que tendréis que utilizar este espacio de tiempo en vuestros cálculos. Para ello, Unity pone a vuestra disposición la variable Time.deltaTime, que no es más que los segundos que ha tardado el juego en completar el último frame.

La excepción es FixedUpdate(), que tiene un tiempo de llamada específicado en la variable global Application.targetFrameRate.

Aunque para trabajar con físicas FixedUpdate() es la función recomendada, debemos tener mucho cuidado al utilizarla, ya que procesos muy cargantes o frameratesdemasiado pequeños pueden hacer que nuestro juego haga cosas raras o nos vaya a saltos.

Ahora que comprendemos el game loop de un juego y sabemos cómo interactuar con él en Unity 3D, podemos empezar a hacer nuestros primeros scripts sin cortocircuitarnos en el intento ;).

Unity 3D – Empezando nuestro proyecto.

Como Neowedge no está seré yo el encargado de hacer esta semana este episodio de Desarróllame, ya conocemos un poco el entorno de trabajo así que ahora vamos a trabajar de verdad, para ello hoy trataremos de hacer que un cilindro se mueva sobre un plano en 3 dimensiones, añadir físicas y que la luz siga a nuestro personaje.

Tras abrir Unity 3D vamos a File -> New Project…

Hay que darle en New Project...

Entonces nos pedirá la localización del proyecto donde pondremos lo que queramos, lo que si es importante es Importar el paquete de Scripts.

Tras seleccionar Scripts.unity le damos a Create, bueno después de unos instantes nos saldrá el editor para que empecemos a jugar.

Primero vamos a crear lo que sería nuestro suelo para ello vamos a crear un plano, esto se hace así GameObject -> Create Other -> Plane y luego haremos lo mismo pero creando un Cilindro (Cylinder).

Luego en Hierarchy vamos al Plano (Plane) y en el inspector le pondremos en el componente Transform lo siguiente: Posición X = 0, Y = 0, Z = 0.
Con el Cilindro pondremos de posición X = 0, Y = 0.5 y Z = 0 y en Scale X = 0.5, Y = 0.5, Z = 0.5, el cilindro quedará como en la foto.

Tras estos primeros retoques la escena quedará ya realizada, pero sin luz por lo que al tocar «Play» no veremos sino algo oscuro, así que vamos a crear una luz para poder ver algo, como siempre vamos a GameObject -> Create Other -> SpotLight y finalmente la pondremos en el X=0, Y = 6, Z = -5, en la rotación (Rotation) con X=45, Y=0, Z=0 y en el componente de Light le aumentaremos la intesidad a 3.

Ahora le pondremos a la cámara un script de los que Unity trae por defecto se trata en la Zona de Project abrir Standard Assets -> Scripts -> Camera Scripts y arrastrar ahí el script SmoothFollow a Main Camera del Hierarchy, miramos que en Main Camera tenemos un componente llamado Smooth Follow y en Target le arrastramos el Cylinder desde el Hierarchy, ese script lo que hace es seguir a un target (Objetivo) en nuestro caso ha sido el cilindro podríamos haber puesto cualquier otro elemento, pero nuestro «personaje» hoy será el cilindro.

Con esta cámara tenemos un efecto de juego en 2.5D como las cámaras de NewSuper Mario o Donkey Kong, evidentemente salvado las distancias, aunque con los parámetros que nos dejan modificar desde el Inspector podemos acercar o alejar la camara y algunas cosas más que hacen que merezca la pena usar un script creado antes que crearlo nosotros para este ejemplo.

El siguiente paso será que nuestro cilindro se mueva para ello tendremos que crear un nuevo script, en Project-> Create -> C Sharp Script, le cambiamos el nombre haciendo click a la derecha del nombre tras seleccionarlo y lo llamamos Controller, puede ser cualquier nombre pero nosotros vamos a usar ese, ahora lo abrimos haciendo doble click.

Ahora borramos todo lo que hay en el código y ponemos esto:

using UnityEngine;
using System.Collections;

public class Controller : MonoBehaviour {

// Constructor
void Start () {

}

// En cada frame se llama a esta función
void Update () {

//Al pulsar el botón A cambiamos la posición de nuestro personaje hacia la izquierda
if(Input.GetKey(KeyCode.A))
transform.position -= Vector3.right * Time.deltaTime;
//Al pulsar el botón D cambiamos la posición de nuestro personaje hacia la izquierda
if(Input.GetKey(KeyCode.D))
transform.position += Vector3.right * Time.deltaTime;
//Al pulsar el boton S iremos hacia adelante
if(Input.GetKey(KeyCode.S))
transform.position -= Vector3.forward * Time.deltaTime;
//Al pulsar el boton W iremos hacia atrás
if(Input.GetKey(KeyCode.W))
transform.position += Vector3.forward * Time.deltaTime;

}
}

Este código es bastante sencillo lo único que hace es que al pulsar una de las teclas A,S,D,W mueve el cilindro cambiando la posición en el eje X o Z, en una unidad por el tiempo que ha pasado entre frames, esto se hace para que si una CPU calcula mucho frames y otra pocos al final el espacio recorrido en 3 segundos sea el mismo.

Ahora vamos a hacer que al cilindro le afecten las físicas, gracias a Physix de NVIDA que viene con Unity esto se hace con un par de clicks, con el cilindro seleccionado vamos a añadir un nuevo componente, este está en Components -> Physics -> RigidBody si ahora le damos a PLAY el cilindro se caerá, ¿por qué? porque el plano no tiene una superficie con la que chocar es invisible para las físicas así que seleccionamos el plano y en el Inspector le damos en el componente Mesh Collider a Smooth Sphere Collision a Convex.

Si le damos a Play ahora el cilindro se caerá así que tenemos que cambiar en la componente de RigidBody las Constantes (Constraints) y poner en la rotación Freeze Rotation en todos los ejes.

En el código después de hacer que se mueva hacia atrás ponemos la siguiente línea:

//Al pulsar el boton espacio saltaremos
if(Input.GetKeyDown(KeyCode.Space))
rigidbody.AddForce(Vector3.up * 200);

Como el código es tan simple si tocamos varias veces espacio haremos dobles saltos y hasta N saltos en el aire.
Ahora vamos a crear un nuevo script llamado Follow para que la luz siga a nuestro personaje

using UnityEngine;
using System.Collections;

public class Follow : MonoBehaviour {

//El objetivo que vamos a seguir
public GameObject objetivo;

void Update () {
//La posición de la luz será la misma que la que habíamos dicho antes, pero ahora estará vinculada con la posición del personaje para que lo siga.
transform.position = new Vector3 (objetivo.transform.position.x, objetivo.transform.position.y + 5, objetivo.transform.position.z – 5);
}
}

Se lo añadimos a nuestra SpotLight y luego en objetivo le ponemos el cilindro y con esto ya tenemos que la luz siga al personaje.

Si te has quedado sin entender algo puedes preguntarnos en los comentarios, además de descargar el proyecto del tutorial aquí