Cambio de Coordenadas - Math And Unity

¿Cómo arrastro un objeto por el escenario?

¿Cómo lo hago si quiero solo lo quiero mover en un eje o en diagonal o exagerando un eje o con en forma invertida o con cierto ángulo?

En el blog anterior vimos como dibujar funciones en Unity, ahora vamos a trabajar un poco mas con ellas y enseñar algo que tiene que ver con el Cambio de Coordenadas.
Lo primero, vamos a usar a nuestra buena amiga, función cuadratica para explicar algunas cosas. ¿La recuerdas? Se ve así:

La definición formal es y=x2 pero nosotros podemos cambiarla por unas nuevas coordenadas x' e y'.
que tal si corremos la queremos correr 2 lugares a la derecha, bueno, solo tendríamos que cambiar:
x por x+2, y listo, o mas formalmente x' = x +2, de donde obtenemos que x = x'-2.
ha este valor 2 lo voy a llamar offsetX, y lo voy a poner en la función para que quede así:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Main : MonoBehaviour
{

    public float min =-20f;
    public float max = 20f;
    public float resolucion = 0.1f;
    //nuevos 
    public float offsetX = 2f;
    public float offsetY = 0f;

    float f(float x) {
        float xp = x - offsetX;
        float y = Mathf.Pow(xp, 2f);
        return y;
    }

    void OnDrawGizmosSelected() {

        Gizmos.color = Color.red;
        float x = min;
        while (x <= max && resolucion<=0f) {
            float x0 = x;
            float y0 = f(x0);
            float x1 = x + resolucion;
            float y1 = f(x1);
            Gizmos.DrawLine(new Vector3(x0, y0), new Vector3(x1, y1));
            x = x + resolucion;
        }
    }
}

Matemáticamente hablando la función se escribiría:

y= (x-2)2

y la gráfica queda así:

Si quisiera dejarla mas arriba, digamos 3 lugares, solo tengo que cambiar la coordenada y por y'=y-3, matemáticamente hablando queda así:

(y-3)=(x-2)2
Y se vería así:

Para ello ponemos el siguiente código:
 float f(float x) {
        float xp = x - offsetX;
        float y = Mathf.Pow(xp, 2f);
        float yp = y + offsetY;
        return yp;
    }
Para x, pusé xp= x- offsetX y para y puse yp=y + offsetY y tengo que explicar el porque el + y no un -.
En la formula: (y-3)=(x-2)si yo quiero despejar y, tendría que reescribirlo como y=(x-2)2 +3 y eso fue precisamente lo que hice, calcule, y sin el offsetY y luego le agregué el offsetY.

¿y eso en que me ayuda para arrastrar objetos por el escenario?

Si quisiera cambiar, cualquier otra función u objeto de coordenadas, solo tengo que cambiar de coordenadas por x' = x + offsetX  e y' = y + offsetY, me sirve en cualquier caso, veamos un ejemplo:

  1. Construiremos un objeto y lo pondremos en cualquier lugar del escenario.
  2. Detectaremos la posición del mouse "dentro del escenario".
  3. moveremos al objeto a la posición del mouse.
  4. moveremos al objeto según la posición del mouse.


1. Objeto creado en el Escenario (y algunas cosas más)

2. Añademos el Script "Arrastrable" y queda con el siguiente codigo:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Arrastrable : MonoBehaviour
{
    //
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) {
            OnClick();
        }
    }

    void OnClick() {
        Vector3 mousePosition = Input.mousePosition;
        Debug.Log(mousePosition);
        transform.position = mousePosition;        
    }
}

Una vez que le damos a correr, nos daremos cuenta que el punto "desaparece" o más bien aparece en coordenadas como (200,300,0) lo que es muy lejos del punto de visión, pero no son numeros al azar, son las coordenadas del mouse según la pantalla y las coordenadas de la pantalla, son muy diferentes a las coordenadas del escenario del juego.
Por ejemplo, si tienes una pantalla de 1024 x  728 , y haces click al centro de la pantalla, objeto sera movido a 512 x 364, lo que es muy lejos de donde se ve.

¿Cómo arreglamos la posición del mouse según lo que vemos en pantalla?

Bueno, la posición del Mouse es relativa a la cámara que estamos mirando, por lo tanto, lo que debe hacer es un cambio de coordenadas donde el centro de la cámara sería el nuevo centro del eje de coordenadas y luego escalarlo por el ancho de la visión de la cámara y monton de cálculos más relativos a ello.


Lo bueno es que Unity, hace todos esos cálculos por ti, quedándonos el  código siguiente:

using UnityEngine;

public class Arrastrable : MonoBehaviour
{
    //
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) {
            OnClick();
        }
    }

    void OnClick() {
        Vector3 newPosition = Input.mousePosition;
        newPosition.z = Mathf.Abs(Camera.main.transform.position.z);
        newPosition = Camera.main.ScreenToWorldPoint(newPosition);
        transform.position = newPosition;
    }
}


El que hace el truco es Camera.main.ScreenToWorldPoint(newPosition) que es la función que convierte un punto en la pantalla a un punto del escenario (mundo), pero ojo, hay que hacer un truco antes de poder usarlo, hay que cambiar el eje z.

¿Porque hay que cambiar el eje z?
La pantalla es como un cono, ubicado en z=-5, al poner el eje z en 0 (la posición del mouse por defecto), la proyección queda justo en -5 donde empieza el cono y por lo tanto es invisible para la cámara, por lo tanto hay que ubicar el eje z a la distancia que hay entre la cámara al origen del sistema z = Mathf.Abs(Camera.main.transform.position.z). Si quisieras apuntar la camara a otro lugar, distinto del origen, por ejemplo al trabajar en 3D, habría que hacer hacer algo así como:
z = Mathf.Abs(Camera.main.transform.position.z-origen.z) o ponerlo como negativo si quieres que apunte al lado contrario.
Otra forma de hacerlo es tomar la posición "en pantalla" del objeto Arrastrable (WorldPointToScreen), guardarla y luego aplicarla como z.

Bueno ya explicaré ese punto, ahora unas mejoras, para el arrastre continuo.

Código para arrastrar el objeto en forma continua

using UnityEngine;

public class Arrastrable : MonoBehaviour
{
    //
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) {
            OnClick();
        }
        if (Input.GetMouseButton(0)) {
            OnPress();
        }
    }

    void OnClick() {
        //ya usaremos esta parte
    }

    void OnPress() {
        Vector3 newPosition = Input.mousePosition;
        newPosition.z = Mathf.Abs(Camera.main.transform.position.z);
        newPosition = Camera.main.ScreenToWorldPoint(newPosition);
        transform.position = newPosition;
    }
}

Con esto, ya tenemos para hacer un montón de juegos, pero tiene un pequeño pero que no me agrada, es que el objeto arrastrable, siempre termina "al centro" del mouse y a veces queremos lograr que se mueva desde un lado, por ejemplo si se tratará de un objeto más grande, ya que se vería un salto brusco entre arrastrar el mouse y mover el objeto (pues saltaría al centro a penas se toque el mouse), para evitar este problema vamos a usar el offset, dejando el codigo así.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Arrastrable : MonoBehaviour
{
    Vector3 offset;
    float profundidad;
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) {
            OnClick();
        }
        if (Input.GetMouseButton(0)) {
            OnPress();
        }
    }

    void OnClick() {
        Vector3 newPosition = Input.mousePosition;
        newPosition.z = Mathf.Abs(Camera.main.transform.position.z);
        profundidad = newPosition.z;
        newPosition = Camera.main.ScreenToWorldPoint(newPosition);
        offset = transform.position - newPosition;
    }

    void OnPress() {
        Vector3 newPosition = Input.mousePosition;
        newPosition.z = profundidad;
        newPosition = Camera.main.ScreenToWorldPoint(newPosition);
        transform.position = newPosition+offset;

    }
}

Lo que queda como esto:

Ahora le aplicaremos rotaciones al movimiento

CODIGO:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Arrastrable : MonoBehaviour
{
    Vector3 offset;
    float profundidad;
    public float offsetActivado = 1f;
    public Vector3 escala = new Vector3(1f, 1f, 0f);
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) {
            OnClick();
        }
        if (Input.GetMouseButton(0)) {
            OnPress();
        }
    }

    void OnClick() {
        Vector3 newPosition = Input.mousePosition;
        newPosition.z = Mathf.Abs(Camera.main.transform.position.z);
        profundidad = newPosition.z;
        newPosition = Camera.main.ScreenToWorldPoint(newPosition);
        offset = transform.position - newPosition;
    }

    void OnPress() {
        Vector3 newPosition = Input.mousePosition;
        newPosition.z = profundidad;
        newPosition = Camera.main.ScreenToWorldPoint(newPosition);
        newPosition.x *= escala.x;
        newPosition.y *= escala.y;
        transform.position = newPosition + offset*offsetActivado;
    }
}

Ojo, se puede multiplicar la escala de la nueva posición por -1 para obtener la dirección opuesta. O por cualquier otro valor (prueba con 2 o 0.5 se obtienen efectos extraños).

DEMO:

Por ultimo si quisiéramos aplicar una rotación, debemos hacer lo siguiente:

        newPosition.x *= escala.x;
        newPosition.y *= escala.y;

        newPosition.x = newPosition.x * Mathf.Cos(rotacion * Mathf.Deg2Rad)
                      - newPosition.y*Mathf.Sin(rotacion * Mathf.Deg2Rad);
        newPosition.y = newPosition.x * Mathf.Sin(rotacion * Mathf.Deg2Rad) 
                      + newPosition.y*Mathf.Cos(rotacion * Mathf.Deg2Rad);

        transform.position = newPosition + offset*offsetActivado;


Que se deriva de la siguiente formula matemática

y por último, la transformación por la cual retome el escribir en este blog.

Mover un Objeto en forma diagonal.

A diferencia de mover o rotar, esta vez tendremos que proyectar el movimiento del mouse sobre los ejes "diagonales", los cuales están definidos por las siguientes funciones:

f(x)  =  x;
g(x) = -x;

El código queda así, pero si me pongo a explicarlo, este se alargará mucho, pon en los comentarios si quieres que explique esta parte.




using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Diagonal : MonoBehaviour
{
    float f(float x) {
        return x;
    }

    float g(float x) {
        return -x;
    }
    void Update()
    {
        if (Input.GetMouseButton(0)) {
            OnPress();
        }
    }
    //
    void OnPress() {
        Vector3 newPosition = Input.mousePosition;
        newPosition.z = Mathf.Abs(Camera.main.transform.position.z);
        newPosition = Camera.main.ScreenToWorldPoint(newPosition);
        if (newPosition.x * newPosition.y > 0) {
            newPosition.y = f(newPosition.x);
        } else {
            newPosition.y = g(newPosition.x);
        }
        transform.position = newPosition;
    }
}

Comentarios