Personnage cinématique (2D)

Introduction

Oui, le nom semble étrange. "Personnage cinématique". Qu'est-ce que c'est que ça ? La raison pour ce nom est que, lorsque les moteurs physiques sont apparus, ils étaient appelés moteurs "dynamiques" (parce qu'ils s'occupaient principalement des réactions de collisions). Beaucoup de tentatives ont été faites pour créer un contrôleur de personnage qui utilise les moteurs dynamiques, mais ce n'était pas aussi facile que ça en avait l'air. Godot a l'une des meilleures implémentations de contrôleur de personnage dynamique que vous pouvez trouver (comme vous pouvez le voir dans le démo de jeu de plateforme 2d), mais l'utiliser demande un niveau de compétence considérable ainsi qu'une bonne connaissance des moteurs physiques (ou beaucoup de patience avec des essais et des erreurs).

Certains moteurs physiques, comme Havok, semblent considérer les contrôleurs de personnages dynamiques comme la meilleure option, alors que d'autres (PhysX) promeuvent plutôt les contrôleurs cinématiques.

Et donc, quelle est la différence ? :

  • Un contrôleur de personnage dynamique utilise un corps rigide avec un tenseur d'inertie infini. En fait, cela veut simplement dire que le corps rigide ne peut pas tourner. Les moteurs physiques laissent toujours les objets entrer en collision, puis résolvent leurs collisions tous ensemble. Cela fait que le contrôleur de personnage dynamique peut interagir avec d'autres objets physiques sans problème, comme on peut le voir dans la démo du jeu de plateforme. Cependant, ces interactions ne sont pas toujours prévisibles. Les collisions peuvent aussi prendre plus qu'une trame pour être résolues, donc quelques collisions peuvent sembler être un petit peu déplacées. Ces problèmes peuvent être résolus, mais demandent un certain niveau de compétence.
  • Un contrôleur de personnage cinématique est toujours considéré comme commençant dans un état sans collision, et va toujours aller vers un état sans collision. S'il commence dans un état de collision, il essaiera de se libérer tout seul comme les corps rigides le font, mais c'est une exception, pas la règle. Cela fait que leur contrôle et leur mouvement sont beaucoup plus prévisibles et plus faciles à programmer. Par contre, l'inconvénient est qu'ils ne peuvent pas interagir directement avec les autres objets physiques, à moins que cela ne soit fait à la main dans le code.

Ce court tutoriel se focalise sur le contrôleur de personnage cinématique. Pour faire simple, c'est la manière traditionnelle de gérer des collisions (ce qui n'est pas nécessairement plus simple sous le capot, mais qui est bien caché et présenté comme une API simple et agréable).

Processus physique

Pour gérer la logique d'un corps ou d'un personnage cinématique, il est toujours conseillé d'utiliser le processus physique, car il est appelé avant l'étape physique et son exécution est synchronisée avec le serveur physique, il est aussi appelé le même nombre de fois par seconde, toujours. Cela rend la physique et le calcul de mouvement plus prévisibles que l'utilisation du processus régulier, qui peut avoir des pics ou perdre en précision si la fréquence d'images est trop élevée ou trop faible.

extends KinematicBody2D

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

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

Configuration de la scène

Pour avoir quelque chose à tester, voici la scène (tirée du tutoriel tilemap) : kbscene.zip. Nous allons créer une nouvelle scène pour le personnage. Utilisez le sprite robot et créez une scène comme celle-ci :

../../_images/kbscene.png

Vous remarquerez qu'il y a une icône d'avertissement à côté de notre nœud CollisionShape2D ; c'est parce que nous n'avons pas défini de forme pour lui. Créez un nouveau CircleShape2D dans la propriété de forme de CollisionShape2D. Cliquez sur <CircleShape2D> pour accéder aux options correspondantes, et réglez le rayon sur 30 :

../../_images/kbradius.png

Note : Comme mentionné précédemment dans le tutoriel sur la physique, le moteur physique ne peut pas gérer l'échelle sur la plupart des types de formes (seuls les polygones de collision, les plans et les segments fonctionnent), donc changez toujours les paramètres (comme le rayon) de la forme au lieu de l'échelle. Il en va de même pour les corps cinématiques/rigides/statiques eux-mêmes, car leur échelle affecte l'échelle des formes.

Maintenant, créez un script pour le personnage, celui utilisé comme exemple ci-dessus devrait servir de base.

Enfin, instanciez cette scène personnage dans la tilemap, et faites de la tilemap la scène principale, de sorte qu'elle s'exécute lorsque vous appuyez sur play.

../../_images/kbinstance.png

Déplacer le personnage cinématique

Retournez à la scène du personnage, et ouvrez le script, la magie commence maintenant ! Le corps cinématique ne fera rien par défaut, mais il a une fonction utile appelée KinematicBody2D.move_and_collide(). Cette fonction prend un Vector2 comme argument, et essaie d'appliquer ce mouvement au corps cinématique. Si une collision se produit, elle s'arrête au moment même de la collision.

Déplaçons notre sprite vers le bas jusqu'à ce qu'il touche le sol :

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

Il en résulte que le personnage bouge, mais s'arrête pile lorsqu'il touche le sol. Plutôt cool, hein ?

La prochaine étape sera d'ajouter de la gravité au mélange, de cette façon il se comporte un peu plus comme un personnage de jeu ordinaire :

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

Maintenant, que le personnage tombe en douceur. Faisons-le marcher sur les côtés, à gauche et à droite en touchant les touches directionnelles. Rappelez-vous que les valeurs utilisées (pour la vitesse au moins) sont en pixels/seconde.

Ceci ajoute un simple support de marche en appuyant sur gauche et droite :

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

Et essayez-le.

C'est un bon point de départ pour un plateformer. Une démo plus complète se trouve dans le zip de démonstration distribué avec le moteur, ou sur le site https://github.com/godotengine/godot-demo-projects/tree/master/2d/kinematic_character.