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 que los objetos se muevan y colisionen, después resuelven sus colisiones todas juntas. Esto hace que los controladores de personaje dinámico sean capaces de interactuar con otros objetos físicos sin inconvenientes, como se ve en la demo del juego de plataformas. Sin embargo estas interacciones no son siempre predecibles. Las colisiones pueden tardar más de un frame en ser resueltas, así que unas pocas colisiones pueden verse desplazadas un poquito. Esos problemas pueden solucionarse, pero requieren de cierta habilidad.

  • Un controlador cinemático de personaje asume siempre en un estado de no colisión, y siempre se moverá a un estado de no colisión. Si comienza en un estado de colisión, intentará liberarse como lo hacen los cuerpos rígidos, pero esta es la excepción, no la regla. Esto hace que su control y movimiento sean mucho más predecibles y fáciles de programar. Sin embargo, como desventaja, no pueden interactuar directamente con otros objetos de la física, a menos que se haga a mano en código.

Este breve tutorial se centrará en el controlador cinemático de personajes. Básicamente, la manera de la vieja escuela de manejar las colisiones (que no es necesariamente más simple por debajo, pero bien escondida y presentada como un API agradable y simple).

Proceso físico

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

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

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)

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))

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.