Personaje cinemático (2D)

Introducción

Sí, el nombre suena extraño. «Personaje Cinemático». ¿Qué es eso? La razón es que cuando aparecieron los motores de físicas, fueron llamados motores de «Dinámicas» (porque tratan principalmente con respuestas de colisiones). Se llevaron a cabo muchos intentos para crear un controlador de personajes utilizando los motores de dinámicas, pero no fue tan fácil como parecía. Godot tiene una de las mejores implementaciones de controlador de personajes dinámicos (como puede verse en la demo 2d/plataformas), pero usarlo requiere un considerable nivel de habilidad y conocimiento de motores de físicas (o mucha paciencia con el ensayo y error).

Algunos motores de física como Havok parecen apostar por los controladores de personaje dinámico como la mejor alternativa, mientras que otros (PhysX) prefieren promover los de personaje cinemático.

Entonces, ¿cuál es la diferencia?:

  • Un controlador de personaje dinámico utiliza un cuerpo rígido con tensor de inercia infinita. Básicamente, es un cuerpo rígido que no puede rotar. Los motores de física siempre dejan chocar a lo objetos y entonces resuelven sus colisiones todas juntas. Esto hace que los controladores de personaje dinámico sean capaces de interactuar con otros objetos con físicas perfectamente (como se ve en la demo del juego de plataformas), sin embargo estas interacciones no siempre son predecibles. Las colisiones también pueden tardar más de un fotograma en ser resueltas, así que unas pocas colisiones pueden verse desplazadas un poquito. Esos problemas pueden solucionarse, pero requieren de cierta habilidad.
  • A kinematic character controller is assumed to always begin in a non-colliding state, and will always move to a non colliding state. If it starts in a colliding state, it will try to free itself (like rigid bodies do), but this is the exception, not the rule. This makes their control and motion a lot more predictable and easier to program. However, as a downside, they can’t directly interact with other physics objects (unless done by hand in code).

This short tutorial will focus on the kinematic character controller. Basically, the old-school way of handling collisions (which is not necessarily simpler under the hood, but well hidden and presented as a nice and simple API).

Physics process

Para manejar la lógica de un personaje o cuerpo cinemático, siempre se aconseja utilizar el proceso de la física, ya que es llamado antes del paso de la física y su ejecución está sincronizada con el servidor de física, también siempre es llamado la misma cantidad de veces por segundo. Esto hace que la física y el cálculo de movimiento funcionen de una manera más predecible que el proceso normal, que puede tener picos o perder precisión si la frecuencia de imagen es demasiado alta o demasiado baja.

extends KinematicBody2D

func _physics_process(delta):
    pass
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    public override void _PhysicsProcess(float delta)
    {
    }
}

Configuración de la escena

Para tener algo que probar, aquí está la escena (del tutorial de tilemap): kbscene.zip. Vamos a crear una nueva escena para el personaje. Usa el sprite robot y crea una escena como esta:

../../_images/kbscene.png

Notarás que hay un icono de advertencia junto al nodo CollisionShape2D, esto es porque no hemos definido una forma para él. Crea un nuevo CircleShape2D en la propiedad shape de CollisionShape2D. Haz clic en <CircleShape2D> para ir a las opciones correspondientes, y establece el radio en 30:

../../_images/kbradius.png

Nota: Como se mencionó anteriormente en el tutorial de física, el motor de física no puede manejar la escala en la mayoría de los tipos de formas (sólo funcionan los polígonos de colisión, planos y segmentos), así que cambia siempre los parámetros (como el radio) de la forma en lugar de escalarla. Lo mismo ocurre con los cuerpos cinemáticos/rígidos/estáticos, ya que su escala afecta a la escala de la forma.

Ahora crea un script para el personaje, el que se usa como ejemplo anterior debería funcionar como base.

Por último, instancia la escena del personaje en el tilemap, y haz que la escena del mapa sea la principal, para que se ejecute al presionar play.

../../_images/kbinstance.png

Moviendo el personaje cinemático

Vuelve a la escena del personaje y abre el script ¡la magia comienza ahora! El cuerpo cinemático no hará nada por defecto, pero tiene una función muy útil llamada KinematicBody2D.move_and_collide(). Esta función toma un Vector2 como argumento, e intenta aplicar ese movimiento al cuerpo cinemático. Si se produce una colisión, se detiene justo en el momento de la colisión.

Entonces, movamos nuestro sprite hacia abajo hasta que toque el suelo:

extends KinematicBody2D

func _physics_process(delta):
    move_and_collide(Vector2(0, 1)) # Move down 1 pixel per physics frame
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    public override void _PhysicsProcess(float delta)
    {
        // Move down 1 pixel per physics frame
        MoveAndCollide(new Vector2(0, 1));
    }
}

El resultado es que el personaje se moverá, pero se detendrá justo cuando golpee el suelo. Bastante genial, ¿no?

El siguiente paso será añadir gravedad a la combinación, de esta manera se comporta de una manera más parecida a la de un personaje real de un juego:

extends KinematicBody2D

const GRAVITY = 200.0
var velocity = Vector2()

func _physics_process(delta):
    velocity.y += delta * GRAVITY

    var motion = velocity * delta
    move_and_collide(motion)
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    const float gravity = 200.0f;
    Vector2 velocity;

    public override void _PhysicsProcess(float delta)
    {
        velocity.y += delta * gravity;

        var motion = velocity * delta;
        MoveAndCollide(motion);
    }
}

Ahora el personaje cae suavemente. Hagamos que camine hacia los lados, izquierda y derecha, cuando se presionen las teclas de dirección. Recuerda que los valores utilizados (al menos para la velocidad) son píxeles/segundo.

Esto añade un sencillo soporte para caminar presionando izquierda y derecha:

extends KinematicBody2D

const GRAVITY = 200.0
const WALK_SPEED = 200

var velocity = Vector2()

func _physics_process(delta):
    velocity.y += delta * GRAVITY

    if Input.is_action_pressed("ui_left"):
        velocity.x = -WALK_SPEED
    elif Input.is_action_pressed("ui_right"):
        velocity.x =  WALK_SPEED
    else:
        velocity.x = 0

    # We don't need to multiply velocity by delta because "move_and_slide" already takes delta time into account.

    # The second parameter of "move_and_slide" is the normal pointing up.
    # In the case of a 2D platformer, in Godot, upward is negative y, which translates to -1 as a normal.
    move_and_slide(velocity, Vector2(0, -1))
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    const float gravity = 200.0f;
    const int walkSpeed = 200;

    Vector2 velocity;

    public override void _PhysicsProcess(float delta)
    {
        velocity.y += delta * gravity;

        if (Input.IsActionPressed("ui_left"))
        {
            velocity.x = -walkSpeed;
        }
        else if (Input.IsActionPressed("ui_right"))
        {
            velocity.x = walkSpeed;
        }
        else
        {
            velocity.x = 0;
        }

        // We don't need to multiply velocity by delta because "MoveAndSlide" already takes delta time into account.

        // The second parameter of "MoveAndSlide" is the normal pointing up.
        // In the case of a 2D platformer, in Godot, upward is negative y, which translates to -1 as a normal.
        MoveAndSlide(velocity, new Vector2(0, -1));
    }
}

E inténtalo.

Este es un buen punto de partida para un juego de plataformas. Se puede encontrar una demostración más completa en el zip de demostración distribuido con el motor, o en https://github.com/godotengine/godot-demo-projects/tree/master/2d/kinematic_character.