Movimiento de Personaje RPG – Old Style

No hace mucho tiempo que comencé la creación de un videojuego RPG/Dungeons (el cual espero poder finalizar a fin de año junto a mi grupo de desarrollo) y la verdad es que estuve estudiando mucho acerca del tema, tanto en el aspecto RPG como en el aspecto de los Dungeons. Principalmente lo que me llevó a escribir este post, es el aspecto específico del movimiento de un personaje en un RPG de la vieja escuela, y también obviamente el hecho de querer compartir esta información por si alguien tuviera el mismo problema y no supiera como enfrentarlo o resolverlo, que fue lo que me sucedió a mi durante unos días, en los que me tuve que poner a investigar, para finalmente poder obtener un resultado decente, aunque no el más automatizado. A continuación procedo a explicarles cuales fueron mis errores, resultados y como me fue posible darle una solución concreta al problema, sin más, comenzamos con este post.


Problema abordado / Objetivo

En términos generales el problema que estaba abordando, era un problema que parecía ser bastante simple, y que de hecho lo han utilizado muchísimos juegos hasta el momento, sobre todo los más antiguos RPG y ahora último en la mayoría de los Dungeons, sin embargo , esa impresión solo me duro hasta que realmente me propuse implementar el script que permitiría al personaje moverse de cuadro en cuadro, es decir, siguiendo un grid, o también llamado saltando de tile en tile. Me imagino que saben a lo que me refiero, pero por si quedarán dudas es esto básicamente.

Tal y como se puede apreciar en el video del famoso videojuego Pokemon para la GameBoylos diseñadores eligieron el sistema de grid para diseñar los niveles, y es por esto que el personaje debe moverse dentro de esos cuadrados de manera exacta, para dar la sensación de forma y rigidez, que a su vez permite al jugador comprender mejor los movimientos del personaje de manera rápida, limitándolo únicamente a moverse por un cuadro a la vez, aunque obviamente, si el jugador así lo desea, puede mantener presionado la tecla de movimiento y el personaje seguirá moviendose hasta que el jugador suelte la tecla. En otras palabras mi objetivo principal era poder imitar este sistema de movimiento de la mejor manera posible dentro de Unity, obviamente limitándome a utilizar los recursos que este motor provee para esto.


 Primeros intentos

La primera disyuntiva sobre la cual tuve que trabajar y decidir que hacer fue si elegir utilizar las físicas (rigidbody) para mover al personaje o simplemente hacerlo mediante el uso y modificación de sus coordenadas (transform)

En primer lugar pensé en la decisión de utilizar las físicas que entrega Unity para permitir al personaje moverse, pero pronto me di cuenta que en realidad no serviría ya que en esta no se puede controlar el movimiento hacia una posición determinada, en un determinado intervalo de tiempo, sino que más bien se puede mover al personaje de manera libre por el terreno. Cree este script pero no sirvió de mucho…

[RequireComponent(typeof (Rigidbody2D))]
public class PlayerMovement : MonoBehaviour {
        [Range(1f, 10f)]
        public float speed = 1f;
        private Rigidbody2D rbd2D;

        void Awake(){
             rbd2D = GetComponent<Rigibody2D>();
        }

        void Start(){
             rbd2D.gravityScale = 0f;
        }

        void FixedUpdate(){
             Vector2 movement = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical");
             rbd2D.MovePosition(rbd2D.position + movement * speed * Time.fixedDeltaTime);
        } 

} 

Resultados inesperados

Tal como dije anteriormente lo único que uno puede lograr mediante la utilización de las físicas en Unity, con respecto al movimiento de un personaje es que este se pueda mover de manera libre, sin seguir un patrón determinado, como por ejemplo, moverse de 1 unidad en 1 unidad de distancia, ya que esto solo es controlable por el jugador. Aquí un video de lo que sucedía cuando se utilizaba el script que deje arriba.


Solución definitiva

Para mi fortuna luego de seguir investigando, y ya habiendo probado todas las funciones posibles con Rigidbody, no me quedaban más opciones que utilizar el Transform del objeto Player. Al principio tenía ciertos recelos con respecto a esto, ya que se supone que si uno quiere realizar cuestiones físicas debería utilizar Rigidbody, más que todo porque al utilizar el Transform de un objeto, este se vuelve a dibujar cada vez que cambia su posición casi de manera manual, independiente de que se realice por código. Luego de hacer varios test de rendimiento me di cuenta que en realidad a pesar de que afecta más que Rigidbody, al  menos para este caso en particular, no influye en gran medida, como para que el juego se quede congelado o algo por el estilo así que decidí finalmente que esta era la mejor opción para mover al personaje tal y como se hacía en los RPG de antaño.

Para esto estudie la función MoveTowards de la clase Vector3 que según la descripción oficial de la documentación de Unity, dice lo siguiente:

Mueve un punto actual en una línea recta hacia un punto de destino.

En otras palabras, si utilizamos el punto actual como la posición actual del personaje y a esto le agregamos un nuevo vector para que el punto de destino quede bien configurado entonces podemos mover a nuestro personaje tal y como queremos. El script es el siguiente:

public class PlayerMovement : MonoBehaviour {
        [Range(1f, 10f)]
        public float speed = 1f;
        private Vector3 nextPosition;
        private bool isWalking;

        void Start(){
             nextPosition = transform.position;
             isWalking = false;
        }

        void Update(){
             float horizontalMovement = Input.GetAxisRaw("Horizontal");
             float verticalMovement = Input.GetAxisRaw("Vertical");

             // Si el jugador está quieto.
             if(transform.position == nextPosition){
                  isWalking = false;
             }

             // Si el jugador quiere moverse y no está caminando.
             // Se calcula la posición futura.
             if(horizontalMovement != 0 && !isWalking){
                  nextPosition += Vector3.right * horizontalMovement;
             }else if(verticalMovement != 0 && !isWalking){
                  nextPosition += Vector3.up * verticalMovement;
             }

             // Si la posición futura es distinta de la actual es porque el jugador quiere mover al personaje...
             if(nextPosition != transform.position){
                   transform.position = Vector3.MoveTowards(transform.position, nextPosition, Time.deltaTime * speed);
                   if(!isWalking){ isWalking = true; }
             }
        }
} 

Y ya con este script funcionando la cosa mejora muchísimo, como se puede observar en el video, pero aún falta algo relevante, que es el hecho de no pasar por sobre los obstáculos o traspasar las paredes.

Yo solucioné esto de la manera más simple posible, utilicé un Raycast para determinar si es que había algún obstáculo que se opusiera al movimiento del personaje, si es que había uno entonces simplemente no permito al personaje moverse hacia ese lugar. Obviamente para que el raycast no verifique con todos los objetos, lo que hice también fue utilizar un LayerMask, que permite simplemente asignarle a los obstáculos el layer de intransitable o unwalkable. Tal como se puede apreciar en la imagen…

Captura de pantalla 2016-01-24 a las 13.50.53
Obstáculos con layer : Unwalkable

El script quedo de la siguiente manera.

public class PlayerMovement : MonoBehaviour {
        [Range(1f, 10f)]
        public float speed = 1f;
        public LayerMask unwalkableMask;
        private Vector3 nextPosition;
        private bool isWalking;

        void Start(){
             nextPosition = transform.position;
             isWalking = false;
        }

        void Update(){
             float horizontalMovement = Input.GetAxisRaw("Horizontal");
             float verticalMovement = Input.GetAxisRaw("Vertical");

             // Si el jugador está quieto.
             if(transform.position == nextPosition){
                  isWalking = false;
             }

             // Si el jugador quiere moverse y no está caminando.
             // Se calcula la posición futura.
             if(horizontalMovement != 0 && !isWalking){
                  nextPosition += Vector3.right * horizontalMovement;
             }else if(verticalMovement != 0 && !isWalking){
                  nextPosition += Vector3.up * verticalMovement;
             }

             // Si la posición futura es distinta de la actual es porque el jugador quiere mover al personaje...
             if(nextPosition != transform.position){
                  Vector2 dir = nextPosition - transform.position;
                  Raycasthit2D hit = Physics2D.Raycast(transform.position, dir, dir.sqrMagnitude, unwalkableMask.value);

             // Se verifica mediante un raycast que no se interponga nada al movimiento.
             if(hit.collider == null){
                  transform.position = Vector3.MoveTowards(transform.position, nextPosition, Time.deltaTime * speed);
                  if(!isWalking){ isWalking = true; }
             }else{
                  Debug.LogError("There is an obstacle, the player can't move");
                  nextPosition = transform.position;
}
             }
        }
} 

Y de esta manera al fin podemos tener un movimiento más que aceptable para nuestro objetivo principal, que era el hecho de poder moverse en un grid, tal y como en los viejos RPG

Espero que este post les sirva, si es que quedará alguna duda, los invito a compartirla en la caja de comentarios. Saludos y suerte con sus proyectos.

Advertisements

3 thoughts on “Movimiento de Personaje RPG – Old Style

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s