UNITY Operaciones sobre objetos 3D

De MediaWiki
Ir a la navegación Ir a la búsqueda

Introducción

  • A todo objeto 3D se le pueden aplicar tres operaciones:
  • Escalado: Hacerlo más grande. Podemos hacerlo más grande en todos los ejes (x,y,z) o solo en alguno de ellos, provocando deformaciones.
  • Rotación: Podemos rotarlo en cualquiera de los tres ejes (x,y,z)
  • Traslación: Podemos trasladar el objeto en cualquiera de los tres ejes (x,y,z). Esto es, moverlo.


  • Como comentamos anteriormente, toda figura 3D se dibuja en el punto (0,0,0) y después se le aplican unas modificaciones para visualizarlo en la posición y forma correctas.
Esto es posible gracias a una serie de operaciones matemáticas con matrices.
Cada uno de los objetos 3D tiene asociado una matriz de modelado (una matriz matemática de 4x4).
Inicialmente, cuando dibujamos un objeto 3D se dibuja en el punto 0,0,0 y sin rotación ya que su matriz de modelado es la matriz identidad. Dicha matriz tiene unos en una de las diagonales.
Cuando indicamos que el objeto se tiene que desplazar, rotar o escalar, estamos modificando su matriz de modelado (para nosotros es transparente) y una vez modificada, se aplica dicha matriz sobre cada uno de los puntos del objeto 3D y por eso se dibuja en la posición correcta, con la escala y con la rotación indicada.



LIBGDX UD4 Animacion 4.jpg



  • En Unity3D accedemos a esta matriz de forma gráfica en la ventana de Inspector, bajo la sección Transform:
Unity3d operaciones 1.jpg



  • En Unity3D accedemos a esta matriz en los scripts teniendo una referencia a un gameobject y sobre él, accediendo a la propiedad transform.
Si queremos acceder al transform del gameobject asociado al script (el script se encuentra gráficamente asociado a un gameobject) podemos poner: transform (en minúscula).
Unity3d operaciones 1B.jpg


Nota: En el ejemplo se está creando una variable de tipo Transform para guardar el transform local del GameObject asociado al script. Esto no es necesario ya que siempre vamos a poder acceder al mismo escribiendo transform con minúsculas.
Crear una variable de tipo Transform es necesario cuando queremos referenciar el transform de otros GameObhects diferentes.




Escalado

  • En la siguiente imagen podéis observar un cubo que mide 1x1x1 unidades (fijaros en la cuadrícula).
Unity3d escala 1.jpg


  • Al pulsar sobre el cubo aparece en el Window Inspector las propiedades del gameobject seleccionado (en este caso el cubo).
Unity3d escala 2.jpg


  • Ahora podemos modificar la escala. Lo podemos hacer de varias formas:
  • Tecleando la nueva escala en la caja de texto correspondiente.
  • Poniendo el ratón en la ventana Inspector, encima del eje que queramos cambiar la escala y manteniendo presionado el botón izquierdo mover a derecha o izquierda.


  • Seleccionado en la barra de herramientas la opción de escala (o pulsando la tecla E) y después, gráficamente sobre la figura, presionar el botón izquierdo del ratón sobre uno de los ejes de colores y sin soltar, mover el ratón.


  • Si pulsamos la tecla Ctrl' mientras escalamos, iremos escalando de una décima de unidad en una décima de unidad.


Rotación

  • Podemos rotar una figura en cualquiera de los tres ejes.
La rotación viene determinada por un número que representa la rotación en grados.
Así, 90 grados sería un cuarto de circunferencia de rotación, 180 sería la mitad y 360 una vuelta entera.


  • Al pulsar sobre el cubo aparece en el Window Inspector las propiedades del gameobject seleccionado (en este caso el cubo).
Unity3d rotacion 11.jpg


  • Para saber como se rota, debemos de tener en cuenta la siguiente regla.
Debemos imaginar que nuestra cabeza está atravesada por un palo, en cada uno de los ejes:



  • Veamos en ejemplo con una figura de un tigre:


  • Para rotar un objeto debemos de seleccionar en la Ventana de Escena (Scene Window).


  • Podemos rotar de varias formas:
  • Tecleando el valor en la caja de texto correspondiente.
Unity3d rotacion 10.jpg


  • Poniendo el ratón en la ventana Inspector, encima del eje que queramos aplicar la rotación y manteniendo presionado el botón izquierdo mover a derecha o izquierda.


  • Seleccionado en la barra de herramientas la opción de rotación (o pulsando la tecla E) y después, gráficamente sobre la figura, presionar el botón izquierdo del ratón sobre uno de los ejes de colores y sin soltar, mover el ratón.
Nota: No os extrañéis sin al modificar uno de los ejes veis que se modifica la posición. Eso es normal, ya que estamos moviendo la figura.


Pivot

  • Como comenté en otro punto, los objetos que arrastramos o creamos en la Hierarchy Window son GameObjects los cuales pueden ser dispuestos de forma jerárquica, unos 'dentro' de otros, como si fueran carpetas dentro de carpetas.
Al hacer esto, todas las propiedades 'transform' del GameObject hijo tienen unos valores relativos con respecto al GameObject padre.
Además, cualquier modificación que hagamos sobre el padre (rotar, trasladar o escala) también se reflejará en el hijo de la misma forma.
  • En todos los objetos 3D existe un punto denominado Pivot que indica al punto de referencia para rotar dicho objeto.
Normalmente este punto se encuentra en el centro del objeto 3D, pero no siempre es así.
Cuando importamos modelos complejos formados por múltiples figuras 3D puede ser que cada figura individual (imaginar un tanque con su torreta, o un avión con sus hélices, ruedas y ametralladoras) tenga su pivot en otro punto. En ese caso, cuando intentemos rotar, la rotación no la hará con respecto al punto que pensamos, sino con respecto a su pivot.
El pivot lo establece el diseñador gráfico con el programa 3D que haya utilizado.



  • Nota: Recordar por tanto que cuando trabéis con modelos importados, si cuando vayáis a realizar una rotación, el modelo no rota según lo esperado, comprobar donde se encuentra su pivot.



Ejes locales / globales de rotación

  • Otro aspecto ya comentado anteriormente es de los ejes X-Y-Z asociados a un GameObject.
Cuando rotamos un GameObject, sus ejes también son rotados.
Unity3d rotac 10f.jpg
Podemos comprobar como el cubo, al rotarlo -45º en el eje Z, sus ejes Y (color amarillo) y X (color rojo) también son rotados.


  • En cualquier momento podemos 'ver' cuales son los ejes del 'mundo' (los no rotados) si pulsamos sobre el botón que pone Local en la ToolBar (el texto del botón pasará a poner Global, para indicar que estamos viendo los ejes del mundo, no los ejes locales del GameObject):
Unity3d rotac 10g.jpg
Se puede observar como los ejes coinciden con los de Unity.


  • Veremos que cuando programemos por medio de 'scripts', podemos acceder:
  • A los ejes locales de la forma: transform.up (eje Y) / transform.forward (eje Z) / transform.right (eje X)
  • A los ejes globales (los no rotados): Vector3.up (eje Y) / Vector3.forward (eje Z) / Vector3.right (eje X)



  • Si pulsamos la tecla Ctrl' mientras rotamos, rotaremos de 15º en 15º.


Traslación

  • Podemos trasladar (mover) un objeto 3D en cualquiera de sus tres ejes.
Lo que haremos será desplazarlo un número de unidades siendo cada unidad uno de los cuadrados de la rejilla que conforman el 'suelo' del entorno de Unity3D.


  • Al pulsar sobre el cubo aparece en el Window Inspector las propiedades del gameobject seleccionado (en este caso el cubo).
Unity3d trasladar 2.jpg


  • Podemos trasladarnos de varias formas:
  • Tecleando el valor en la caja de texto correspondiente.
Unity3d trasladar 1.jpg


  • Poniendo el ratón en la ventana Inspector, encima del eje sobre el que queramos aplicar la traslación y manteniendo presionado el botón izquierdo mover a derecha o izquierda.


  • Seleccionado en la barra de herramientas la opción de traslación (o pulsando la tecla W) y después, gráficamente sobre la figura, presionar el botón izquierdo del ratón sobre uno de los ejes de colores y sin soltar, mover el ratón.



  • Si pulsamos la tecla Ctrl' mientras nos trasladamos, iremos moviendo la figura de unidad en unidad.



Ajustando los planos

  • Cuando tenemos varias figuras 3D y queremos 'colocar' unas adyacentes a otras, en cualquiera de sus 3 ejes (x,y,z) puede llegar a ser una operación 'complicada'.
Veamos un ejemplo:



Avanzado: Conceptos para programadores

  • Lo que viene a continuación son unas explicaciones teóricas para los alumnos que realizan el ciclo de Programación de aplicaciones multiplataforma.
  • Como comentamos anteriormente, las figuras 3D están formadas por múltiples triángulos.


  • Cuando se dibuja una figura 3D, la estamos dibujando siempre a partir del punto (0,0,0).
Posteriormente, se utiliza una matriz denominada matriz de modelado que se aplica sobre cada uno de los vértices de la figura para obtener el valor x-y-z una vez movida la figura, rotada y escalada.
Las operaciones anteriores (traslación, rotación y escalado) modifican la matriz de modelado.
  • Una vez la figura se encuentra en la posición correcta en nuestro mundo 3D es necesario dibujarla. Quien decide como se dibuja es una cámara (viene a ser nuestros ojos en el mundo real). Esta cámara posee otra matriz denominada matriz de proyección, la cual se aplica a cada uno de los vértices y de esa forma obtenemos los puntos x-y para dibujar nuestra figura en nuestro monitor.



Programando la Posición

  • Como ya comentamos anteriormente, las figuras están compuestas por múltiples triángulos están formados por 3 vértices.
  • Cuando se define una figura 3D (Mesh) indicamos la posición 3D (x,y,z) de cada vértice de cada triángulo (existen otras formas de aprovechar los vértices comunes y disminuir el número total de vértices).
LIBGDX UD4 Animacion 2.jpg


Esta posición se define con respecto al punto (0,0,0) Esto quiere decir que todas las figuras 3D se dibujan en dicho punto. Después dependiendo de la posición que queramos darles, se multiplican por unas matrices (matriz de modelado) para obtener las posiciones x-y-z de nuestro mundo 3D.


  • Como vimos anteriormente, las operaciones que podemos realizar sobre una figura 3D son las de: Traslación - Rotación - Escala.
Dichas operaciones van a modificar una matriz denominada matriz de modelado:
LIBGDX UD4 Animacion 4.jpg
La imagen anterior muestra la matriz de identidad (matriz con todos 1 en su diagonal) y que viene a ser como el número 1 en la multiplicación (no afecta al modelo).




Ejemplo de código utilizado en el framework LIBGDX

Ejemplo de definición de matriz:

1 public Matrix4 matriz;


Para cargar la matriz de identidad tenemos que llamar al método idt():

1 public Matrix4 matriz;
2 ..........
3 
4 matriz.idt();

Ahora será necesario aplicar los 3 tipos de operaciones:

  • Traslación:
Método translate:
Ejemplo: matriz.translate(posicion);
Siendo 'posicion' un Vector3 que indica hacia donde se tiene que trasladar.
Es importante comprender que siempre debemos de partir de la matriz identidad y aplicar la nueva posición a la que queremos llegar. No lo podemos hacer a partir de la última posición guardada.

Ejemplo de código:

1 public Matrix4 matriz;
2 ..........
3 matriz.idt();
4 matriz.translate(10,1,0);
  • Rotación:
Método rotate:
Este método está sobrecargado e tenemos varias posibilidades.

Un ejemplo:

1 public Matrix4 matriz;
2 ..........
3 matriz.idt();
4 matriz.translate(10,1,0);
5 matriz.rotate(1,0,0,90);

En este caso estaremos rotando en el eje X la figura 90º. En este caso o método rotate lleva cuatro parámetros:

  • param1: Indica se o eje X debe rotarse. Si tiene valor 1 lo rota y si tiene 0 no hace nada en ese eje.
  • param2: Indica se o eje Y debe rotarse. Si tiene valor 1 lo rota y si tiene 0 no hace nada en ese eje.
  • param3: Indica se o eje Z debe rotarse. Si tiene valor 1 lo rota y si tiene 0 no hace nada en ese eje.
  • param4: Indica el número de grados de la rotación.

Aviso: Es importante primero hacer la traslación y después la rotación, ya que si lo hacemos al revés, el punto está rotado cuando aplicamos la traslación y por tanto se moverá a otro lugar diferente al que queremos. Si lo aplicamos a una esfera, tendríamos un efecto de traslación alrededor del punto (0,0,0).


  • Escala:
Método scale:

Podemos modificar la escala en cada uno de los ejes (X-Y-Z).

Por exemplo:

1 public Matrix4 matriz;
2 ..........
3 matriz.idt();
4 matriz.translate(10,1,0);
5 matriz.rotate(1,0,0,90);
6 matriz.scale(2,1,1);

En este ejemplo indicamos que el tamaño X de la figura debe de ser el doble que el tamaño Y y Z.


  • Consultar ahora como realizar una animación en LIBGDX (sobre todo entender los conceptos de la matriz de proyección-modelado) y como se 'construye' un Mesh a partir de los vértices de sus triángulos: Enlace a animación 3D con LIBDX.




Ejemplo de código utilizado en el UNITY

  • Este es un ejemplo de código de un script de nombre PruebaMatrix4x4 asociado a una esfera:
 1 public class PruebaMatrix4x4 : MonoBehaviour {
 2     public Vector3 translation = new Vector3(5, 2, 3);
 3     public Vector3 eulerAngles = new Vector3(30, 45, 25);
 4     public Vector3 scale = new Vector3(1, 1, 1);
 5     private MeshFilter mf;
 6     private Vector3[] origVerts;
 7     private Vector3[] newVerts;
 8 
 9     void Start()
10     {
11         mf = GetComponent<MeshFilter>();
12         origVerts = mf.mesh.vertices;
13         newVerts = new Vector3[origVerts.Length];
14     }
15 
16     void Update()
17     {
18         Quaternion rotation = Quaternion.Euler(eulerAngles.x, eulerAngles.y, eulerAngles.z);
19         Matrix4x4 m = Matrix4x4.identity;
20         m.SetTRS(translation, rotation, scale);
21         int i = 0;
22         while (i < origVerts.Length)
23         {
24             newVerts[i] = m.MultiplyPoint3x4(origVerts[i]);
25             i++;
26         }
27         mf.mesh.vertices = newVerts;
28     }
29 }
Líneas 2-4: Son propiedades públicas y por tanto van a aparecer en la ventana 'Inspector' de Unity. Se definen los vectores para la posición, rotación y escala.
La rotación se define con ángulos Euler que es la forma más 'humana' de entenderlo. Internamente se guardan en un objeto de la clase Quaternion.
Línea 18: Definimos un objeto de la clase Quaternion para guardar la rotación del objeto en forma de ángulos de Euler.
Línea 19: Definimos la matriz de modelado igualándola a la matriz identidad.
Línea 20: Asignamos los tres vectores a la matriz.
Después de eso, el siguiente código aplica a cada uno del vértices del GameObject asociado los datos de la matriz.
Si ejecutáis el programa podéis ver como aparecen las propiedades public en el inspector y podéis modificar sus valores viendo como cambia la posición-escala-rotación de la esfera.




Manipular la posición en Unity

  • Nota: Vamos a partir de que a nivel de diseño tenéis un Cubo en la posición (0,0,0) con la cámara mirando hacia él.
Asociado a dicho cubo vamos a tener un script de nombre 'UD_Movim_basico_1'.


Unity3d mov 1.JPG



  • Todo visto en los puntos anteriores sirve como información de qué es lo que hace realmente Unity.
  • Pero existe una forma mucho más sencilla (que es la que usaremos nosotros) para manejar los objetos.
Es haciendo uso de la clase Transform.
Todo objeto de Unity tiene un objeto de la clase Transform asociado.
Dicho objeto nos va a permitir modificar la rotación-escala-posición del objeto.


1         Vector3 posicion = gameObject.transform.position;   // Aparece en Unity en la ventana Inspector debajo de la sección 'Transform'. No hace falta poner gameObject
2         Quaternion rotacion = gameObject.transform.rotation;   // Aparece en Unity en la ventana Inspector debajo de la sección 'Transform'. No hace falta poner gameObject
3         Vector3 escala = transform.localScale;              // No hace falta poner gameObject
  • IMPORTANTE: Lo que estamos guardando es una copia de los valores que guarda el GameObject. Por lo tanto si realizamos modificaciones tendríamos que volver a asignarlas a las propiedades del GameObject.
Recordar: Cada vez que hagáis uso de transform.position, transform.rotation y transform.localScale estáis trabajando sobre una copia y por tanto hacer esto: transform.position.Set(5,5,5) no modifica la posición del GameObject.


  • Si quisiéramos manipularlas, podemos hacerlo directamente de la forma:
(Escribir este código en el script 'UD_Movim_basico_1' creado anteriormente)
 1     // Use this for initialization
 2     void Start () {
 3 
 4         transform.position = new Vector3(2,2,2);    // Mueve el gameobject a la posición (2,2,2)
 5         transform.Rotate(30, 45, 90);       // Rota 30º en X, 45º en Y 90º en Z
 6         transform.localScale = new Vector3(2, 2, 5);  // Escala por 2 en X e Y y por 5 en Z
 7     }
 8 
 9     // Update is called once per frame
10     void Update () {
11 
12     }


Unity3d mov 2.JPG


Como vemos hemos movido el GameObject cubo a la posición indicada.


  • Podemos hacer que cualquier propiedad aparezca gráficamente en Unity estableciendo el tipo de acceso a public. Así, en nuestro caso podemos establecer las tres propiedades para que gráficamente puedan ser modificadas de la forma:
 1 public class UD_Movim_basico_1 : MonoBehaviour {
 2 
 3     public Vector3 posInicial = new Vector3(0, 0, 0);
 4     public Vector3 rotInicial = new Vector3(0, 0, 0);
 5     public Vector3 escalaInicial = new Vector3(1, 1, 1);    // La escala no debería ser 0
 6 
 7     // Use this for initialization
 8     void Start () {
 9         transform.position = posInicial;
10         transform.Rotate(rotInicial);
11         transform.localScale = escalaInicial;
12     }
13 	
14     // Update is called once per frame
15     void Update () {
16 		
17     }
18 }


Unity3d mov 3.JPG


Ahora podemos modificar los valores iniciales.
  • Nota: El recurso de hacer public a propiedades se puede aplicar a cualquier propiedad en Unity. Por ejemplo, podríamos tener una propiedad que fuera un GameObject o un objeto de la clase Transform. De esta forma podríamos arrastrar gráficamente cualquier objeto y llevarlo al Inspector, copiándose todas sus propiedades.
Si queremos que una propiedad aparezca en el Inspector pero no queremos que se pueda acceder a ella desde otros GameObject mediante una referencia, podemos anteponer la palabra clave [SerializedField] en su definición de la forma:
1 public class UD_Movim_basico_1 : MonoBehaviour {
2 
3     [SerializeFiled]private Vector3 posInicial = new Vector3(0, 0, 0);
4     [SerializeFiled]private Vector3 rotInicial = new Vector3(0, 0, 0);
5     [SerializeFiled]private Vector3 escalaInicial = new Vector3(1, 1, 1);    // La escala no debería ser 0
6 
7 }
Así si 'arrastramos' este GameObject a otro diferente, no podremos acceder a las propiedades anteriores al estar definidas como private.




  • Ahora ha llegado el momento de hacer que el cubo se mueva. Como suponéis, debemos de modificar el valor de uno de los ejes.
El método donde se debe de escribir el código es el método Update.
A dicho método se llama de forma continuada desde que comienza el juego.
 1 public class UD_Movim_basico_1 : MonoBehaviour {
 2 
 3     public Vector3 posInicial = new Vector3(0, 0, 0);
 4     public Vector3 rotInicial = new Vector3(0, 0, 0);
 5     public Vector3 escalaInicial = new Vector3(1, 1, 1);    // La escala no debería ser 0
 6 
 7     public float posFinalX = 5f;
 8 
 9     // Use this for initialization
10     void Start () {
11         transform.position = posInicial;
12         transform.Rotate(rotInicial);
13         transform.localScale = escalaInicial;
14     }
15 	
16     // Update is called once per frame
17     void Update () {
18 
19         if (transform.position.x <= posFinalX)
20         {
21             Vector3 temp = transform.position;
22             temp.x += .5f;
23             transform.position = temp;
24         }
25 
26 		
27     }
28 }
  • Si ejecutáis el código veréis como el cubo se mueve rápidamente hacia posición indicada por la variable 'posFinalX'.
Unity3d mov 3.JPG



Parámetro delta

  • El problema que tenemos es que no tenemos control de la velocidad.
Necesitamos comprender el concepto del parámetro delta


  • Lo que tenemos que tener que claro que cualquier movimiento se produce borrando toda la pantalla y volviéndola a dibujar.
Lo que pasa es que se hace tan rápido que para nuestros ojos solo se tiene la impresión del movimiento.
  • La rapidez con que se borra todo y se vuelve a dibujar la establecemos mediante un parámetro denominado Fotogramas Por Segundo (fps).
Este parámetro va a depender de la máquina donde se ejecute el juego (capacidades hardware de la misma, como procesador, tarjeta gráfica,...) y de lo optimizado que esté nuestro juego.


  • Al depender de la máquina, no podemos hacer que los elementos del juego se muevan sin ningún control, ya que con el código anterior, en una máquina a 60fps se movería mucho más rápido que en una máquina a 30fps. Debemos hacer que la velocidad del movimiento sea independiente del tipo de máquina.
Eso lo conseguimos con el parámetro delta.
  • delta es el tiempo que transcurre desde que se llama a un método y vuelve a llamarse al mismo método.
La forma de obtener dicho valor es mediante la clase Time de la forma Time.deltaTime.


  • Imaginemos que tenemos dos dispositivos Android, uno funcionando a 50fps y otro a 25fps.
Esto quiere decir que (más o menos) se va a llamar al método Update ese número de veces por segundo.
Si ejecutamos este código para mover el cubo:
1     void Update () {
2        if (transform.position.x<100){
3         Vector3 temp = transform.position;
4         temp.x += 1f;
5         transform.position = temp;
6        }	
7     }
  • Como vemos se va a mover mientras X sea menor que 100 unidades (en Unity, cada cuadrado en el entorno tridimensional tiene un tamaño 1 metro x 1 metro (a tener en cuenta si vamos a aplicar el motor de físicas a nuestro juego).



Lo que va a pasar:
Máquina1:
50fps => se mueve a 50 unidades por segundo => Tarda 100/50 en recorrer toda la pantalla => 2 segundos.
Máquina2:
25fps => se mueve a 25 unidades por segundo => Tarda 100/25 en recorrer toda la pantalla => 4 segundos.


  • Por lo tanto se movería más rápido en la primera máquina y eso no parece muy justo :)
¿ Cómo podemos hacer para que el movimiento sea independiente del hardware de la máquina ? Multiplicando la velocidad por delta siendo delta el tiempo que tarda en llamar al método update que actualiza la posición.
  • Por ejemplo, siguiendo con el ejemplo anterior:
Máquina1: a 50 fps. Quiere decir que llama 50 veces en un segundo al método update.
Delta = 1/50
temp.x = temp.x + (1 * delta) :multiplicamos la velocidad por delta (en este caso la velocidad es una unidad por segundo). En nuestro caso se moverá durante 100 unidades.
¿ Cuanto tiempo tarda en recorrer las 100 unidades ?
En un segundo recorre:
50 (fps = veces por segundo) * 1/50 (es delta) * 1 (la velocidad) = 1.
Por lo tanto va a sumar una unidad a la posición X cada segundo.


Máquina2: a 25 fps. Quiere decir que llama 25 veces en un segundo al método update.
Delta = 1/25
temp.x = temp.x + (1 * delta) :multiplicamos la velocidad por delta (en este caso la velocidad es una unidad por segundo). En nuestro caso se moverá durante 100 unidades.
¿ Cuanto tiempo tarda en recorrer las 100 unidades ?
En un segundo recorre:
25 (fps = veces por segundo) * 1/25 (es delta) * 1 (la velocidad) = 1.
Por lo tanto va a sumar una unidad a la posición X cada segundo.


  • Como vemos tarda lo mismo. Lo que está haciendo es ajustar, disminuyendo o aumentando la velocidad en función del parámetro delta. Si delta es más grande (llama muchas veces por segundo) hace que se mueva más lentamente ya que lo multiplica por 1/delta.
  • Aplicando estos conceptos a nuestro ejemplo el código quedaría así:
 1 public class UD_Movim_basico_1 : MonoBehaviour {
 2 
 3     public Vector3 posInicial = new Vector3(0, 0, 0);
 4     public Vector3 rotInicial = new Vector3(0, 0, 0);
 5     public Vector3 escalaInicial = new Vector3(1, 1, 1);    // La escala no debería ser 0
 6 
 7     public float posFinalX = 5f;
 8     public float velocidad = 0.5f;
 9 
10     // Use this for initialization
11     void Start () {
12         transform.position = posInicial;
13         transform.Rotate(rotInicial);
14         transform.localScale = escalaInicial;
15     }
16 	
17     // Update is called once per frame
18     void Update () {
19 
20         if (transform.position.x <= posFinalX)
21         {
22             Vector3 temp = transform.position;
23             temp.x = temp.x + (velocidad*Time.deltaTime);
24             transform.position = temp;
25         }
26     }
27 }


Si ejecutáis el código anterior y en ejecución os vais a la pestaña 'Scene' podéis comprobar como el cubo de mueve a la velocidad indicada y recorre (en este ejemplo) 5 unidades:
Unity3d mov 6.JPG

Optimizando el uso de la memoria

  • Más información:


  • Debemos de tener en cuenta que el método Update se está llamando muchas veces por segundo, por lo que tenemos que tener cuidado con las operaciones que reservan memoria y que se ejecuten dentro de dicho método.
  • Concatenación de cadenas: Es algo que consume memoria, ya que cada concatenación haciendo uso del símbolo más crea una nueva cadena en memoria. Se debe hacer uso de la clase StringBuielder.
  • Cualquier operación que lleve consigo el uso de un 'new', deberíamos intentar 'sacarla' fuera del método Update o que si se ejecuta bajo ciertas condiciones, establecerlas previamente con un if.


  • Por ejemplo, el siguiente código comprueba que ha habido un cambio en el marcador para mostrarlo, en vez de llamar al método mostrarMarcador() sin condición:
 1     ............
 2     private bool cambiaMarcador = false;
 3 
 4 
 5     private void mostrarMarcador()
 6     {
 7         // Código para mostrar el marcador
 8     }
 9 
10     // Update is called once per frame
11     void Update () {
12 
13         if (cambiaMarcador)
14         {
15             mostrarMarcador();
16         }
17 
18         .........
19    }



Variables y métodos de ayuda para el movimiento


Vectores de movimiento
  • Vectores que se pueden utilizar para el movimiento:
Unity3d mov 8.JPG


  • up, down, left, right ,back, forward:
Así, si quiero desplazarme a la derecha tendré que poner este código:
1   void Update () {
2 
3         if (transform.position.x <= posFinalX)
4         {
5             transform.position += Vector3.right*velocidad*Time.deltaTime;
6         }
7   }
Y lo mismo para todas las demás direcciones.
Si quisiéramos movernos 'arriba a la derecha':
1 void Update () {
2 
3         if (transform.position.x <= posFinalX)
4         {
5             transform.position += (Vector3.right+Vector3.up)*velocidad*Time.deltaTime;
6         }
7 }


  • Nota Importante:
Debemos de tener en cuenta que estos vectores siempre devuelven los mismo valores. Por ejemplo, el Vector3.forward es el vector (0,0,1).
Sin embargo, a nivel de programación podemos acceder a los vectores: up, forward,right del gameobject, de la forma: gameObject.transform.up (o también transform.up). Fijarse que 'transform' es con minúsculas, indicando que se refiere al gameobject asociado.
Estos vectores son los vectores que 'apuntan' hacia arriba, adelante y derecha estando rotados si el objeto fue rotado previamente.
Se ve claramente en la siguiente imagen:

[Imagen:15B_direction_vecs.jpg]

Respuesta de gabs en este enlace
Los colores claros son los vectores de los objetos que han sido rotados. Los colores oscuros son los vectores del mundo (Vector3.up,Vector3.right,....) que siempre 'apuntan' hacia la misma dirección, no se rotan.



En Unity podemos 'ver' los ejes globales o los ejes locales (los rotados) de un objeto 3D presionando el botón



Vector cero
Dentro de la clase Vector3.
  • Vector zero: Utilizado para inicializar un Vector3 o para situar un objeto en la posición (0,0,0).



Vectores infinitos
Dentro de la clase Vector3.


  • Vector negativeInfinity, positiveInfinity: Devuelven un vector3 con valores 'infinitos' en (x,y,z). Se puede utilizar para determinar cuando no te encuentras dentro de un área de visualización, asignando dichos valores al vector correspondiente.



Método Translate
  • Método 'translate' de la clase Transform: Modifica la posición de un gameobject desplazándola en los valores indicados. Es equivalente a poner: position = position+valor. Es decir, traslada el objeto desde su posición a una nueva añadiendo un nuevo valor. No hace como la propiedad position, que 'mueve' el gameobject a la posición indicada.

Nota: Tener en cuenta que el método está sobrecargado y existen varias formas de realizar la misma acción.


Veamos un ejemplo sobre el script anterior:
 1 public class UD_Movim_basico_1 : MonoBehaviour {
 2 
 3     public Vector3 posInicial = new Vector3(0, 0, 0);
 4     public Vector3 rotInicial = new Vector3(0, 0, 0);
 5     public Vector3 escalaInicial = new Vector3(1, 1, 1);    // La escala no debería ser 0
 6 
 7     public float velocidad = 2f;
 8 
 9     // Use this for initialization
10     void Start () {
11         transform.position = posInicial;
12         transform.Rotate(rotInicial);
13         transform.localScale = escalaInicial;
14 
15     }
16 	
17 	// Update is called once per frame
18 	void Update () {
19 
20         transform.Translate(Vector3.up* velocidad * Time.deltaTime);
21 
22 	}
23 }


Este método está sobrecargado. Podemos hacer uso de una de sus variantes, en las que utiliza como como punto de referencia para moverse, no su posición local (donde se encuentra el GameObject) sino:
  • Otro sistema de referencia de otro objeto (el transform de otro gameobject).
  • El sistema de referencia del mundo 3D, haciendo uso de la constante: Space.World
¿ Qué significa 'sistema de referencia' ? Pues que parte del punto indicado por el sistema de referencia para realizar la traslación del GameObject. Esto parece que no tiene sentido, ya que lo que hacemos es desplazar, por lo tanto, ¿ qué diferencia hay entre desplazar el GameObject 3 unidades hacia arriba tomando como referencia el propio GameObject (es decir, su posición actual) que tomar como referencia la coordenada (0,0,0) del mundo 3D ? Siguen siendo 3 unidades en los dos sistemas de referencia.
La diferencia está en la rotación. En el sistema de referencia del mundo no se tiene en cuenta la rotación, por lo tanto, desplazar 3 unidades hacia arriba conlleva modificar la posición del GameObject en el eje Y 3 unidades arriba.
Sin embargo, si utilizamos como sistema de referencia un objeto rotado, digamos 45 grados en el eje X, y lo movemos hacia arriba, se desplazará hacia arriba-atrás (eje Y-Z).
Veamos un ejemplo.
Modificar la escena anterior y añadir una esfera a la posición (0,0,0).
Modificar el cubo de la escena, cambiarle el nombre por el de 'Cube-Self' (cuidado las mayúsculas-minúsculas) y posicionarlo en la posición (-2,0,0). Rotar el cubo 45 grados en el eje X.
Unity3d mov 12.JPG
Ahora vamos a duplicar el gameobject 'Cube-Self' pulsando el botón derecho sobre él y escogiendo la opción Duplicate:
Unity3d mov 13.JPG
Cambiamos el nombre del GameObject por el de 'Cube-World' (cuidado las mayúsculas-minúsculas) y lo posiciones en la coordenada (2,0,0)
Unity3d mov 14.JPG
Modificamos el código del script 'UD_Movim_basico_1':
 1 public class UD_Movim_basico_1 : MonoBehaviour {
 2 
 3     public float velocidad = 2f;
 4 
 5     // Use this for initialization
 6     void Start () {
 7     }
 8 
 9     // Update is called once per frame
10     void Update()
11     {
12 
13         if (transform.position.x < 5 && transform.position.y < 5 && transform.position.z < 5)
14         {
15             if (gameObject.name.Equals("Cube_Self"))
16             {
17                 transform.Translate(Vector3.up * velocidad * Time.deltaTime, Space.Self);
18             }
19             else if (gameObject.name.Equals("Cube_World"))
20             {
21                 transform.Translate(Vector3.up * velocidad * Time.deltaTime, Space.World);
22             }
23 
24         }
25     }
26 }
Lo que hace el script es trasladar el 'Cube_Self' utilizando como sistema de referencia su propio transform(se podría omitir este tercer parámetro y tendría el mismo efecto) y trasladar el cubo 'Cube_World' utilizando como sistema de referencia el mundo 3D (Space.World).
Al ejecutar el script podemos ver como el 'Cube_World' se traslada hacia arriba mientras que el 'Cube_Self' se traslada hacia arriba-atrás, debido a que está rotado:


NOTA IMPORTANTE: En este método es mejor utilizar siempre los vectores Vector.up, Vector.forward,..., es decir, los vectores de referencia deberían ser los del mundo, teniendo en cuenta que el segundo parámetro del método nos indica el sistema de referencia, si queremos que un objeto se mueva hacia adelante con respecto a sus ejes (estando rotado) deberemos de poner: transform.Translate(Vector3.forward * velocidad * Time.deltaTime, Space.self);



Método MoveTowards
Dentro de la clase Vector3.


  • Método MoveTowards: Mueve el GameObject desde la posición indicada por el parámetro 'current' hasta la posición indicada por el parámetro 'target' en la distancia indicada por el parámetro step.
  • Normalmente 'step' tiene como valor: Velocidad * Time.deltaTime
Debo aclarar que 'mover' no es la definición exacta, ya que no modifica los vectores que le pasamos como parámetros, sino que devuelve un Vector3 con la nueva posición (la que va de origen a destino) movida un 'poquito' en cada frame.
Lo que debemos hacer es igualar ese vector a la posición de lo que queramos mover, de la forma: transform.position=Vector.moveTowards(origen, destino, velocidad*delta).


Veamos un ejemplo, modificando el código del script 'UD_Movim_basico_1' para que el cubo se mueva hacia donde se encuentre el ratón en la pantalla:

Nota: Recordar que vimos anteriormente como proyectar un punto desde la pantalla al espacio 3D.

 1 public class UD_Movim_basico_1 : MonoBehaviour {
 2 
 3     public Vector3 posInicial = new Vector3(0, 0, 0);
 4     public Vector3 rotInicial = new Vector3(0, 0, 0);
 5     public Vector3 escalaInicial = new Vector3(1, 1, 1);    // La escala no debería ser 0
 6 
 7     public float posFinalX = 5f;
 8     public float velocidad = 1f;
 9 
10     // Use this for initialization
11     void Start () {
12         transform.position = posInicial;
13         transform.Rotate(rotInicial);
14         transform.localScale = escalaInicial;
15 
16     }
17 
18     // Update is called once per frame
19     void Update () {
20 
21         Vector3 posRaton = Input.mousePosition;
22         posRaton.z = 8;         // Plano donde se encuentra el cubo con respecto a la cámara. Adaptarlo a vuestro caso si la cámara se encuentra en otra posición.
23         Vector3 posMundo = Camera.main.ScreenToWorldPoint(posRaton);
24 
25         transform.position = Vector3.MoveTowards(transform.position, posMundo, velocidad * Time.deltaTime);
26 
27     }
28 }


Si ahora ejecutamos el juego, al mover el ratón el cubo irá hacia el mismo:
Unity3d mov 9.JPG



Método Lerp
  • Método Lerp: Es parecido al anterior, también mueve un GameObject de un origen a un destino, pero varía en el tercer parámetro, el cual indica el porcentaje de cuan cerca está del punto destino. Es decir, el tercer parámetro va de 0 a 1, siendo 0 el valor que indica que está en el origen y 1 el valor que indica que está en el destino. Si por ejemplo tuviéramos 0.5f querría decir que estaríamos en el punto intermedio entre el origen y destino.
Al igual que en el método anterior, no modifica los vectores que le pasamos como parámetro y devuelve un Vector3 con la nueva posición.
Hace uso de la interpolación para obtener los puntos intermedios desde el origen al destino.


Debemos de tener en cuenta que aquí no se puede pasar como tercer parámetro Velocidad*Time.deltaTime ya que este valor nunca será igual a uno y por tanto nunca llegaría al punto destino.
Un uso que se le puede dar a Lerp (entre otros) es el de recorrer una determinada distancia en un tiempo concreto.
Podemos utilizar 'Time.deltaTime' como un reloj y poner como tercer parámetro: reloj / tiempo_total, siendo tiempo_total el número de segundo que queremos que tarde en alcanzar el punto de destino.


Modificamos el script anterior para que cubo se mueva a la posición del ratón cuando pulsamos el botón del mismo en un tiempo (por defecto 1 segundo). Vamos a hacer que el tiempo que tarda pueda ser modificado desde el inspector haciendo pública la propiedad:


 1 public class UD_Movim_basico_1 : MonoBehaviour {
 2 
 3     public Vector3 posInicial = new Vector3(0, 0, 0);
 4     public Vector3 rotInicial = new Vector3(0, 0, 0);
 5     public Vector3 escalaInicial = new Vector3(1, 1, 1);    // La escala no debería ser 0
 6 
 7     public float tiempoTotal = 1f;    // Tiempo que tarda el cubo en llegar al destino.
 8     private float cronometro = 0f;
 9 
10     private Vector3 posFinal = new Vector3(0,0,0);       // Posición de destino
11 
12     // Use this for initialization
13     void Start () {
14         transform.position = posInicial;
15         transform.Rotate(rotInicial);
16         transform.localScale = escalaInicial;
17 
18     }
19 	
20     // Update is called once per frame
21     void Update () {
22 
23 
24         if (Input.GetMouseButtonDown(0))    // Presionamos el botón del ratón
25         {
26             cronometro = 0;
27             posInicial = transform.position;
28 
29             Vector3 posRaton = Input.mousePosition;
30             posRaton.z = 8;         // Plano donde se encuentra el cubo con respecto a la cámara. Adaptarlo a vuestro caso si la cámara se encuentra en otra posición.
31             posFinal = Camera.main.ScreenToWorldPoint(posRaton);
32 
33         }
34 
35         cronometro += Time.deltaTime;
36         transform.position = Vector3.Lerp(posInicial, posFinal, cronometro / tiempoTotal);
37 
38 	}
39 }
Unity3d mov 10.JPG



Método SmoothDamp
Dentro de la clase Vector3.


  • Método SmoothDamp: Tiene el mismo efecto que el método anterior pero realiza el movimiento de una form más 'suave' ya que va aumentando la velocidad progresivamente y cuando se va acercando al destino va disminuyendo de velocidad. Se suele utilizar para seguir a una cámara en el juego.

Nota: Tener en cuenta que el método está sobrecargado y existen varias formas de realizar la misma acción.

Veamos un ejemplo modificando el código del script.
Al pulsar el botón de ratón, el cubo se moverá a dicha posición en el tiempo indicado por la variable 'tiempoTotal'.
Aquí no necesitamos cronómetro ya que el método hace los cálculos para llegar en el tiempo indicado, ajustando la velocidad.
Dispone de un parámetro currentVelocity. Es un parámetro que va a hacer referencia a un atributo de tipo Vector3 el cual va a modificar en cada frame, ajustando la velocidad. Podemos ver como dicha velocidad se modifica...


 1 public class UD_Movim_basico_1 : MonoBehaviour {
 2 
 3     public Vector3 posInicial = new Vector3(0, 0, 0);
 4     public Vector3 rotInicial = new Vector3(0, 0, 0);
 5     public Vector3 escalaInicial = new Vector3(1, 1, 1);    // La escala no debería ser 0
 6 
 7     public float tiempoTotal = 1f;    // Tiempo que tarda el cubo en llegar al destino.
 8     private Vector3 posFinal = new Vector3(0,0,0);       // Posición de destino
 9 
10     private Vector3 velocidadActual = Vector3.zero;
11 
12     // Use this for initialization
13     void Start () {
14         transform.position = posInicial;
15         transform.Rotate(rotInicial);
16         transform.localScale = escalaInicial;
17 
18         posFinal = transform.position;
19     }
20 	
21     // Update is called once per frame
22     void Update () {
23 
24         Debug.Log("Velocidad:" + velocidadActual);
25         if (Input.GetMouseButtonDown(0))    // Presionamos el botón del ratón
26         {
27             Vector3 posRaton = Input.mousePosition;
28             posRaton.z = 8;         // Plano donde se encuentra el cubo con respecto a la cámara. Adaptarlo a vuestro caso si la cámara se encuentra en otra posición.
29             posFinal = Camera.main.ScreenToWorldPoint(posRaton);
30 
31         }
32 
33         transform.position = Vector3.SmoothDamp(transform.position, posFinal,ref velocidadActual, tiempoTotal);
34 
35 	}
36 }
  • Línea 10: Vamos a guardar la velocidad que se va a modificar automáticamente al llamar al método 'SmoothDamp'.
  • Línea 24: Por la consola vamos a poder ver como la velocidad se modifica.
  • Línea 33: Fijarse que el parámetro de la velocidad lleva la palabra reservada 'ref'.


Unity3d mov 11.JPG




Distancia y dirección
  • Durante el desarrollo de juegos suele ser bastante habitual la necesidad de determinar la distancia a la que se encuentra un gameobject de otro, así como determinar el vector dirección entre los dos.



Distancia: Método magnitude
  • Para calcular la distancia entre dos puntos de nuestro espacio 3D tenemos que hacer uso del método magnitude de la clase Vector3.
Veamos un ejemplo, modificando el script anterior:
 1 public class UD_Movim_basico_1 : MonoBehaviour {
 2 
 3     public float velocidad = 2f;
 4 
 5     private GameObject esfera;
 6 
 7     // Use this for initialization
 8     void Start () {
 9 
10         esfera = GameObject.Find("Sphere"); // Buscamos el GameObject por su nombre. No debe de ponerse en el método Update
11     }
12 
13     // Update is called once per frame
14     void Update()
15     {
16 
17         if (transform.position.x < 5 && transform.position.y < 5 && transform.position.z < 5)
18         {
19             if (gameObject.name.Equals("Cube_Self"))
20             {
21                 transform.Translate(Vector3.up * velocidad * Time.deltaTime, Space.Self);
22             }
23             else if (gameObject.name.Equals("Cube_World"))
24             {
25                 transform.Translate(Vector3.up * velocidad * Time.deltaTime, Space.World);
26                 Vector3 distancia = transform.position - esfera.transform.position;
27                 Debug.Log(distancia.magnitude);
28             }
29 
30         }
31 
32 
33     }
34 }
Unity3d mov 17.JPG


IMPORTANTE:' El llamar al método magnitude lleva consigo realizar una operación de raíz cuadrada sobre (x*x+y*y+z*z).

Si queremos saber la distancia entre Varios gameobject para saber cual está más cerca con respecto a otro gameobject es más eficiente hacer uso del método sqrMagnitude que devuelve la distancia al cuadrado (y por tanto no tiene que realizar la operación de raíz cuadrada).
Por lo tanto, si queremos controlar la distancia a un gameobject haciendo uso de este método debemos de tener algo parecido a esto:
1 Vector3 distancia = GameObject1.position - GameObject2.position;
2 if (distancia.sqrMagnitude < maxDistancia*maxDistancia) {
3 
4 }
Como vemos debemos de multiplicar la distancia a controlar consigo mismo.



Vector dirección
  • Normalmente en todos los juegos vais a tener algún personaje que se mueva hacia otro, lo persiga, un misil teledirigido...
Unity ya ofrece métodos que nos permite realizar estas acciones (como el método Lerp visto anteriormente), pero en ciertas ocasiones puede ser necesario obtener la dirección de un GameObject con respecto a otro.
Para saberlo es necesario obtener lo que se denomina Vector dirección.


  • Primero debemos de saber que es un Vector. De forma resumida para nosotros un Vector va a representar una dirección que tiene que seguir un GameObject para llegar a un punto de destino.
Si dicho punto de destino no varía en el tiempo, el vector de dirección se calcula al principio (método Start) y no variará con el tiempo. Si queremos seguir a otro GameObject que se mueve en el tiempo, debemos de recalcular el vector dirección cada cierto tiempo (método Update).
La forma más sencilla es verlo con un ejemplo:
Nota: Los conceptos aprendidos serán igualmente aplicables a modelos 3D añadiendo la coordenada Z.


Imaginemos que estamos en la coordenada (1,1) y queremos dirigirnos a la coordenada (4,2).
Lo que tendríamos que hacer es calcular cual es el vector de dirección, es decir, que números (x,y) sumados a (1,1) nos llevan hasta (4,2).


LIBGDX UD2 7 graficos 4.jpg
Lo más lógico es restar el punto de destino menos el punto de origen de la forma:
  • Vector Dirección = Punto_Destino – Punto_Origen = (4,2)-(1,1) = (3,1).


  • En Unity, podemos restar los vectores directamente (utilizando Vector3, sería igual si usáramos Vector2 pero sin la coordenada Z):
  • Vector3 direccion= posDestino - posOrigen;
Nota: Recordar que si podemos evitarlo, es mejor no poner esta operación en el método Update. Si no varía el punto destino ni origen puede ir en el Start o bien dentro del Update cuando se produzca alguna variación en la posición (iría con un if).


En el ejemplo anterior tendríamos un Vector de dirección con los valores (3,1)
Ahora podríamos llegar la punto destino en un único 'salto' ya que si sumamos de la forma:
posicion = posicion + direccion= (1,1)+(3,1) = (4,2).
Para que vaya moviéndose 'poco a poco' es necesario 'normalizar' dicho vector para que tenga una magnitud de 1.
Podemos hacerlo de dos formas:
  • Opción a) Dividiendo la distancia total por su magnitud.
  • Vector3 direccion = direccion / direccion.magnitude;
  • Vector3 direccion = direccion.normalized;
Una vez normalizado, podríamos hacer uso de dicho vector para llegar desde el punto origen al punto destino de la forma ya vista:
  • posicion += direccion*velocidad*Time.deltaTime;


Veamos un ejemplo, moviendo la esfera detrás de uno de los cubos.
 1 public class UD_Movim_basico_1 : MonoBehaviour {
 2 
 3     public float velocidad = 2f;
 4 
 5     private GameObject esfera;
 6 
 7     // Use this for initialization
 8     void Start () {
 9 
10         esfera = GameObject.Find("Sphere"); // Buscamos el GameObject por su nombre. No debe de ponerse en el método Update
11     }
12 
13     // Update is called once per frame
14     void Update()
15     {
16 
17         if (transform.position.x < 5 && transform.position.y < 5 && transform.position.z < 5)
18         {
19             if (gameObject.name.Equals("Cube_Self"))
20             {
21                 transform.Translate(Vector3.up * velocidad * Time.deltaTime, Space.Self);
22             }
23             else if (gameObject.name.Equals("Cube_World"))
24             {
25                 transform.Translate(Vector3.up * velocidad * Time.deltaTime, Space.World);
26                 Vector3 direccion = transform.position - esfera.transform.position;
27                 esfera.transform.Translate(direccion.normalized * velocidad * Time.deltaTime);
28             }
29 
30         }
31 
32 
33     }
34 }



  • Para obtener el vector dirección de un objeto (hacia donde está apuntando) podemos hacer uso de los vectores: up, forward y right del transform del gameobject.
Es decir:
  • transform.up (con minúscula) retorna el vector dirección del eje Y del gameobject estando rotado.
  • Vector3.up retorna el vector (0,1,0) siempre.



Método RotateTowards
Dentro de la clase Vector3.
  • Parámetro maxRadiansDelta es la velocidad de rotación, normalmente se pone Time.Delta*velocidad
  • Parámetro (maxMagnitudeDelta) normalmente se pone de valor 0.0f.
  • Es parecido al método MoveTowards con la diferencia que no nos movemos, sino que rotamos un vector.
Como pasaba en métodos anteriores, realmente no rota nada, sino que dicho método devuelve un Vector3 con los datos necesarios para rotar un GameObject.
Debemos de tener en cuenta que este método trabaja con direcciones no con posiciones.
Es decir, el Vector3 current indica la dirección en la que nos encontramos, y el Vector3 target indica la dirección a la que queremos ir.
Además solo trabaja con los ejes Z (vector3 forward) e Y (vector3 up) (es decir, solo rota esas direcciones).




Programando la Rotación

  • Para rotar, al igual que en el caso del movimiento, disponemos de varios métodos.


  • Nota Importante: Debemos de tener cuidado con las animaciones (las veremos en este punto de la Wiki que tenga asociado un modelo, ya que si estas tienen movimientos de rotación, sobre-escribirán las rotaciones que tengamos puesto a nivel de programación.
Para evitarlo podemos:
  • Crear un GameObject vacío y hacer que el modelo sea 'hijo' de este GameObject, asociando nuestro script al GameObject vacío.
  • Cambiar el orden de los componentes en la Inspector Window y hacer que el script se ejecute antes, por lo que primero realizará nuestra rotación y después la rotación de la animación.


  • Las rotaciones pueden realizarse empleando ángulos (ángulos de Euler) o unas matrices 4x4 de nombre Quaternion.
Lógicamente para el ser humano es más fácil entender las rotaciones pasando el valor del ángulo que queremos rotar y el eje sobre el que queremos rotar (x,y,z) pera el empleo de Quaternion´s permite realizar operaciones más complejas.
Lo que debéis entender es que un Quaternion representa una rotación con unos ángulos de rotación determinados.
  • Si multiplicamos dos Quaternion estamos sumando sus ángulos de rotación



Método Transform.Rotate

Rota un GameObject en base a los ángulos suministrados en el método (ángulos de Euler).
Nota: Debemos de tener en cuenta que cualquier rotación que hagamos es acumulativa. Esto quiere decir que sumáis a la rotación que ya tengáis, la nueva. Por eso, en la sección anterior donde teníamos un script en el que podíamos asignar una 'rotación inicial' en la ventana de Inspector, si tuviéramos alguna rotación previa en las propiedades del Transform, se sumaría.
Nota: Al igual que en el caso del método Translate podemos hacer la rotación con respecto a diferentes sistemas de referencia. Por defecto será Space.Self, es decir, el propio GameObject. Al igual que en el caso de Translate, si tomamos como referencia Space.World, la rotación de referencia serán los ejes (x,y,z) de nuestro mundo (los cuales no están rotados). En otro caso, se tomará como referencia la rotación del transform que utilicemos como parámetro, en los que los ejes pueden estar previamente rotados.


Para inicializar la rotación de un objeto tendremos que poner el siguiente código:
1         transform.rotation = Quaternion.identity;


Veamos un ejemplo de código de rotación.
Crear un script de nombre UD2_Movim_Rotac_1 y asociarlo a un cubo de nombre Cube_Self posicionado en (-2,0,0) y con un ángulo de rotación de 45 grados en el eje Y. Crear otro cubo de nombre Cube_World posicionado en (2,0,0) y con un ángulo de rotación de 45 grados en el eje Y.
La cámara mirando hacia ambos.
Asociar a los dos cubos el script siguiente:
Código de UD2_Movim_Rotac_1:
 1 public class UD2_Movim_Rotac_1 : MonoBehaviour {
 2 
 3 	// Use this for initialization
 4 	void Start () {
 5         if (gameObject.name.Equals("Cube_Self"))
 6         {
 7             transform.Rotate(45, 0, 0, Space.Self);
 8         }
 9         else
10         {
11             transform.Rotate(45, 0, 0, Space.World);
12         }
13     }
14 
15 	
16 	// Update is called once per frame
17 	void Update () {
18 
19 
20 		
21 	}
22 }



  • Al igual que hicimos en Translate, podemos rotar los objetos dentro del método Update para que giren de forma continuada y combinar diferentes operaciones a la vez.
Crear un nuevo script de nombre UD2_Movim_Rotac_2.
 1 public class UD2_Movim_Rotac_2 : MonoBehaviour {
 2     public float velocidadTraslac = 2f;
 3     public float velocidadRotac = 30f; // 30 grados por segundo
 4 
 5     // Use this for initialization
 6     void Start()
 7     {
 8     }
 9 
10 
11     // Update is called once per frame
12     void Update()
13     {
14         if (gameObject.name.Equals("Cube_Self"))
15         {
16             transform.Rotate(Vector3.right * velocidadRotac * Time.deltaTime);
17             transform.Translate(Vector3.left * velocidadTraslac * Time.deltaTime);
18         }
19         else
20         {
21             transform.Rotate(Vector3.right * velocidadRotac * Time.deltaTime, Space.World);
22             transform.Translate(Vector3.right * velocidadTraslac * Time.deltaTime, Space.World);
23         }
24 
25     }
26 
27 }


En Unity podemos asociar varios scripts al mismo GameObject simplemente arrastrándalos hacia el mismo o añadiendo un componente como ya vimos. En este caso vamos a quitar el script anterior y asociar el nuevo:


  • Este método está sobrecargardo y podemos emplearlo para rotar en algún eje concreto de la forma:
1     transform.Rotate(Vector3.up * Time.deltaTime*velocidad);
En el ejemplo estaríamos rotando el GameObject en el eje Y del mundo (sin tener en cuenta la rotación local del GameObject)


Método Transform.RotateAround

  • Método RotateAround de la clase Transform: Modifica la transform de un objeto rotando dicho objeto sobre un eje alrededor de un punto determinado.
Un ejemplo clásico sería el de un planeta rotando alrededor de un sol.


  • Veamos un ejemplo:
Vamos a crear dos esferas de diferentes tamaños.
  • Esfera 1: Cambiamos el nombre por 'Sol'. Posicionado en (0,0,0) y escala (2,2,2)
  • Esfera 2: Cambiamos el nombre por 'Planeta'. Posicionado en (-15,0,0)
  • Cámara: Posicionada en (0,6,-18) (podéis ponerla donde queráis, es una referencia). Rotada (23,0,0).


Tendréis algo parecido a esto:
Unity3d mov rot 12.JPG
Creamos un script de nombre UD_Movim_Rotac_5:
 1 public class UD_Movim_Rotac_5 : MonoBehaviour {
 2 
 3     private float angulo = 10f;
 4 
 5     // Use this for initialization
 6     void Start () {
 7 		
 8     }
 9 	
10     // Update is called once per frame
11     void Update () {
12 
13         transform.RotateAround(Vector3.zero, Vector3.up, angulo*Time.deltaTime);
14     }
15 }


Lo que hace el script es rotar 10' por segundo el gameobject alrededor del punto (0,0,0)
Asociar el script al GameObject planeta
Si ahora ejecutáis el juego podéis observar como el planeta 'gira' alrededor del sol:
Unity3d mov rot 13.JPG



Método Quaternion.LookRotation

Pertenece a la clase Quaternion.
Dicho método espera dos parámetros:
  • Vector3 forward: Es el vector dirección de hacia donde queremos seguir.
  • Vector3 upwards: Es el vector que indica la dirección del vector 'up'. Normalmente pondremos Vector3.up
El vector que falta (right), se obtiene de multiplicar los dos vectores anteriores.
La clase Quaternion debe guardar la información de rotación de los tres ejes.


  • Recordar que para rotar un GameObject haciendo uso de un objeto de la clase Quaternion, solamente tenemos que igualarlo a su propiedad 'rotation' de la forma:
1    gameObject.transform.rotation = quaternion;
Siendo quaternion un objeto de la clase Quaternion.


  • Veamos un ejemplo práctico y conjunto con el método anterior (RotateAround).
Vamos a realizar un ejercicio en el que tendremos un cañón que irá 'apuntando' a una esfera que se moverá alrededor del cañón.
Unity3d mov rotac 3.jpg


  • En el diseño vamos a aprovechar una de las ventajas de utilizar la jerarquía para hacer que el cañón sea 'hijo' del cubo. De esa forma, al rotar el cubo también rotará el cañón.


  • Ahora necesitamos crear un script que se asociará a la esfera que y la moverá alrededor del cañón.
  • Nombre script: UD2_Mov_Rotac_6A
 1  using UnityEngine;
 2 
 3 /**
 4  * Rota la esfera alrededor del cañon
 5  * 
 6  */
 7 public class UD2_Mov_Rotac_6A : MonoBehaviour
 8 {
 9     [SerializeField]
10     private float angulo = 30;
11     [SerializeField]
12     private Transform posCanhon;
13 
14     
15     // Start is called before the first frame update
16     void Start()
17     {
18         
19     }
20 
21     // Update is called once per frame
22     void Update()
23     {
24         transform.RotateAround(posCanhon.position, Vector3.up, angulo * Time.deltaTime);
25     }
26 }
  • Línea 12: El código es el mismo que el ejercicio anterior con la diferencia que estamos haciendo uso de una variable de tipo 'Transform' la cual va a guardar la posición del cañón ya que la idea es que la esfera gire alrededor de donde se encuentre el cañón.




  • Ahora creamos el script que hará que el cañón rote, persiguiendo a la esfera.
  • Nombre script: UD2_Mov_Rotac_6B
 1 using UnityEngine;
 2 
 3 /** Ejemplo de script que muestra un ejemplo de uso del
 4  *   método Quaternion.LookRotation
 5  *   
 6  */
 7 public class UD2_Mov_Rotac_6B : MonoBehaviour
 8 {
 9     [SerializeField]
10     private Transform posicionRotar;
11 
12     // Start is called before the first frame update
13     void Start()
14     {
15         
16     }
17 
18     // Update is called once per frame
19     void Update()
20     {
21         Vector3 dir = posicionRotar.position - transform.position;     // No hace falta normalizar el vector3 ya que no lo vamos a usar para movernos
22         Quaternion quaternion = Quaternion.LookRotation(dir, Vector3.up);
23         transform.rotation = quaternion;
24 
25         
26     }
27 }
  • Línea 10: Este script se va a asociar al cañón, el cual queremos que 'rote' persiguiendo a la esfera. Por tanto necesitamos saber cual es la posición de la esfera.
Definimos por tanto una propiedad que va a guardar esa información.
  • Línea 21: Obtenemos el vector dirección que apunta a la esfera.
  • Línea 22: Guardamos en el objeto quaternion la información de rotación.
  • Línea 23: Rotamos el cañón.




  • Si ahora ejecutáis el juego podéis ver como la esfera rota alrededor del cañón y el cañón apunta a la esfera.



Método Quaternion.Lerp

Pertenece a la clase Quaternion.
Esto permite que la rotación del objeto vaya 'retrasada' con respecto a la rotación del objeto que estamos siguiendo.
Es lo que sucede en la realidad. Cuando un tanque gira para perseguir a un enemigo no lo hace inmediatamente, sino que gira a una velocidad determinada.
Si lo aplicamos al ejemplo anterior:
  • Modificamos el script UD2_Mov_Rotac_6B
 1 using UnityEngine;
 2 
 3 /** Ejemplo de script que muestra un ejemplo de uso del
 4  *   método Quaternion.LookRotation
 5  *  
 6  */
 7 public class UD2_Mov_Rotac_6B : MonoBehaviour
 8 {
 9     [SerializeField]
10     private Transform posicionRotar;
11 
12     [SerializeField]
13     private float velocidad;
14 
15     // Update is called once per frame
16     void Update()
17     {
18         Vector3 dir = posicionRotar.position - transform.position;     // No hace falta normalizar el vector3 ya que no lo vamos a usar para movernos
19         Quaternion quaternion = Quaternion.LookRotation(dir, Vector3.up);
20         transform.rotation = Quaternion.Lerp(transform.rotation,quaternion,velocidad*Time.deltaTime);
21 
22 
23     }
24 }
  • Línea 13: Definimos la velocidad a la que el cañón va a rotar siguiendo la esfera.
  • Línea 20: Creamos un objeto Quaternion con la función Lerp, la cual irá rotando desde la posición del cañón a la posición rotada que apunta a la esfera.


Unity3d mov rotac 10.jpg

Método LookAt

Rota un GameObject pero siempre va a mover el eje 'forward' (el eje de color azul en Unity). No podemos indicar que mueva otro eje. El eje 'forward' siempre va a mirar al enemigo que queramos perseguir.
Normalmente se suele utilizar para que la cámara mire al protagonista cuando lo va siguiendo.
Cualquier rotación que tengamos la va a obviar y lo único que hará será rotar el objeto de acuerdo al 'vector up' que le digamos (por defecto es el eje Y).
El 'vector up' es un vector dirección que normalmente se asocia a cámaras.
Unity3d mov rotac 11a.jpg
Imagen obtenida de macedoniamagazine.frodrig.com


Este vector sirve para 'rotar' la cámara (o un objeto en este caso). Imaginar que claváis un palo en una cámara y que la atraviesa de arriba-abajo (vector up). Ahora coger la cámara y moverla a izquierda-derecha hasta que apunte a objeto destino. Si el objeto destino no está en la línea de la cámara el palo sube-baja hasta situarlo en la misma. En Unity, el Vector up siempre es perpendicular en 90 grados al vector Forward, por lo que irá rotando el eje Y para que esté siempre a esa distancia.


  • Vamos a construir la siguiente escena (después podemos aplicar lo aprendido en el punto anterior):
Unity3d mov rotac 11.jpg
Nos vamos a la 'Asset Store' y descargamos un tanque gratuito (free).
Arrastramos dos tanques de la carpeta Models del tanque importado a la escena (puede ser necesario cambiar la escala del modelo, recuerda que es un parámetro que se cambia en el modelo de nombre Scale Factor).
Les cambiamos el nombre a los tanques y los colocamos como en la imagen.
Creamos un plano para dar la sensación de que haya un suelo.
Creamos el script UD_Movim_Rotac_7
 1 using UnityEngine;
 2 
 3 /**
 4  * Ejemplo de uso del método Transform.LookAt
 5  */
 6 public class UD2_Mov_Rotac_7 : MonoBehaviour {
 7 
 8     [SerializeField]
 9     private Transform tanqueObjetivo;
10 
11 	
12 	// Update is called once per frame
13 	void Update () {
14 
15         transform.LookAt(tanqueObjetivo);
16 
17 	}
18 }
Como vemos el código es mucho más sencillo.
  • Línea 9: Necesitamos saber cual es la posición del tanque que queremos perseguir, por lo que creamos un objeto de la clase Transform que sea público.
  • Línea 15: Vamos a hacer que la torreta del tanque mire siempre hacia el tanque objetivo.


  • Ahora colocamos el script en la torreta del tanque atacante:


  • Si ejecutáis el juego puede ser que os llevéis una desagradable sorpresa :(
Unity3d mov rotac 14.jpg
La torreta está rotada !!!!


Esto es debido a que el método LookAt siempre va a 'mirar' con el eje forward (el de color azul) y resulta que el modelo importado, sus ejes por defecto, están rotados y no se corresponden con los ejes x-y-z de Unity:
Unity3d mov rotac 15.jpg
Si tenéis suerte y el modelo importado está correcto, no tendréis que hacer nada, pero de todas formas leeros lo siguiente por si os paso con otro modelo...
  • Podemos hacer varias cosas para solucionarlo:
  • Editar el dibujo 3D y cambiar su rotación para hacer que la posición se corresponda con la posición de los ejes de Unity.
  • Hacer uso de un GameObject vacío. Hacer la torreta 'hijo' de este GameObject y llevar el script de rotación al GameObject vacío.
Vamos a hacer esto segundo.



  • Si ejecutáis el juego ahora sí que funciona :)
Unity3d mov rotac 22.jpg


  • Nota: Recordar que 'mirar hacia algo' no implica hacer lo mismo que el objeto al que se está mirando. Así, el objeto podría rotar 360 grados y nosotros quedaríamos igual mirando hacia el mismo.
  • Como ejercicio propuesto podríais intentar hacer rotar el tanque en vez de la torreta.
Pista: Podéis rotar los componentes del tanque (torreta y cuerpo) para que coincida con la orientación de los ejes de Unity.


Programando la Escala

  • Para modificar la escala debemos de hacer uso de la propiedad 'localscale' del transform asociado como ya vimos al principio de esta entrada.
Para modificar dicha escala podemos hacer uso del vector3: Vector3.one que representa el vector (1,1,1).
Así podríamos poner: transform.localscale = Vector3.one * 5; (estaríamos asignando una escala de 5 unidades al GameObject).



Método Scale

Vamos a aplicarlo a los GameObject.


  • Creamos una esfera en al punto (0,0,0) con la cámara mirando hacia ella a cierta distancia.
Creamos el script de nombre UD_Movim_Escala_1.
 1 public class UD_Movim_Escala_1 : MonoBehaviour {
 2 
 3     private Vector3 nuevaEscala;
 4     private Vector3 reducirEscala;
 5 
 6 	// Use this for initialization
 7      void Start () {
 8 
 9         nuevaEscala = new Vector3(2, 2, 2);  // Multiplica la escala por estos valores
10         reducirEscala = new Vector3(1/nuevaEscala.x,1/nuevaEscala.y,1/nuevaEscala.z);
11      }
12 	
13      // Update is called once per frame
14      void Update () {
15 
16         if (Input.GetKeyDown(KeyCode.UpArrow))
17         {
18             transform.localScale = Vector3.Scale(transform.localScale, nuevaEscala);
19 
20         }
21         if (Input.GetKeyDown(KeyCode.DownArrow))
22         {
23             transform.localScale = Vector3.Scale(transform.localScale, reducirEscala);
24 
25         }
26 
27     }
28 }
Lo que hace el script es definir dos vectores:
  • Uno public de nombre 'nuevaEscala' (línea 3) que multiplicará la escala local del GameObject por estos valores en caso de querer aumentar la escala.
  • Una private de nombre 'reducirEscala' (línea 4 y línea 9) que es un Vector3 invertido al anterior, para el caso de querer reducir la escala.
Después en función de si presionamos la tecla 'cursor arriba' o 'cursor abajo' modifica la escala del GameObject asociado.


  • Asocia el script anterior a la esfera creada.
Ejecuta el juego y comprueba como cambia la escala:


Nota: Recordar que cuando vimos el punto del 'rendimiento' indicamos que siempre que se pueda se deben de quitar instrucciones del método Update. Por eso la instanciación del Vector3 'reducirEscala' está en el método Start().



Constraint

  • Las constraint son componentes que se añaden a un GameObject y sirven para 'enlazar' las propiedades de posición, escala y rotación de un GameObject con otros, de tal forma que la modificación en los valores de cualquiera de las propiedades anteriores, modifica las mismas propiedades en los GameObjects enlazados.



Métodos útiles


Basado en esta operación tenemos varias variantes:
  • LerpAngle: Hace lo mismo que el anterior pero sus 'valores' son interpretados como ángulos entre 0 y 360 grados.
  • InverseLerp: Devuelve el 'porcentaje' entre los valores [0-1] en que se encuentra un punto con respecto a dos dados, siendo el valor 0 el punto inicial, y el valor 1 el punto final.





Enlace a la página principal del curso



-- Ángel D. Fernández González -- (2018).