Menu

domingo, 2 de noviembre de 2014

Restringir Rotaciones dentro de un cono

En ocasiones se hace necesario tener que limitar la rotación de un objeto a un cono determinado. Por ejemplo cuando queremos rotar la cámara que no se salga de unos límites. La siguiente figura ilustra esta idea:




En ella, se ven reflejados el radio y el área de la base del cono, aunque realmente ninguno de estos dos valores lo vamos a usar. Lo que nos importa es el valor marcado con la Omega: el ángulo máximo alrededor del radio.

Sin hacer una descripción matemática del problema, voy a describir cómo resolver este caso en Unity, gracias a la clase Quaternion. Mi objetivo es que cualquiera pueda usar esta técnica aunque no tenga conocimientos avanzados de quaterniones y matemáticas.

Primero necesitamos declarar las variables que vamos a usar. También las inicializamos:

      public float maxAngulo= 45.0f; // Ángulo máximo de rotación

    private Quaternion rotacionBase; // Rotación base
    private Quaternion rotacionDeseada; // Rotación deseada

    void Start () {
        // Inicialización de variables
       
rotacionBase    = transform.rotation;
       
rotacionDeseada = Quaternion.identity;

    }




 
 A continuación, en la función LateUpdate (suponiendo que estemos moviendo la cámara), obtenemos la rotación deseada con la función Euler de la clase Quaternion, que transforma la entrada del ratón, en forma de ángulos Euler (Vector3), en un quaternion.



    void LateUpdate() {

       // Guardamos la rotación en z para hacer correcciones posteriores
       float z = transform.localEulerAngles.z;

      
rotacionDeseada = Quaternion.Euler(Input.GetAxis("Mouse Y"),Input.GetAxis("Mouse X"),0);
 


El siguiente valor que calculamos es el ángulo deseado, en base a la rotación deseada. Para ello usamos la función Angle, que devuelve el ángulo entre la rotación base y la rotación deseada calculada justo antes. Debemos hacer la multiplicación de la rotación deseada (input de ratón) por la rotación del transform, para poder obtener el valor.
            





       float anguloDeseado= Quaternion.Angle(
rotacionBase,transform.rotation * rotacionDeseada );
    


Si el ángulo obtenido es mayor que nuestro ángulo máximo, entonces utilizamos la función RotateTowards (rotar hacia), desde la rotación obtenida HACIA la rotación base, tanto como el ángulo obtenido menos el ángulo máximo.

Es decir, gráficamente lo que hacemos es lo siguiente:

Hay que tener en cuenta que en la figura "Ángulo Deseado" y "Rotación Deseada" hacen referencia a las variables. Es lo calculado previamente en base al input del ratón, por eso la palabra "deseado". Si te fijas, si al ángulo deseado le restamos el ángulo base, tenemos el ángulo sobrante.





    if ( anguloDeseado> maxAngulo) {
       // Limitación: Rotamos sólo la resta entre el límite y la rotación deseada
      transform.rotation = Quaternion.RotateTowards(transform.rotation * rotacionDeseada,rotacionBAse,anguloDeseado - maxAngulo);
    }
    else {
      // Rotamos libremente en el caso de que estemos dentro del cono
      transform.rotation = Quaternion.Slerp(transform.rotation, transform.rotation * rotacionDeseada,0.25f);
    }

    // Corregimos en z rotaciones no deseadas
    Vector3 angles = transform.localEulerAngles;
    angles.z = z;
     transform.localEulerAngles = angles;
 }
   


Para realizar este tutorial, me he basado en el siguiente hilo de los foros de Unity:
http://answers.unity3d.com/questions/32383/limiting-rotation-to-solid-angle.html

donde el usuario Robert Grant da el script original que he utilizado para explicar el método (está al final del post, no es el marcado como respuesta correcta).


Espero que os sirva para poder limitar vuestras rotaciones. Cualquier duda me la podéis escribir en los comentarios y trataré de aclararla.

4 comentarios:

  1. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  2. Hola, pues una forma sencilla de hacerlo, sin complicarse con la programación, puede ser poner un collider delante de la cámara (asumo que es para rotar la cámara), y lanzar un raycast desde la misma.
    Mira la función SceenPointToRay: http://docs.unity3d.com/ScriptReference/Camera.ScreenPointToRay.html

    Cuando choque, puedes hacer un LookAt al punto donde ha chocado, así la cámara mirará a donde apuntas con el dedo.

    Un saludo :)

    ResponderEliminar
  3. perdona por no responder a tiempo y ya lo logre lo que yo quería hacer era que una torre en 2d para que disparara en un punto touch preciso
    aki esta el código por si lo necesitas
    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;


    public class FuncionCanon : MonoBehaviour {
    public Texture bala;
    public Transform punto;


    void Update() {
    Vector3 puntoDir = punto.position - transform.position;
    Vector3 nuevaDir = Vector3.RotateTowards(transform.forward, puntoDir, 1, 90F);
    Debug.DrawRay(transform.position, nuevaDir, Color.red);
    transform.rotation = Quaternion.LookRotation(nuevaDir);



    }

    }

    ResponderEliminar
  4. Me alegro de que lo solucionaras ¿No te dan problemas los vectores3 con las rotaciones en 2D? Puedes considerar el uso de Vector2 para 2D, suele ser más práctico.

    ResponderEliminar