Unity: Plataformas móviles en juego 2D

¡Hola developers! Hoy os traigo un pequeño tutorial sobre algo tan sencillo y útil como son las plataformas móviles para cualquier tipo de juego 2D. Construiremos una plataforma totalmente configurable a la que le podremos definir su tipo de movimiento (movimiento continuo o movimiento en ‘ping-pong’ con un máximo recorrido), la dirección del movimiento (horizontal, vertical o ambas) y su velocidad (tanto su velocidad vertical como su velocidad horizontal). Empecemos…

Creación del GameObject ‘PlataformaMovil’

Vamos a crear un GameObject vacío y le añadiremos los siguientes componentes:

  • BoxCollider: Será el que permitirá que nuestra plataforma sea colisionable y, por tanto, que cualquier objeto pueda posarse sobre ella.
  • SpriteRenderer: A su propiedad ‘Sprite‘ le asignaremos la imagen que queramos usar para nuestra plataforma.
GameObject 'PlataformaMovil'
GameObject ‘PlataformaMovil’

Creación del script ‘MovimientoPlataforma.cs’

Ahora vamos a crear el script que se encargará de mover la plataforma y en el que podremos configurar todos sus parámetros de movimiento, dirección y velocidad.

Comenzamos definiendo las variables públicas que luego nos interesará configurar desde el inspector y algunas variables privadas que necesitaremos:


using UnityEngine;
using System.Collections;
using System;

// Tipos enumerados para definir las direcciones
public enum DireccionH { Izquierda, Derecha }
public enum DireccionV { Arriba, Abajo }

public class MovimientoPlataforma : MonoBehaviour
{
    // Es la velocidad a la que se moverá la plataforma en el eje horizontal.
    public float VelocidadH = 0.3F;

    // Indica el sentido horizontal al que comenzará a moverse la plataforma.
    public DireccionH SentidoH = DireccionH.Derecha;

    // Es la velocidad a la que se moverá la plataforma en el eje vertical.
    public float VelocidadV = 0.0F;

    // Indica el sentido vertical al que comenzará a moverse la plataforma.
    public DireccionV SentidoV = DireccionV.Arriba;

    // Es el la distancia que recorrerá la plataforma en modo ping-pong.
    // Para desactivar el modo ping-pong, establecer esta variable a -1.
    public float MaxRecorridoPingPong = 5.0F;

    // Variables privadas
    private Transform PlatformTransform;
    private float WalkedDistanceH = 0.0F;
    private float WalkedDistanceV = 0.0F;
    private float ReferencePingPongHPosition;
    private float ReferencePingPongVPosition;
    private Vector3 InitialPlatformPosition;
}

Creamos la función Start() e inicializamos algunas variables:


    void Start ()
    {
        PlatformTransform = transform;

        // Guardamos la posición inicial de la plataforma
        InitialPlatformPosition = PlatformTransform.position;

        // Inicializamos la posición de referencia para el cálculo de rebote horizontal (ping-pong)
        ReferencePingPongHPosition = PlatformTransform.position.x;

        // Inicializamos la posición de referencia para el cálculo de rebote vertical (ping-pong)
        ReferencePingPongVPosition = PlatformTransform.position.y;
	}

Bien, ahora pasemos a la creación de la función Update(), en la que definiremos todo el comportamiento de la plataforma:


void Update ()
{
    // Calculamos la distancia horizontal recorrida desde el último rebote
    WalkedDistanceH = Math.Abs(PlatformTransform.position.x - ReferencePingPongHPosition);

    // Calculamos la distancia vertical recorrida desde el último rebote
    WalkedDistanceV = Math.Abs(PlatformTransform.position.y - ReferencePingPongVPosition);

    if (MaxRecorridoPingPong != -1 && WalkedDistanceH >= MaxRecorridoPingPong)
    {
        // Si la función de Ping-Pong esta activada y se ha hecho el máximo recorrido en horizontal, la plataforma cambia de sentido
        if (SentidoH == DireccionH.Izquierda)
        {
            SentidoH = DireccionH.Derecha;
        }
        else
        {
            SentidoH = DireccionH.Izquierda;
        }

        // Actualizamos la posición horizontal de referencia para el cálculo de rebote horizontal (ping-pong)
        ReferencePingPongHPosition = PlatformTransform.position.x;
    }

    if (MaxRecorridoPingPong != -1 && WalkedDistanceV >= MaxRecorridoPingPong)
    {
        // Si la función de Ping-Pong está activada y se ha hecho el máximo recorrido en vertical, la plataforma cambia de sentido
        if (SentidoV == DireccionV.Arriba)
        {
            SentidoV = DireccionV.Abajo;
        }
        else
        {
            SentidoV = DireccionV.Arriba;
        }

        // Actualizamos la posicion vertical de referencia para el calculo de rebote vertical (ping-pong)
        ReferencePingPongVPosition = PlatformTransform.position.y;
    }

    // Configuramos el sentido del movimiento horizontal
    if (SentidoH == DireccionH.Izquierda)
    {
        VelocidadH = -Math.Abs(VelocidadH);
    }
    else
    {
        VelocidadH = Math.Abs(VelocidadH);
    }

    // Configuramos el sentido del movimiento vertical
    if (SentidoV == DireccionV.Abajo)
    {
        VelocidadV = -Math.Abs(VelocidadV);
    }
    else
    {
        VelocidadV = Math.Abs(VelocidadV);
    }

    // Movemos la plataforma
    PlatformTransform.Translate(new Vector3(VelocidadH, VelocidadV, 0) * Time.deltaTime);
}

Con esto, nuestra plataforma ya se movería correctamente, pero seguimos teniendo un problema, y es que cuando coloquemos a nuestro personaje (o cualquier otro objeto) encima de la plataforma, éste no será transportado por ella. Este problema ocurre debido a que, cuando nuestro script mueve la plataforma, está interactuando directamente sobre su transform, es decir, está cambiando continuamente sus corrdenadas en el espacio y dichas coordenadas son independientes de las coordenadas que tienen el resto de objetos, por tanto cada objeto se rige por su propio transform. Para arreglar esto existe una solución muy sencilla y se trata de hacer que, cada vez que un objeto toque la plataforma, automáticamente pase a convertirse en hijo de ésta. De este modo, los objetos afectados pasarán de tener unas coordenadas basadas en el espacio global de la escena a tener unas coordenadas basadas en el espacio local de la plataforma, por lo cual seguirán su movimiento. Veamos cómo hacer esto:

Añadir trigger al GameObject ‘PlataformaMovil’

Lo único que debemos hacer es añadir un nuevo BoxCollider al objeto de nuestra plataforma y activar la opción IsTrigger de dicho componente. Una vez hecho esto, lo único que tenemos que hacer es ponerle a este collider un ancho y un alto adecuados para que sobresalga un poco del otro BoxCollider que definimos al principio a la plataforma (el que no es trigger), tal y como muestro en la imagen:

GameObject 'PlataformaMovil'
GameObject ‘PlataformaMovil’
Plataforma con ambos colliders: el normal y el trigger
Plataforma con ambos colliders: el normal y el trigger

Modificar el script ‘MovimientoPlataforma.cs’

Ahora deberemos implementar los eventos OnTriggerStay() y OnTriggerExit() en nuestro script de movimiento de la plataforma para indicarle que, cada vez que un objeto entre dentro del trigger que le hemos definido, lo incluya como un objeto hijo. Será tan sencillo como esto:


void OnTriggerStay(Collider other)
{
    // Incluímos como hijo de la plataforma a cualquier objeto que se pose sobre ella
    other.transform.parent = transform;
}

void OnTriggerExit(Collider other)
{
    // Excluímos como hijo de la plataforma a cualquier objeto que se separe de ella
    other.transform.parent = null;
}

Asignar el script ‘MovimientoPlataforma.cs’ al GameObject ‘PlataformaMovil’

Una vez tenemos completado nuestro script, solo nos queda asignárselo al GameObject de nuestra plataforma.

Script 'MovimientoPlataforma' asignado al GameObject
Script ‘MovimientoPlataforma’ asignado al GameObject

Y eso es todo, ya tenemos nuestra plataforma móvil lista para usarla donde queramos y cómo queramos. Éstos son los diferentes comportamientos que tendría en función de cómo configuremos las variables del script en su inspector:

  • Movimiento horizontal en ping-pong
    • VelocidadH = velocidad a la que queramos que se mueve en el eje X.
    • SentidoH = Izquierda o Derecha.
    • VelocidadV = 0.
    • SentidoV = indiferente.
    • MaxRecorridoPingPong = distancia que queramos que recorra.
  • Movimiento horizontal continuo (sin ping-pong)
    • VelocidadH = velocidad a la que queramos que se mueve en el eje X.
    • SentidoH = Izquierda o Derecha.
    • VelocidadV = 0.
    • SentidoV = indiferente.
    • MaxRecorridoPingPong = -1.
  • Movimiento vertical en ping-pong
    • VelocidadH = 0.
    • SentidoH = indiferente.
    • VelocidadV = velocidad a la que queramos que se mueve en el eje Y.
    • SentidoV = Arriba o Abajo.
    • MaxRecorridoPingPong = distancia que queramos que recorra.
  • Movimiento vertical continuo (sin ping-pong)
    • VelocidadH = 0.
    • SentidoH = indiferente.
    • VelocidadV = velocidad a la que queramos que se mueve en el eje Y.
    • SentidoV = Arriba o Abajo.
    • MaxRecorridoPingPong = -1.
  • Movimiento diagonal con ping-pong
    • VelocidadH = velocidad a la que queramos que se mueve en el eje X.
    • SentidoH = Izquierda o Derecha.
    • VelocidadV = velocidad a la que queramos que se mueve en el eje Y.
    • SentidoV = Arriba o Abajo.
    • MaxRecorridoPingPong = distancia que queramos que recorra.
  • Movimiento diagonal continuo (sin ping-pong)
    • VelocidadH = velocidad a la que queramos que se mueve en el eje X.
    • SentidoH = Izquierda o Derecha.
    • VelocidadV = velocidad a la que queramos que se mueve en el eje Y.
    • SentidoV = Arriba o Abajo.
    • MaxRecorridoPingPong = -1.

Espero que les haya gustado y os haya sido útil el tutorial. Por supuesto, cualquier comentario, duda o sugerencia será bien recibido. ¡Saludos y gracias por su atención! 😉

Anuncios

34 comentarios en “Unity: Plataformas móviles en juego 2D

  1. Muy buen tutorial. Llevo todo el dia con un problema y el tutorial me lo ha resuelto a la perfeccion. Por mas que he buscado no encontraba respuesta al deslizamiento de los objetos encima de otro. La solucion de hacerlo hijo no solo es sencilla sino genial y muy correcta para no tener que hacer scripts tediosos y enredados. Gracias x el aporte y espero que sirva a mas gente.

    Le gusta a 1 persona

  2. Muy bueno. Solo tengo un problema. La plataforma funciona perfectamente. Pero cuando el personaje se baja de ella es como que el objeto que le puse en los pies para matar enemigos saltando encima se desactiva y aunque me maten no vuelve. En cambio si me paso la fase en la escena siguiente vuelve a funcionar de forma correcta. Sabes que puede ser? Gracias de antemano.

    Le gusta a 1 persona

    1. Hola Shanxolin, gracias por tu comentario. Lo que dices que te ocurre podría deberse a que cuando entramos en la plataforma móvil, lo que hace el script es establecer el objeto de nuestro personaje como hijo del objeto de la plataforma y cuando salimos de ella vuelve a separarlos. Entonces a lo mejor lo que te está ocurriendo es que cuando sales de la plataforma, cuando el script va a separar los objetos se deja algunos dentro de la plataforma, ¿podría ser?. ¿El objeto de tu personaje lo tienes agrupado todo bajo un mismo game object? Para que funcione todo correctamente, tu personaje y todas sus partes deberían estar englobadas bajo un mismo objeto padre.

      Me gusta

  3. Hola Excelente Tutorial… lo de hacer hijo al personaje cuando colisiona estuvo tremendo….. Solo una cosa.. con los nuevos componentes 2D de Unity a mi no me ha funcionado bien el OnTriggerStay y el OnTriggerExit sin Embargo busque un poco y se reemplaza por el OnTriggerEnter2D y el OnTriggerExit2D y “Voilá” haz esa salvedad el el tuto…
    Saludos desde Argentina…

    Me gusta

    1. Gracias por el comentario! No quise usar los eventos 2D para no limitar el tutorial solamente para casos de juegos en 2D y que también pueda servir de guía para gente que quiera implementar una plataforma móvil en un entorno 3D 😉 Me alegro de que te haya sido de ayuda! Saludos!

      Le gusta a 1 persona

  4. Excelente tutorial sólo una duda, al subirse el personaje en ellas se empieza a quedar atrás poco a poco, no sabes como resolver esto? Será que tengo que agregar un material con fricción?

    Le gusta a 1 persona

    1. Hola Iván, gracias por el comentario. El que se encarga de arrastrar al personaje junto a la plataforma es el collider de tipo trigger. Éste debería hacer que funcione bien. LO que se me ocurre que puede estar pasándote es que dicho trigger lo hayas hecho demasiado pequeño y no detecte bien a tu personaje. En ese caso prueba a hacerlo un poco más grande a ver qué tal.

      Me gusta

    1. Hmmmm… no sabía que también te cambiaba la escala. Estás con Unity 4 o 5? De todas formas una solución que se me ocurre de primeras es poner un objeto vacío intermedio hijo de la plataforma y que sea de la misma escala que tu personaje para que te respete esa escala. Eso sí, tendrías que hacer un pequeño cambio en el código del script para que haga a tu personaje hijo de ese objeto intermedio en vez de hacerlo hijo del objeto de la propia plataforma. Pruébalo a ver qué tal!

      Me gusta

  5. Vale, funciona pero tengo otro problema:

    Mi personaje lleva como hijo dos GameObject, uno que es FirePoint en el que indico desde qué punto sale mi disparo (sólo tiene componente transform) y otro IsGrounded que mira si mi personaje está o no sobre suelo (es un BoxCollider2D trigger) para controlar animación de salto y doble salto. Al saltar sobre la plataforma, el personaje se vuelve hijo y FirePoint sigue siendo hijo pero sin embargo IsGrounded deja de serlo, ¿alguna idea?

    Me gusta

    1. No me he encontrado con este caso concreto, pero me parece que ahí ya vas a tener que controlarlo desde los eventos:

      void OnTriggerStay(Collider other)
      {
      // Incluímos como hijo de la plataforma a cualquier objeto que se pose sobre ella
      other.transform.parent = transform;
      }

      void OnTriggerExit(Collider other)
      {
      // Excluímos como hijo de la plataforma a cualquier objeto que se separe de ella
      other.transform.parent = null;
      }

      Donde además de incluir/excluir como hijo el objeto de tu personaje, también deberás tener en cuenta esos objetos hijo.

      Me gusta

    2. Aunque ahora que lo pienso, poniendo como hijo tan solo el objeto padre de tu personaje y excluyendo el resto, debería ir bien…

      Prueba a hacer esto, a ver cómo se comporta:

      void OnTriggerStay(Collider other)
      {
      if (other.name != “nombre de tu objeto FirePoint” && other.name != “nombre de tu objeto IsGrounded”)
      {
      // Incluímos como hijo de la plataforma a cualquier objeto que se pose sobre ella
      other.transform.parent = transform;
      }
      }

      void OnTriggerExit(Collider other)
      {
      if (other.name != “nombre de tu objeto FirePoint” && other.name != “nombre de tu objeto IsGrounded”)
      {
      // Excluímos como hijo de la plataforma a cualquier objeto que se separe de ella
      other.transform.parent = null;
      }
      }

      Me gusta

  6. Vaya!! Haciendo pruebas me he dado cuenta que en cuanto la plataforma llega a las coordenadas 0,0,0 de la pantalla, la distanciaRecorridaH empieza desde 0 y recorre MaxRecorricoPingPong solo una vez…. No se si me explico. Al principio si que hace el recorrido pero al llegar a 0, empieza el MaxRecorridoPingpong desde esas coordenadas 0.
    Sabes que podría ser?
    Gracias.

    Le gusta a 1 persona

    1. Tienes razón PASANDAV, hay un error en el script que hace que se comporte mal en ese caso específico que has detectado. Ya lo he corregido, el problema estaba en esta asignación:

      ReferencePingPongHPosition = Math.Abs(PlatformTransform.position.x);

      Al hacerle el valor absoluto es cuando la estaba liando. La solución sería hacer la misma asignación pero sin valor absoluto, es decir:

      ReferencePingPongHPosition = PlatformTransform.position.x;

      Lo mismo pasa en vertical, por tanto también deberíamos hacer la corrección aquí:

      ReferencePingPongVPosition = Math.Abs(PlatformTransform.position.y);

      Cambiándolo por:

      ReferencePingPongVPosition = PlatformTransform.position.y;

      Muchas gracias por informar del error y ayudar a mejorar el script 🙂 Espero que ahora te funcione bien!

      Me gusta

    2. Ah! Se me olvidaba que también, aparte de lo anterior, habría que cambiar en la función Start() lo siguiente:

      //Inicializamos la posicion de referencia para el calculo de rebote horizontal (ping-pong)
      ReferencePingPongHPosition = Math.Abs(PlatformTransform.position.x);

      //Inicializamos la posicion de referencia para el calculo de rebote vertical (ping-pong)
      ReferencePingPongVPosition = Math.Abs(PlatformTransform.position.y);

      Por esto:

      //Inicializamos la posicion de referencia para el calculo de rebote horizontal (ping-pong)
      ReferencePingPongHPosition = PlatformTransform.position.x;

      //Inicializamos la posicion de referencia para el calculo de rebote vertical (ping-pong)
      ReferencePingPongVPosition = PlatformTransform.position.y;

      Ya me dirás qué tal.
      Saludos!!

      Me gusta

    3. Y una última cosa! (lo prometo)

      Cambiar también el cálculo de WalkedDistanceH. Pasar de esto:

      WalkedDistanceH = Math.Abs(Math.Abs(PlatformTransform.position.x) – ReferencePingPongHPosition);

      A esto:

      WalkedDistanceH = Math.Abs(PlatformTransform.position.x – ReferencePingPongHPosition);

      Y lo mismo con el WalkedDistanceV. Pasar de esto:

      WalkedDistanceV = Math.Abs(Math.Abs(PlatformTransform.position.y) – ReferencePingPongVPosition);

      A esto:

      WalkedDistanceV = Math.Abs(PlatformTransform.position.y – ReferencePingPongVPosition);

      Espero que te funcione. He hecho los cambios directamente sobre el blog sin haberlo probado aún sobre Unity, pero lo haré en cuanto pueda 😉

      Me gusta

  7. Hola Santi.
    De nuevo gracias por tu rápida respuesta e interés.
    He estado probando las modificaciones. Ha mejorado pero te explico.
    Si coloco la plataforma en el lado positivo de las coordenadas (por ejemplo x: 2) y que recorra una distancia de 8, con una velocidad de 4… Hace lo siguiente:
    Llega a las coordenadas x: -10, rebota, pero ya no vuelve a la coordenada x: 2, vuelve a rebotar pero en las coordenadas x: -2 (Es decir, no vuelve al mismo punto donde ha comenzado). Creo que el problema está en cuanto pasa por las coordenadas 0 , 0. Eso hace que no mida bien la distancia recorrida. Es sólo una opinión por intentar ayudar, ya que le he dado un montón de vueltas para intentar calcular la distancia recorrida y no he sido capaz. (Aún soy muy nuevo en esto de programación y también en Unity). 😦
    Un saludo y gracias otra vez.

    Le gusta a 1 persona

    1. Ya lo he revisado PASANDAV. Está con los últimos cambios que te he comentado. Incluso he probado con los mismos valores que me dices y hace bien el movimiento ping-pong.
      Es probable que se te haya olvidado aplicar alguno (te lo puse en 3 comentarios). Te los voy a poner aquí todos como resumen mejor, vale?

      – Dentro de la función Start() cambiamos:

        ReferencePingPongHPosition = Math.Abs(PlatformTransform.position.x);
        ...
        ReferencePingPongVPosition = Math.Abs(PlatformTransform.position.y);
      

      Por:

        ReferencePingPongHPosition = PlatformTransform.position.x;
        ...
        ReferencePingPongVPosition = PlatformTransform.position.y;
      

      – Dentro de la función Update() cambiamos:

        WalkedDistanceH = Math.Abs(Math.Abs(PlatformTransform.position.x) - ReferencePingPongHPosition);
        ...
        WalkedDistanceV = Math.Abs(Math.Abs(PlatformTransform.position.y) - ReferencePingPongVPosition);
      

      Por:

        WalkedDistanceH = Math.Abs(PlatformTransform.position.x - ReferencePingPongHPosition);
        ...
        WalkedDistanceV = Math.Abs(PlatformTransform.position.y - ReferencePingPongVPosition);
      

      – Por último, dentro también de la función Update(), más abajo, cambiamos:

        ReferencePingPongHPosition = Math.Abs(PlatformTransform.position.x);
        ...
        ReferencePingPongVPosition = Math.Abs(PlatformTransform.position.y);
      

      Por:

        ReferencePingPongHPosition = PlatformTransform.position.x;
        ...
        ReferencePingPongVPosition = PlatformTransform.position.y;
      

      Aún así, he actualizado todo el script en esta entrada del blog, así si quieres puedes volver a copiártelo a ver qué tal.

      Me gusta

  8. Espectacular!!!
    Funciona todo!! (He probado el vertical y el horizontal) y me ha funcionado genial!!
    Jejej. Si supieras.. Llevo toda la tarde haciendo cálculos “aritmeticos” (es un decir) para poder encontrar la forma de realizarlo!!!
    En fin… Enhorabuena y perdona por toooodas las molestias.
    Muchas gracias Santi.

    Le gusta a 1 persona

  9. Buenas tardes antes que nada agradecerte enormemente por este tutorial que me ayudo muchisimo. Sin embargo me topo con un problema que no le encuentro sentido y es que al momento de querer mover la plataforma de forma vertical esta se mueve en diagonal y no encuentro error alguno te explico como esta configurada:
    velocidad H: 0
    sentido H: Izquierda (esto es indiferente tal como mencionas pero ya probe también cambiando el sentido).
    Velocidad V: 10
    Sentido V; Arriba
    Max Recorrido Ping Pong; 5

    Siempre me mueve la plataforma en forma diagonal hacia la derecha sabes porque será?
    pd estoy trabajando en unity 5.4.1

    Le gusta a 1 persona

    1. Hola Dragonoz Matuaria, gracias por tu comentario. Se me ocurre que quizás tu problema sea que el objeto de tu plataforma (el que tiene asignado el script) lo tengas girado de alguna manera y el script, al estar usando coordenadas locales, esté moviénlo en función de dicha rotación. Para asegurarnos de que no está pasando esto, prueba a cambiar esta instrucción:

      PlatformTransform.Translate(new Vector3(VelocidadH, VelocidadV, 0) * Time.deltaTime);

      Por esta:

      PlatformTransform.Translate(new Vector3(VelocidadH, VelocidadV, 0, Space.World) * Time.deltaTime);

      De este modo le indicamos al script que mueva siempre el objeto en cordenadas globales y no dependa de su rotación.

      Pruébalo y dime qué tal! Si sigues teniendo problemas analizamos otras soluciones 😉

      Saludos!

      Me gusta

  10. Hola buenas, en primer lugar querría darte las gracias por explicar todas las entradas actuales súper bien aunque yo hay cosas que no llego a entender ya que todavía soy novato y me estoy iniciando en este mundillo.
    En el apartado de crear la función void start y la de void update no entiendo muy bien como lo hiciste, si lo pusiste todo en mismo script o… En distintos ya que empiezan desde la linea 1 tanto uno como otro, espero que me lo puedas aclarar. Un saludo 🙂

    Le gusta a 1 persona

    1. Hola Álvaro, las funciones Start() y Update() van dentro del mismo script.
      Te recomiendo, antes de meterte de lleno a programar con Unity, aprender sus principios básicos para poder entender cómo funcionan sus librerías, sus funciones, su métodos, etc.
      Para ello hay bastantes cursos y tutoriales e incluso en la web oficial de Unity tienes una sección de learning bastante interesante para comenzar a meterle caña.
      Saludos y suerte!

      Me gusta

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s