Movimiento en 2D

Introducción

Todos los principiantes han pasado por esto: «¿Cómo muevo mi personaje?» Dependiendo del estilo de juego que estés haciendo, puedes tener requerimientos especiales pero, en general, el movimiento en la mayoría de los juegos 2D está basado en una pequeña cantidad de estilos.

Utilizaremos KinematicBody2D para estos ejemplos, pero el principio aplica también a otros tipos de nodos (Area2D, RigidBody2D).

Organización

Cada ejemplo mostrado debajo, usa la misma organización de escena. Comienza con un KinematicBody2D con dos hijos: Sprite y CollisionShape2D. Puedes utilizar el ícono de Godot («icon.png») o cualquier otra imagen que tengas para la textura del Sprite.

Abre Projecto -> Ajustes del Proyecto y selecciona la pestaña Mapa de entradas. Agrega las siguientes acciones (ver InputEvent para más detalles):

../../_images/movement_inputs.png

Movimiento en 8 sentidos

En este escenario, quieres que el usuario pueda presionar las cuatro teclas direccionales (arriba/izquierda/abajo/derecha o W/A/S/D) y se mueva en la dirección indicada. El nombre «movimiento en 8 sentidos» (o 8-way movement en inglés) viene del hecho de que el jugador se puede mover diagonalmente al presionar dos teclas al mismo tiempo.

../../_images/movement_8way.gif

Añade un script al cuerpo cinemático y agrega el siguiente código:

extends KinematicBody2D

export (int) var speed = 200

var velocity = Vector2()

func get_input():
    velocity = Vector2()
    if Input.is_action_pressed('right'):
        velocity.x += 1
    if Input.is_action_pressed('left'):
        velocity.x -= 1
    if Input.is_action_pressed('down'):
        velocity.y += 1
    if Input.is_action_pressed('up'):
        velocity.y -= 1
    velocity = velocity.normalized() * speed

func _physics_process(delta):
    get_input()
    velocity = move_and_slide(velocity)
using Godot;
using System;

public class Movement : KinematicBody2D
{
    [Export] public int Speed = 200;

    Vector2 velocity = new Vector2();

    public void GetInput()
    {
        velocity = new Vector2();

        if (Input.IsActionPressed("right"))
            velocity.x += 1;

        if (Input.IsActionPressed("left"))
            velocity.x -= 1;

        if (Input.IsActionPressed("down"))
            velocity.y += 1;

        if (Input.IsActionPressed("up"))
            velocity.y -= 1;

        velocity = velocity.Normalized() * Speed;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        velocity = MoveAndSlide(velocity);
    }
}

En la función get_input() comprobamos si están presionadas las 4 teclas y las sumamos para obtener el vector velocidad. Esto tiene el beneficio de que dos teclas opuestas se cancelan, pero también resulta en un movimiento diagonal más rápido debido a que ambas direcciones son agregadas juntas.

Podemos prevenir eso si normalizamos el vector velocidad, lo que significa que el vector tendrá una longitud total igual a 1, y de ese modo multiplicamos por el valor total esperado de velocidad.

Truco

Si nunca antes has usado cálculo vectorial (operaciones matemáticas entre vectores) o necesitas un recordatorio, puedes ver la explicación del uso en Godot en Matemáticas vectoriales.

Rotación + movimiento

Este tipo de movimientos es a veces llamado «estilo Asteroids» porque recuerda al funcionamiento del juego clásico de ese nombre. Presionando izquierda/derecha rota el personaje, mientras que arriba/abajo lo mueve hacia adelante y hacia atrás acorde a la dirección a la que apunta.

../../_images/movement_rotate1.gif
extends KinematicBody2D

export (int) var speed = 200
export (float) var rotation_speed = 1.5

var velocity = Vector2()
var rotation_dir = 0

func get_input():
    rotation_dir = 0
    velocity = Vector2()
    if Input.is_action_pressed('right'):
        rotation_dir += 1
    if Input.is_action_pressed('left'):
        rotation_dir -= 1
    if Input.is_action_pressed('down'):
        velocity = Vector2(-speed, 0).rotated(rotation)
    if Input.is_action_pressed('up'):
        velocity = Vector2(speed, 0).rotated(rotation)

func _physics_process(delta):
    get_input()
    rotation += rotation_dir * rotation_speed * delta
    velocity = move_and_slide(velocity)
using Godot;
using System;

public class Movement : KinematicBody2D
{
    [Export] public int Speed = 200;
    [Export] public float RotationSpeed = 1.5f;

    Vector2 velocity = new Vector2();
    int rotationDir = 0;

    public void GetInput()
    {
        rotationDir = 0;
        velocity = new Vector2();

        if (Input.IsActionPressed("right"))
            rotationDir += 1;

        if (Input.IsActionPressed("left"))
            rotationDir -= 1;

        if (Input.IsActionPressed("down"))
            velocity = new Vector2(-Speed, 0).Rotated(Rotation);

        if (Input.IsActionPressed("up"))
            velocity = new Vector2(Speed, 0).Rotated(Rotation);

        velocity = velocity.Normalized() * Speed;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        Rotation += rotationDir * RotationSpeed * delta;
        velocity = MoveAndSlide(velocity);
    }
}

Aquí hemos agregado dos variables nuevas para seguir la dirección de rotación y velocidad. De nuevo, presionando dos teclas cancelará el valor resultando una rotación nula. La rotación es aplicada directamente a la propiedad rotation del cuerpo.

Para asignar la velocidad, usamos el método Vector2.rotated() así apunta a la misma dirección que el cuerpo. rotated() es una función útil de los tipos vectores que se puede usar en muchas circunstancias en la que de otro modo deberían aplicarse funciones trigonométricas.

Rotación + movimiento (ratón)

Este estilo de movimiento es una variante del anterior. Ahora, la dirección es indicada por la posición del ratón en lugar del teclado. El personaje siempre «mirará hacia» el puntero del ratón. El movimiento hacia adelante y hacia atras se mantiene igual.

../../_images/movement_rotate2.gif
extends KinematicBody2D

export (int) var speed = 200

var velocity = Vector2()

func get_input():
    look_at(get_global_mouse_position())
    velocity = Vector2()
    if Input.is_action_pressed('down'):
        velocity = Vector2(-speed, 0).rotated(rotation)
    if Input.is_action_pressed('up'):
        velocity = Vector2(speed, 0).rotated(rotation)

func _physics_process(delta):
    get_input()
    velocity = move_and_slide(velocity)
using Godot;
using System;

public class Movement : KinematicBody2D
{
    [Export] public int Speed = 200;

    Vector2 velocity = new Vector2();

    public void GetInput()
    {
        LookAt(GetGlobalMousePosition());
        velocity = new Vector2();

        if (Input.IsActionPressed("down"))
            velocity = new Vector2(-Speed, 0).Rotated(Rotation);

        if (Input.IsActionPressed("up"))
            velocity = new Vector2(Speed, 0).Rotated(Rotation);

        velocity = velocity.Normalized() * Speed;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        velocity = MoveAndSlide(velocity);
    }
}

Aquí utilizamos el método look_at() de Node2D para apuntar el jugador a una posición dada. Sin esta función se puede obtener el mismo efecto asignando el ángulo de este modo:

rotation = get_global_mouse_position().angle_to_point(position)
var rotation = GetGlobalMousePosition().AngleToPoint(Position);

Clic y mover

Este último ejemplo utiliza sólo el ratón para controlar el personaje. Haciendo clic en la pantalla ocasiona que el jugador se mueva a la posición indicada.

../../_images/movement_click.gif
extends KinematicBody2D

export (int) var speed = 200

var target = Vector2()
var velocity = Vector2()

func _input(event):
    if event.is_action_pressed('click'):
        target = get_global_mouse_position()

func _physics_process(delta):
    velocity = (target - position).normalized() * speed
    # rotation = velocity.angle()
    if (target - position).length() > 5:
        velocity = move_and_slide(velocity)
using Godot;
using System;

public class Movement : KinematicBody2D
{
    [Export] public int Speed = 200;

    Vector2 target = new Vector2();
    Vector2 velocity = new Vector2();

    public override void _Input(InputEvent @event)
    {
        if (@event.IsActionPressed("click"))
        {
            target = GetGlobalMousePosition();
        }
    }

    public override void _PhysicsProcess(float delta)
    {
        velocity = (target - Position).Normalized() * Speed;
        // Rotation = velocity.Angle();
        if ((target - Position).Length() > 5)
        {
            velocity = MoveAndSlide(velocity);
        }
    }
}

Note la comprobación length() que se hace antes del movimiento. Sin esto, el cuerpo podría «temblar» al llegar al destino, ya que el movimiento puede pasarse un poco e intentará moverse hacia atrás, pasándose de largo nuevamente y repitiendo el proceso.

Quitando los comentarios en la línea que dice rotation hará también que el cuerpo apunte a la dirección del movimiento.

Truco

Esta técnica también puede utilizarse como base para un personaje «persecutorio». La posición del objetivo puede ser la de cualquier objeto al que quieras moverte.

Sumario

Estos ejemplos pueden ser útiles como punto de partida para tus propios proyectos. Siéntete libre de usarlos y experimentar con ellos para ver lo que puedes hacer.

Puedes descargar este proyecto de ejemplo aquí: 2D_movement_demo.zip