Si yo fuera un modelo de datos de una maquina, habría variables que me describirían incluyendo una para hacer referencia a mi nombre (un valor que no ha cambiado) y mi edad (un valor que cambia cada año). Algunas variables solo pueden ser determinadas por mí, como el nombre de mi película favorita, y otras pueden ser controladas total o parcialmente por fuentes externas, como mi estado civil. Todos estos tipos de relaciones se pueden modelar en código cuando conoces el vocabulario correcto. En esta lección, crearemos una clase que implemente el ejemplo que acabo de proporcionar, y luego usaremos nuestra comprensión de variables para animar un sistema solar de ejemplo, todo con un solo script.
Modelando un humano
Para comenzar, crea una nueva escena. Agregue un GameObject vacío (de la barra de menú elija “GameObject -> Create Empty”) y luego en el inspector haga clic en el botón “Agregar componente”. Escriba “Humano” en el mensaje que aparece. No hay componentes con este nombre, por lo que debe mostrar “New Script”. Presiona la tecla Enter y verás las opciones del script. Asegúrese de que la opción Languaje esté configurada en C# y presione la tecla Intro nuevamente. Esto creará un nuevo script y lo adjuntará al objeto simultáneamente. Finalmente, “duplica” el GameObject para que tengas dos “Humanos” (desde la barra de menú, elige “Editar -> Duplicar” o Ctrl + D).
Abra el script “Humano” en Visual Studio (o su editor de código favorito). Para nuestra primera tarea, descubramos cómo nombrar a nuestros Humanos. Todos los GameObjects ya tienen una variable para almacenar su nombre. Esto es lo que ve listado en el panel Jerarquía y en la parte superior del Inspector. Nuestros dos objetos actualmente tienen el nombre “GameObject”, que personalmente considero que es un nombre terrible para un ser humano. Como nota al margen, cuando ves el nombre de tu GameObject, estás “leyendo” o “obteniendo” la variable. Ahora, vamos a “escribir” o “establecer” la variable. Proporcione un nombre que suene diferente para cada persona tipeando en el campo en el inspector.
Ahora, eso no fue muy de programador, ¿verdad? Veamos cómo haríamos lo mismo a través del código. Modifique el método de Start de la siguiente manera:
private void Start()
{
Debug.Log("Hola, mi nombre es " + name);
}
Guarde su script y ejecute la escena. Debería ver dos mensajes imprimirse en la ventana de la consola (un mensaje enviado por cada humano que creó).
Lo que hemos hecho es “leer” o “obtener” el valor almacenado por una variable llamada “name”, anexarlo a la cadena de texto y pasar todo el mensaje como un parámetro al método que imprimió el mensaje a la consola.
Para “escribir” o “establecer” una variable, usamos algo llamado operador de asignación. Para confundirte, los programadores de hace mucho tiempo decidieron que este operador se vería como el signo igual (y para comprobar realmente la igualdad, decidieron usar DOS signos iguales - suspiro). Entonces, para “escribir” o “establecer” una variable, puede usar una línea de código como la siguiente:
name = "Moon";
Si tuviera que poner esa línea de código en el método de Start sobre la línea que imprime el mensaje “Hola”, cuando ejecute la escena, tendrá dos personas que lo recibirán con el mensaje “Hola, mi nombre es Moon”. Mientras que la escena continúe, Moon será su nombre, como puede verificar en el inspector. Sin embargo, cuando detenga la escena, los nombres volverán a los valores que tenían antes de presionar play (en mi caso, Moon y Andrea). Lo que sucedió es que Unity está haciendo algo llamado “Serialización”: básicamente, esto significa que cualquier variable en un GameObject que Unity “comprenda” se guardará en el momento de edición con la escena como valor de inicio predeterminado. Esto le permite configurar sus niveles y asegurarse de que una escena comience de la misma manera todo el tiempo.
Campos
GameObjects no tiene una variable por edad, así que a continuación le mostraré cómo “declarar” su propia variable llamada “campo”. Como mínimo, todas las variables deben declararse con dos cosas, un “Tipo de datos” y un “Nombre” mediante el cual usted “identifica” la variable. Agregue la siguiente línea a su script, dentro de la clase (entre los corchetes), pero fuera de los métodos. Por convención, la mayoría de las variables de nivel de clase aparecen en la parte superior de la clase.
int edad;
Consejo:
Hay muchos tipos de datos que puede elegir al declarar sus variables (consulte la lista completa aquí: http://msdn.microsoft.com/en-us/library/ms228360(v=vs.90).aspx) . Los más comunes que querrás incluir son:
- bool - este es un tipo de valor que por defecto es falso, y solo puede contener “falso” o “verdadero”
- int - este es un tipo de valor que por defecto es 0, y puede contener números enteros, por ejemplo: 42
- float: este es un tipo de valor que por defecto es 0 y puede contener números con decimales, por ejemplo: 3.14159f
- string - un tipo especial que tiene como valor predeterminado null (nothing), y puede contener matrices de caracteres,como: “Hola”
Como es usuario de Unity, también usará con frecuencia otros tipos de datos, como:
- GameObject: este es un tipo de referencia cuyo valor predeterminado es nulo y puede contener una referencia a un GameObject activo.
- Transform: este es un tipo de referencia que por defecto es nulo y puede contener una referencia a una Transformación activa.
- Vector3 - este es un tipo de valor (una estructura) que por defecto es cero en cada uno de sus subcampos (x, y , z).
- RigidBody: este es un tipo de referencia que se establece por defecto en nulo y puede contener una referencia a un RigidBody activo.
Hay varias otras palabras que pueden aparecer en una declaración de variable antes del tipo de datos. “Solo lectura” indica una variable que solo se puede asignar en el momento en que se crea un objeto (por una clase “Constructor” o mientras se inicia la variable). “Const” es similar, pero debe inicializarse solo en la declaración. “Estático” indica una variable que pertenece a la clase misma en lugar de instancias de la clase (por ejemplo, muchas de las variables en la clase “Time” son estáticas).
Al declarar la variable en el nivel raíz de nuestra clase, le estamos dando un “alcance” de nivel de clase. Esto significa que la variable será visible en todas partes dentro de la clase, incluso dentro de los métodos dentro de la clase. Tenga en cuenta que también puede declarar variables dentro de un método, pero su alcance será más limitado, y otros métodos o clases no tendrán acceso a esas variables.
Guarde su script y regrese a Unity. Mira a uno de tus Humanos e intenta establecer la variable de edad que acabamos de declarar … oye espera, ¿por qué no lo vemos? Las variables tienen algo llamado “visibilidad”. Cuando no se especifica la visibilidad de una variable, se predetermina a “private”, lo que significa que solo su clase sabe que existe. Unity solo muestra variables que están marcadas como “public” o por “[SerializeField]”, que es una forma especial de exponer una propiedad al inspector de Unity sin permitir que otros scripts conozcan la variable. Cualquiera de las siguientes líneas permitirá que su variable aparezca en el inspector, aunque por ahora, usaremos la declaración “public”:
public int edad;
Con la variable de edad establecida en público, ahora puede verla aparecer en el inspector. ¿Por qué tiene un valor ya? Nuestra variable de edad se inicializó a cero, porque ese es el valor predeterminado para ese tipo de datos, y porque no lo inicializamos a ningún otro valor en nuestra declaración. Podríamos haber definido nuestra variable de la siguiente manera:
public int edad = 25;
Esta declaración se ve como lo hacía antes, con la excepción de que también tenemos el operador de asignación y un valor que está asignado como predeterminado. Si modifica su script para usar este ejemplo y luego regresa a Unity, los scripts Humanos todavía mostrarán cero. Esto se debe a que 25 solo se asigna cuando el objeto se “crea” por primera vez, pero nuestros scripts ya se han creado y serializado con el valor de 0. Si creaste un GameObject nuevo y agregaste el script Humano, el nuevo Humano tendría el valor predeterminado aplicado. También puede hacer clic en el ícono de ajustes de nuestro script y seleccionar “Reset”, que volverá a cargar los valores predeterminados en el script.
Propiedades
En caso de que quiera establecer restricciones sobre la forma en que una variable puede cambiar, puede usar algo llamado “propiedad”. Las propiedades se parecen mucho a “campos”, que es lo que creamos con nuestra variable “edad”, con una gran diferencia: la propiedad “encapsula” el campo, lo que significa que las secuencias de comandos externas no tienen acceso directo al campo, y usted puede controlar lo que se les permite leer o escribir. A continuación se muestra un ejemplo de una propiedad.
public string PeliculaFavorita { get; set; }
Con la excepción de los corchetes, y las palabras “get” y “set”, esto se ve muy similar a la declaración de un campo. Debe incluir un “getter” o “setter” (las palabras clave get y set), pero no tiene que especificar ambos. Además, puede tener diferentes niveles de visibilidad para cada uno. En este ejemplo, otros scripts podrán leer y escribir el valor de PeliculaFavorita. Podemos cambiarlo así:
public string PeliculaFavorita { get; private set; }
Ahora, otras clases pueden descubrir cuál es la película favorita de este Humano, pero no tienen la capacidad de cambiarla. Solo los métodos dentro de la clase Humana pueden modificar el valor almacenado.
Aunque esta propiedad es pública, no podrá verla en el Inspector. Las propiedades en realidad no almacenan ningún dato como lo hace un campo. Lo que está sucediendo es que el compilador de C# crea automáticamente otro campo, que es “privado” y lee ese valor cuando usa el captador, y escribe en el campo cuando usa el colocador. La siguiente forma es funcionalmente idéntica, pero ayuda a ilustrar la anterior versión.
public string PeliculaFavorita {
get {
return _peliculaFavorita;
}
private set {
_peliculaFavorita = value;
}
}
private string _peliculaFavorita;
Si especifica su propio “get y set” para una propiedad de esta manera, también puede agregar la etiqueta “[SerializeField]” y obtendrá lo mejor de ambos mundos: sus datos están encapsulados y aún puede configurarlos fácilmente con el inspector. Por otro lado, también agrega mucho código a sus scripts de esta manera, y puede ralentizar el desarrollo y hacer que un script sea más difícil de leer y mantener. Por lo general, solo convierto mis campos en propiedades si necesito limitar el acceso de alguna manera o responder cada vez que cambia un valor.
Modelado de un sistema solar
El ejemplo humano sirvió para ilustrar vocabulario diverso para variables, pero no fue muy inspirador porque no “hizo” nada. Hagamos un ejemplo más interesante y permitamos que las variables que definimos tengan un efecto sobre otra cosa.
Crea una nueva escena para trabajar. Si tienes algunas texturas bonitas y sabes cómo aplicarlas a las esferas, crea una esfera y aplica tu textura. De lo contrario, simplemente crea un cubo, para que sea más obvio cuando usamos el código para hacer que gire (desde la barra de menú elige “GameObject -> 3D Object -> Cube”).
Cree un nuevo script llamado “Motor” y ejecútelo de la siguiente manera:
using UnityEngine;
using System.Collections;
public class Motor : MonoBehaviour
{
public float vel;
void Update () {
transform.Rotate( new Vector3(0, vel * Time.deltaTime, 0) );
}
}
En la línea 6 de este script definimos la variable “vel”, que determinará la velocidad en “grados por segundo”, en la cual un objeto está girando. Por ejemplo, para hacer que un objeto gire en un círculo completo cada segundo, le conviene asignar el valor “360”. Esta unidad de medida haría un gran calculo, aunque lo excluí porque lo estoy explicando aquí y quiero mantener los fragmentos de código pequeños. Tenga en cuenta que no asignamos el valor de la variable en ningún lugar de este script porque se supondrá que la persona que lo utiliza lo asignará en el editor o mediante otro script.
En la línea 8 vemos un método que se incluye en el código de plantilla predeterminado llamado “Update”. Unity llama a este método una vez por frame para permitirle modificar los objetos del juego de forma incremental. Los sistemas rápidos pueden llamar a este método 100 veces por segundo, mientras que las máquinas lentas o los teléfonos móviles solo pueden llamarlo 30 veces por segundo. Si tuviera que hacer cambios en objetos usando valores fijos, entonces parecería jugaria “más rápido” en la mejor máquina. Para crear una muestra que parece ejecutarse a la misma velocidad en todas las plataformas, multiplicamos nuestra velocidad deseada por “Time.deltaTime”, de modo que la tasa real que usamos no es constante, sino que varía según la cantidad de tiempo que realmente ha pasado. Por ejemplo, en el teléfono móvil, el ejemplo Time.deltaTime podría ser 0.0333 … y en el equipo más rápido algo así como 0.01 en su lugar.
La declaración que ponemos dentro del método es compuesta (está haciendo muchas cosas). Podría haber sido más detallado como en el siguiente ejemplo que es funcionalmente equivalente:
float velFrameLimitada = vel * Time.deltaTime;
Vector3 rotacion = new Vector3(0, velFrameLimitada, 0);
transform.Rotate( rotacion );
Este ejemplo muestra que creamos una nueva variable float que contiene la velocidad de giro para este frame. También creamos un Vector3, que es una variable struct especial. Usted crea el Vector3 usando algo llamado “Constructor” que toma tres parámetros que representan valores para tres ejes: X, Y y Z. Finalmente, pasamos el Vector3 a un método que realmente rota el objeto mismo.
A veces, expandir el código de este modo lo ayuda a ser más legible, pero en este caso, sentí que definirlo en una sola línea estaba bien. Algunas de las razones por las que me hubiera ido con la versión posterior incluyen:
- necesidad de usar las variables en más de un lugar
- la línea individual es excesivamente larga, así que tendría que desplazarme horizontalmente para leerlo todo
- el paréntesis anidado puede ser confuso
Adjunte el script del motor a cualquier objeto 3D que haya terminado de crear, y asigne un valor a vel. Ejecuta la escena. Tenga en cuenta que incluso puede cambiar la vel mientras el juego se está ejecutando y la velocidad del motor se actualizará instantáneamente.
Detener la escena (porque los cambios realizados en el modo de reproducción no se guardan). Duplica tu objeto 3D y escalalo hacia abajo. Trataremos nuestro primer objeto como si fuera la “Tierra” y el nuevo objeto más pequeño como si fuera la “Luna”. Puede hacer que cada objeto gire en su eje a una velocidad diferente. ¿Pero cómo puedes usar el mismo script para hacer la órbita lunar alrededor de la Tierra? La respuesta es usar jerarquía de objetos. Crea un GameObject vacío. Junta la luna con el GameObjeto vacío y luego desplazalo lo suficiente como para que quede fuera de la “Tierra”. ¡Ahora agrega el script Motor al Empty GameObject y verás la órbita de la luna alrededor de la tierra mientras gira su eje! Tenga en cuenta que el efecto será más pronunciado si la velocidad a la que la Tierra gira es diferente de la velocidad a la que gira el objeto en órbita. Si emparejaste tanto la tierra como el objeto de juego vacío con otro objeto de juego vacío, podrías hacer que orbitasen alrededor del sol. De esta forma, puedes construir lentamente la complejidad de tu escena hasta que hayas creado un modelo funcional del Sistema Solar.