viernes, 13 de diciembre de 2013

U0) Bases de programación en Unity

¡Muy buenas!

Ya estamos por aquí a puntito de comenzar con Unity. No entraré en detalles de cómo lo podéis instalar o del propio interfaz. Me centraré en algunas clases prácticas de cómo realizar un pequeño juego de plataformas con Unity 3D. Desde luego, va a ser un viaje sencillo y con clases bastante básicas. Muy posiblemente entre en algunos detalles de dilemas con los que os podéis encontrar a la hora de tomar decisiones de cómo realizar ciertas cosas con código. Tomadlo como lo que es, una de las opciones que tenéis disponibles y recordando, que procuraré ser lo más claro y sencillo posible (tampoco quiero profundizar en estructuras que puedan resultar complicadas si no es necesario).

Por supuesto, recordad una cosa: Hay mil formas de hacer las cosas. Lo importante es que os encontréis cómodos a la hora de trabajar con el código que estáis haciendo y que, vosotros mismos, os deis cuenta de aquellas estructuras o formas de realizar vuestro código que se adecuen a aquello que queréis crear. Y recordad una gran máxima: "Si parece que funciona, funciona". No os volváis locos con la perfección, ni intentéis crear el código perfecto. Simplemente porque creo que es como buscar el Santo Grial. Simplemente, cread aquello que necesitéis y si más adelante necesitáis más modularidad en el código o cosas más complejas, vosotros mismos os daréis cuenta de ello. Tampoco os obsesionéis con el rendimiento: Primero que las cosas funcionen y después le dais al coco de cómo se puede hacer más eficiente, SÓLO si os hace falta. No tiene sentido si vuestro juego va con la fluidez que deseáis poneros con quebraderos de cabeza cuando os podéis poner a trabajar en otras partes del mismo.

Organizando las cosas. Jerarquía de carpetas

Es muy importante tener las cosas en un sitio donde, más adelante, podáis encontrarlas fácilmente. Igual que en UDK colocábamos un prefijo en todos los scripts para saber que son nuestros, aquí tendremos que trabajar con muchas otras cosas que no son código, y conviene que os creéis una jerarquía de carpetas donde situar cada cosa, del mismo modo que crear una nomenclatura clara para la denominación de cada cosa (normalmente indicada en un documento técnico de diseño).
Mi recomendación:

En la jerarquía de carpetas (La pestaña Project en Unity), podéis crear bajo la carpeta Assets todas las carpetas que deseéis. Allí crearía algunas básicas...

- Scenes, para guardar las escenas de juego en su interior.
- Code, para colocar vuestros scripts de código y clases. Podéis subdividir tanto como queráis (Navigation, CameraControl, Abstract, Shaders...)
- Materials, para colocar materiales. Se recomienda jerarquizar subcarpetas en su interior también.
- Models, para colocar mallas importadas en FBX o en el formato que deseéis.
- Textures, para texturas.
- Prefabs, para colocar, cuando veamos lo que son más adelante, los prefabs que deseemos. 

GameObject, la base de Unity


Muy bien, pues como podéis ver, Unity funciona con escenas que debéis guardar en disco (como si fueran los mapas de UDK). Arriba, en la barra de opciones tenemos GameObject. Si pinchamos en él, nos aparecerá Create Empty o Create Other. Básicamente, la primera opción nos generará un GameObject desnudo. ¿Y qué es un GameObject? Pues es una entidad en Unity que tiene un Componente llamado Transform que es el encargado de darle una posición en el espacio y una orientación.
¿Interesante verdad? Se parece mucho a los ActorComponents de UDK. Pero no es igual. Como vemos, podemos pinchar en crear un GameObject y nos aparecerá un gizmo con las coordenadas del espacio en la pestaña de Scene y también en la pestaña de Hierarchy. Si lo pinchamos o seleccionamos tanto en la pestaña de Scene o de Hierarchy, en la pestaña inspector normalmente a la derecha, nos encontraremos con que aparece el componente transform el cual podemos toquetear al gusto.
Bien, visto esto, ya tenemos claro dónde podemos encontrar las cosas en Unity:
En la pestaña Hierarchy aparece el árbol de escena, con todos los objetos e hijos de estos (Que aparecerán tabulados bajo sus padres y que podremos desplegar pinchando en la flecha que tendrán los mismos a su izquierda).
En la propia escena, en la pestaña Scene, podemos seleccionar elementos. 
En la pestaña Inspector, veremos los componentes que componen cada GameObject y sus variables "visibles" para poder modificarlas.

Dicho esto, vemos la primera gran utilidad de Unity. Los Componentes no son más que clases de código que heredan de una clase especial llamada MonoBehaviour que les proporciona los eventos y llamadas a métodos necesarios por el motor. Así mismo, las variables globales de dichas clases, son mostradas por el Inspector y lo mejor: PUEDEN MODIFICARSE EN TIEMPO REAL mientras se ejecuta el juego.

¿Cómo se hace esto? Muy sencillo: Veis que arriba existen botones parecidos a los de un reproductor de música. Efectivamente, el botón con flecha es para comenzar a ejecutar todos los scripts MonobeHaviour de los GameObjects en la escena actual. En la pestaña Scene se mostrará exactamente lo que la cámara principal renderice en ese momento.

Para hacer una pequeña prueba de lo que os comento, basta con crear en la pestaña GameObject->Create Other->Cube. Lo colocamos en la pestaña de Scene en una posición en la que podamos verlo por la cámara. Si no tenemos cámara, es el momento de crear una en la misma pestaña GameObject->Create Other->Camera.
También necesitaremos una luz para poder iluminar nuestra escena: GameObject->Create Other-> Point Light.

Muy bien, ya podemos ejecutar nuestra escena y seleccionando nuestro Cube en Hierarchy, podremos modificar su posición numéricamente, yendo a la pestaña Inspector y modificando su Position dentro de su Componente Transform.

Componentes

¿Cómo añado un componente? Existen algunos ya "creados" en Unity para, por ejemplo, utilizar el motor de físicas con sus colisiones y rigidbody. Así mismo, para poder tener ciertas mallas de figuras geométricas simples.
En la barra superior, tenemos la pestaña Component con un listado de dichos componentes.

Es importante señalar la importancia de los componentes RigidBody, en el apartado Physics así como Box Collider, o Capsule Collider.

¿Cómo creamos nuestros scripts de comportamientos? Podemos hacerlos de muchas maneras. Desde la pestaña de Project, podemos con el botón derecho crear un script de programación (Create X Script). En este curso, utilizaremos C# como lenguaje para nuestros scripts, al tener riqueza de librerías externas para utilizar y al ser un lenguaje que podemos utilizar fuera de Unity al ser utilizado para programación en general. Javascript puede resultar más sencillo, pero para mi gusto está más relacionado con la programación web y es allí donde debe permanecer :)

Nuestro primer contacto con la API de Unity (Script reference) Clase MonoBehaviour

Una vez creéis un script (acordaos de colocarlo en un lugar ordenado dentro de vuestra jerarquía de carpetas para no sembrar el caos), podéis hacer doble click en él y acceder desde el IDE que instala por defecto Unity: Monodevelop. Como veréis es muy sencillo de utilizar y posee herramientas más que de sobra para comenzar con Unity. Se puede utilizar otros IDEs, y se deben especificar en la barra de arriba, Edit->Preferences->External Tools. Monodevelop está perfectamente configurado e integrado para trabajar con Unity de modo que a vuestra elección queda utilizar otro (aunque nosotros utilicemos Monodevelop para este curso, que además es gratuito :D ).

Bien, la misma jerarquía de assets que tenemos en el proyecto de Unity debería apareceros en Monodevelop, de modo que podéis buscar y crear scripts desde el propio IDE. Además, se puede debuggear "attachando" al hilo de ejecución de Unity para que lo inspeccione (la tecla que tiene una flecha grande, que al pulsarla os indicará con el hilo que deseáis trabajar).

Ahora si llega el momento de conocer dónde se encuentra la documentación de referencia de Scripts para Unity: http://docs.unity3d.com/Documentation/ScriptReference/

Lo primero que haremos será echarle un vistazo a la clase MonoBehaviour:

En el apartado Messages podemos ver todos los eventos a los que llama el motor y el momento en el que lo hace documentado. Los más básicos que nos interesan ahora mismo son:

- Awake, Como nos indica la documentación, se llama después de que se hayan cargado todos los objetos en escena, de modo que es un sitio seguro para buscar otros objetos en la escena y guardarnos una referencia de ellos. Además, sólo se llama una vez en todo el tiempo de vida del objeto. Se llama aunque el componente no esté activo por defecto en la escena (todos los componentes tienen un tick en el inspector para activarlos o desactivarlos). Ideal para utilizar como "Constructor" o método que configura referencias y valores de variables y atributos.
- Start, Llamado antes de que se realice la primera llamada al evento Update. Del mismo modo que Awake, se llama sólo una vez en el tiempo de vida del objeto. Pero no se llamará si el script no está activo en el momento de inicialización. Ideal para utilizar como "Constructor" o método que configura referencias y valores de variables y atributos.
- Update, Llamado cada "tick" del motor o frame. Mediante la clase estática Time.deltaTime, no deja de ser la clase principal para colocar en el "loop" de ejecución del programa. Aquí se realizará la mayor parte del comportamiento del script.
- LateUpdate, Como su nombre indica, se ejecuta después de las llamadas de Update. Es interesante su uso, por ejemplo, para recolocar cámaras que siguen ciertos objetos, ya que éstos se habrán movido con anterioridad en la llamada a Update.
-FixedUpdate, Recomendado su uso como un Update cuando tenemos que interactuar o utilizar temas de físicas.
-OnEnable, Cuando el componente se activa, se hace una llamada a este método.
-OnLevelWasLoaded, Como su nombre indica, al cargar una escena, se realiza una llamada a este método. Muy útil para recorrer todos los objetos de la escena y cargar valores que se deseasen haber guardado (SaveGameManager).

Además de todos los eventos que se llaman, también tenemos variables importantes para el comportamiento de los Componentes:

- enabled, booleano que indica si está activo o no dicho componente.
- renderer, referencia al primer componente de tipo Renderer que tenga el GameObject si tiene alguno, si no será null
- rigidbody, referencia al componente RigidBody del GameObject si tiene alguno, si no será null
- transform, referencia al componente Transform del GameObject (con el que le damos la posición, posición local y rotación del GameObject)
- name, nombre del GameObject

Mi primer script de navegación, ¿Qué componentes voy a necesitar?

Para realizar un primer ejemplo de navegación, necesitaremos:

- Components->Physics-> RigidBody
- Components->Physics-> Capsule Collider 

Si al añadir el RigidBody se os coloca ya un Collider, podéis con el botón derecho en el Inspector, eliminar dicho componente con Remove Component.

Al igual que hemos hecho antes, es interesante ver el funcionamiento de RigidBody y de los Collider. 

En Monobehaviour debemos consultar para las colisiones:

- OnCollision*
- OnTrigger*

Y en RigidBody es importante leer la documentación completa de cómo funciona.

Un poco de mates. Métodos y funciones interesantes antes de empezar.

Para empezar, tenemos que tener claros algunos métodos y funciones utilizadas en las clases Vector3 y Quaternion. Estas clases son las utilizadas para colocar puntos y vectores en el espacio y rotaciones respectivamente.
Para ello, debemos volver de nuevo al Script reference:

Funciones matemáticas con float.

No hay mucho que explicar. Todo son funciones matemáticas para trabajar con float. Entre las más utilizadas están Sqrt para hacer raíces cuadradas (recordad no abusar ya que es costoso aunque la implementaciónd e Unity espero sea eficiente y aproximada, también tened cuidado con eso), así como el clásico Lerp para realizar interpolación lineal entre dos valores. Encontraremos Lerp también en la clase Vector más adelante y en Quaternion.

Funciones matemáticas con Vectores de 3 dimensiones:

El método Normalize() hace que el Vector3 desde el que lo llamemos, se quede normalizado. Si es demasiado pequeño, se convertirá en un Vector3 vacío (0,0,0). También podemos devolver una copia del mismo utilizando su variable normalized.
Es interesante destacar la interpolación lineal entre vectores con Lerp http://docs.unity3d.com/Documentation/ScriptReference/Vector3.Lerp.html

Mediante RotateTowards, utilizaremos un Vector como una dirección que como un punto en el espacio. Claramente su uso nos vendrá bien para poder orientar nuestro personaje mediante vectores (luego veremos con Quaternion cómo conseguir un vector o cómo convertir un vector dirección en un Quaternion).

El código que aparece es exactamente de lo que os hablo a la hora de calcular y modificar rotaciones de nuestro personaje.

Funciones y métodos con Quaterniones

Para representar rotación, Unity utiliza cuaterniones. Las funciones Euler, Lerp y LookRotation (así como el método SetLookRotation) son las más comúnmente utilizadas. La primera sirve para rotar con grados euler, la segunda para realizar interpolación lineal entre dos Quaterniones (que también puede hacerse esféricamente mediante Slerp) y LookRotation nos va a devolver un Quaternion, a partir de un Vector dirección y un vector que apunte hacia arriba.

Al contrario que UDK, debemos darnos cuenta de que las coordenadas de mundo, están colocadas de diferente forma. El eje Y es el correspondiente a up o altura, mientras que el eje Z es el correspondiente a forward (o mirar hacia delante). 

Recoger el Input de teclado o periféricos.

Unity tiene una clase estática llamada Input (http://docs.unity3d.com/Documentation/ScriptReference/Input.html) donde almacena cada frame los estímulos recibidos por periféricos. Se pueden configurar "Buttons" y "Axis" (hacer que esos estímulos hagan referencia a un nombre específico). Buttons es, como su nombre indica, para pulsaciones (tiene eventos propios para comprobar si el botón se ha pulsado, si se ha dejado de pulsar, para consultar su valor... GetButtonDown, GetButtonUp), Axis para simular ejes como crucetas de mandos. Un Button o un Axis no deja de ser un valor entre dos float (normalmente -1 y 1 para los Axis y 0 y 1 para los button) que dependen del periférico en cuestión relacionado. Podemos acceder a teclas directamente con GetKey* conociendo los códigos de las letras por defecto que podemos utilizar (http://docs.unity3d.com/Documentation/ScriptReference/KeyCode.html). Si trabajamos en otras plataformas, debemos consultar la documentación para conocer qué KeyCode corresponde a dichos inputs.
Para configurar cómo se tratarán los valores de los inputs, debemos buscar en la barra Edit->Project Settings->Input. Ahí nos aparecerá en la ventana de Inspector los valores de los input por defecto que tenemos configurados. 
Podemos obtener el valor sin modificar por Unity de los Input utilizando funciones que contienen la palabra Raw como GetAxisRaw. Esto es interesante si queremos realizar nuestra propia modificación de los valores de Input (los mandos de consola por ejemplo tienen una DeadZone, que es un área en la que los valores que nos devuelven pueden no ser fiables ya que por el paso del tiempo, la propia fabricación del mando, pueden tener desviados su centro). Es muy interesante el siguiente artículo en caso de que queráis aventuraros a realizar vuestro propio "wrapper" clase estática con vuestro tratamiento de input (cosa harto interesante si vais a trabajar vuestro proyecto en diferentes plataformas). (http://gamasutra.com/blogs/JoshSutphin/20130415/190541/Doing_Thumbstick_Dead_Zones_Right.php)

Manos a la obra. Scriptando que es gerundio.






No hay comentarios:

Publicar un comentario