Movimientos de personajes basicos en Godot Engine

Una de las primeras cosas básicas que queremos hacer cuando iniciamos en el desarrollo de videojuegos, es un personaje simple que se pueda mover, dependiendo de lo que queramos hacer, ya sea un plataformero o un topdown por ejemplo, vamos a necesitar programar el movimiento de diferentes formas, pero en este post vamos a ver cómo hacer los movimientos mas simples posibles, explicado paso a paso.

Para realizar estos ejemplos vamos a hacer lo siguiente:

Al nodo KinematicBody2D le asignaremos un script y al Sprite le podemos poner el ícono de Godot. El KinematicBody2D requiere un CollisionShape2D y éste require un shape, aunque practicamente no le daremos uso en este ejemplo.


Nota que si deseas probar estos scripts, al final he dejado un proyecto de ejemplo para que puedas probarlo.

4 Direcciones

El movimiento en 4 direcciones se refiere a que el personaje se puede mover hacia arriba, abajo, izquierda y derecha.

Por dar un ejemplo, los juegos topdown, es decir, con vista desde arriba, tienden a tener este tipo de movimiento, como los juegos de Zelda. El código básico para hacer que se mueva en 4 direcciones es el siguiente:
extends KinematicBody2D
var mov = Vector2.ZERO
const SPEED = 300
    func _physics_process(_delta):
        mov = move_and_slide(mov)
        if Input.is_action_pressed("ui_left"):
            mov.x = -SPEED
            mov.y = 0
        elif Input.is_action_pressed("ui_right"):
            mov.x = SPEED
            mov.y = 0
        elif Input.is_action_pressed("ui_up"):
            mov.y = -SPEED
            mov.x = 0
        elif Input.is_action_pressed("ui_down"):
            mov.y = SPEED
            mov.x = 0
        else:
            mov = Vector2.ZERO

Explicación

La variable mov la declaramos para manejar el movimiento, Vector2 es la clase que sirve para manejar la posición y dirección en Godot, le ponemos .ZERO porque es el equivalente a poner Vector2(0,0), es decir, declarar el mov con valores en cero, ZERO es una constante de Vector2.

La constante SPEED es para indicar la velocidad con la que se moverá el personaje. Dentro del método _physics_process tenemos mov = move_and_slide(mov), esto es porque move_and_slide devuelve la velocidad resultante de moverlo, por ejemplo, si colisiona y se detiene, devolverá Vector2(0,0) por lo que mov pasaría a tener ese valor, es importante hacerlo de esta manera siempre para evitar problemas.

A partir de ahí, los if solo se anidan, le asignamos movimiento y detenemos el movimiento según el caso. Al final, en caso de que el usuario no aprete ningún botón, volvemos a asignar mov a sus valores en cero con Vector2.ZERO.

8 Direcciones

En caso de necesitar que el personaje se pueda mover en 8 direcciones, es decir, las 4 del punto anterior mas sus combinaciones, arriba-derecha, arriba-izquierda, etc.

Lo único que necesitamos es variar un poco la estructura de los if del script.
extends KinematicBody2D
    var mov = Vector2.ZERO
    const SPEED = 300
    func _physics_process(_delta):
        mov = move_and_slide(mov)
        if Input.is_action_pressed("ui_left"):
            mov.x = -SPEED
        elif Input.is_action_pressed("ui_right"):
            mov.x = SPEED
        else:
            mov.x = 0
        if Input.is_action_pressed("ui_up"):
            mov.y = -SPEED
        elif Input.is_action_pressed("ui_down"):
            mov.y = SPEED
        else:
            mov.y = 0

Explicación

En este ejemplo tenemos 2 if anidados, uno para el eje X, y otro para el eje Y, ambos con un else que pasa el valor a cero si no se está presionando, esto permite mezclar 2 botones, los del eje X son ui_left y ui_right, los del eje Y son ui_up y ui_down.

Estilo Asteroids o conducción

Este es uno de mis favoritos, recuerdo cuando recién comenzaba y se me hacía super complejo hacer algo como esto, pero no es tanto así, si has jugado Asteroids o similares ya sabes a qué tipo de movimiento me refiero, tener botones para girar izquierda y derecha, uno para avanzar y opcionalmente uno para retroceder, similar a la conducción de un automóvil.
extends KinematicBody2D
var mov = Vector2.ZERO
const SPEED = 300
const ROT_SPEED = PI
var rot = 0
func _physics_process(delta):
    mov = move_and_slide(mov)
    rotation = rot
    if Input.is_action_pressed("ui_right"):
        rot += ROT_SPEED * delta
    elif Input.is_action_pressed("ui_left"):
        rot -= ROT_SPEED * delta
    if Input.is_action_pressed("ui_up"):
        mov = Vector2(cos(rot), sin(rot)) * SPEED
    elif Input.is_action_pressed("ui_down"):
        mov = Vector2(cos(rot), sin(rot)) * -SPEED
    else:
        mov = Vector2.ZERO

Explicación

Aquí añadimos una variable y una constante nueva que manejan la rotación. ROT_SPEED como lo indica su nombre, es la velocidad con la que va a rotar, le asigno PI para indicarte que la rotación se mide en radianes, es decir, valores entre -Pi a Pi, esto es importante entenderlo para tener siempre una idea de los valores de rotación que asignamos, no confundir con rotation_degrees que es una alternativa a la rotación en grados y no radianes.

En el _physics_process agregamos rotation = rot para modificar la rotación del nodo, asi que dependiendo del valor que adquiera rot mostrará la rotación. En lugar de modificar el mov para girar, modificamos el rot, sumando o restando según sea el caso, lo multiplicamos por delta para generalizar el movimiento y que el resultado final no dependa necesariamente de la velocidad de refrescamiento del dispositivo, aunque usemos _physics_process puede suceder que hayan bajones de fps y cambie la velocidad final.

Luego tenemos el control de avance y retroceso con ui_up y ui_down respectivamente, aquí es donde modificamos el mov, pero dependiendo del ángulo de rotación (recuerda, en radianes) vamos a obtener el eje x, y a donde apunta, cos() y sin() significa coseno y seno, si manejas un poco de trigonometría entenderás fácilmente el tema.

El vector resultante es la dirección del ángulo (rot) con valores flotantes (decimales) que van desde -1 a 1, por esto es que lo multiplicamos por SPEED, para obtener el movimiento necesitado.

Siguiendo al ratón

Otro tipo de movimiento que solemos buscar es uno donde el personaje se mueva hacia el ratón o algún objeto en particular.
extends KinematicBody2D
var mov = Vector2.ZERO
const SPEED = 300
func _physics_process(delta):
    mov = move_and_slide(mov)
    var mouse_pos = get_global_mouse_position()
    look_at(mouse_pos)
    var dir = mouse_pos - global_position
    if Input.is_mouse_button_pressed(BUTTON_LEFT) and dir.length() > 5:
        mov = dir.normalized() * SPEED
    else:
        mov = Vector2.ZERO

Explicación

En este caso obtenemos la posición del mouse con get_global_mouse_position, y le pasamos la función look_at() que a como su nombre lo indica rota el objeto en dirección al Vector2 que le pasemos por argumento, en este caso mouse_pos.

Luego restamos mouse_pos y global_position para obtener la dirección a la que tiene que ir el personaje, podríamos decir que es restar la posición del objetivo con la posición actual.

Luego con el if validamos si el botón izquierdo del mouse está apretado y si el length del vector es mayor que cinco, esto último es necesario para saber qué tan lejos está el personaje del objetivo y evitar que este "se pase" del objetivo, piensalo un poco, el personaje viaja a cierta velocidad y cada frame avanza una cantidad de pixeles, es muy complicado que el personaje caiga exactamente en la posición objetivo, así que es mejor que deje de moverse cuando se acerca lo suficiente, en este caso podríamos decir que 5 pixeles.

Entonces asignamos a mov la dirección normalizada, el normalized() es una función que cambia los valores de un vector a su mínima expresión, con valores de -1 a 1, esto para poderlo multiplicar con SPEED y obtener la velocidad deseada.

Existen mas variaciones de movimiento

Existen muchas formas de mover un personaje u objeto, éstas son las que creo mas comunes y que te pueden interesar, si te interesa que haga mas ejemplos como estos o algún caso en particular, no dudes en dejarme un comentario.

Prueba los scripts

Comentarios