Utilisation de KinematicBody2D

Introduction

Godot offre un certain nombre d'objets de collision pour assurer à la fois la détection et la réponse aux collisions. Essayer de décider lequel utiliser pour votre projet peut prêter à confusion. Vous pouvez éviter les problèmes et simplifier le développement si vous comprenez comment chacun d'eux fonctionne et quels sont leurs avantages et leurs inconvénients. Dans ce tutoriel, nous allons regarder le nœud KinematicBody2D et montrer quelques exemples de son utilisation.

Note

Ce document suppose que vous connaissez les différents corps physiques de Godot. Veuillez d'abord lire Introduction à la physique.

Qu'est-ce qu'un kinematic body ?

KinematicBody2D est destiné à l'implémentation de corps qui doivent être contrôlés par code. Ils détectent les collisions avec d'autres corps en mouvement, mais ne sont pas affectés par les propriétés physiques du moteur, comme la gravité ou la friction. Bien que cela signifie que vous devez écrire du code pour créer leur comportement, cela signifie également que vous avez un contrôle plus précis sur la façon dont ils bougent et réagissent.

Astuce

Un KinematicBody2D peut être affecté par la gravité et d'autres forces, mais vous devez calculer le mouvement en code. Le moteur physique ne déplace pas un KinematicBody2D.

Mouvement et collision

Lorsque vous déplacez un KinematicBody2D, vous ne devez pas définir directement sa propriété position. Au lieu de cela, vous utilisez les méthodes move_and_collide() ou move_and_slide(). Ces méthodes déplacent le corps le long d'un vecteur donné et s'arrêtent instantanément si une collision est détectée avec un autre corps. Après la collision d'un KinematicBody2D, toute réponse à la collision doit être codée manuellement.

Avertissement

Le mouvement du KinematicBody2D ne doit se faire que dans la méthode de rappel _physics_process().

Les deux méthodes de mouvement ont des objectifs différents, et plus loin dans ce tutoriel, vous verrez des exemples de leur fonctionnement.

move_and_collide

Cette méthode prend un paramètre : a Vector2 indiquant le mouvement relatif du corps. Typiquement, c'est votre vecteur de vitesse multiplié par le pas de temps de l'image (delta). Si le moteur détecte une collision le long de ce vecteur, le corps s'arrête immédiatement. Si cela se produit, la méthode retournera un objet KinematicCollision2D.

KinematicCollision2D est un objet contenant des données sur la collision et l'objet entrant en collision. À l'aide de ces données, vous pouvez calculer votre réaction en cas de collision.

move_and_slide

La méthode move_and_slide() est destinée à simplifier la réponse de collision dans le cas courant où vous voulez qu'un corps glisse le long de l'autre. Ceci est particulièrement utile dans les jeux de plates-formes ou les jeux top-down, par exemple.

Astuce

move_and_slide() calcule automatiquement le mouvement basé sur les frames en utilisant delta. Ne multipliez pas votre vecteur de vitesse par delta avant de le passer à move_and_slide().

En plus du vecteur de vitesse, move_and_slide() prend un certain nombre d'autres paramètres vous permettant de personnaliser le comportement du glissement :

  • up_direction - valeur par défaut : Vecteur2( 0, 0 )

    Ce paramètre vous permet de définir quelles surfaces le moteur doit considérer comme étant le sol. Cette option vous permet d'utiliser les méthodes is_on_floor(), is_on_wall() et is_on_ceiling() pour détecter avec quel type de surface le corps est en contact. La valeur par défaut signifie que toutes les surfaces sont considérées comme des murs.

  • stop_on_slope - valeur par défaut : false

    Ce paramètre empêche un corps de glisser sur les pentes lorsqu'il est à l'arrêt.

  • max_slides - valeur par défaut : 4

    Ce paramètre est le nombre maximum de collisions avant que le corps s'arrête de bouger. Le mettre à une valeur trop basse peut complètement empêcher le mouvement.

  • floor_max_angle - valeur par défaut: 0.785398 (en radians, équivalent à 45 degrés)

    Ce paramètre est l'angle maximum avant qu'une surface ne soit plus considérée comme un "sol".

  • infinite_inertia - valeur par défaut : true

Lorsque ce paramètre est true, le corps peut pousser RigidBody2D des noeuds, en ignorant leur masse, mais ne détectera pas les collisions avec eux. S'il est false, le corps entre en collision avec les corps rigides et s'arrête.

move_and_slide_with_snap

Cette méthode ajoute une fonctionnalité en plus à move_and_slide() en ajoutant le paramètre snap. Tant que ce vecteur est en contact avec le sol, le corps restera attaché à la surface. Notez que cela veut dire qu'il faut désactiver cette fonctionnalité pendant un saut, par exemple. Vos pouvez faire cela soit en ajustant la valeur de snap à Vector2.ZERO ou en utilisant move_and_slide() à la place.

Détection des collisions

Lorsque vous utilisez move_and_collide(), la fonction retourne directement un KinematicCollision2D, et vous pouvez l'utiliser dans votre code.

En utilisant move_and_slide(), il est possible que plusieurs collisions se produisent, car la réponse de la glissière est calculée. Pour traiter ces collisions, utilisez get_slide_count() et get_slide_collision() :

# Using move_and_collide.
var collision = move_and_collide(velocity * delta)
if collision:
    print("I collided with ", collision.collider.name)

# Using move_and_slide.
velocity = move_and_slide(velocity)
for i in get_slide_count():
    var collision = get_slide_collision(i)
    print("I collided with ", collision.collider.name)

Voir KinematicCollision2D pour plus de détails sur les données de collision renvoyées.

Quelle méthode de mouvement utiliser ?

Une question courante chez les nouveaux utilisateurs de Godot est : "Comment décider quelle fonction de mouvement utiliser ?" Souvent, la réponse est d'utiliser move_and_slide() parce qu'elle est plus "simple", mais ce n'est pas nécessairement le cas. Une façon d'y réfléchir est de se dire que move_and_slide() est un cas spécial, et move_and_collide() est plus général. Par exemple, les deux extraits de code ci-dessous conduisent à la même réaction à la collision :

../../_images/k2d_compare.gif
# using move_and_collide
var collision = move_and_collide(velocity * delta)
if collision:
    velocity = velocity.slide(collision.normal)

# using move_and_slide
velocity = move_and_slide(velocity)
// using MoveAndCollide
var collision = MoveAndCollide(velocity * delta);
if (collision != null)
{
    velocity = velocity.Slide(collision.Normal);
}
// using MoveAndSlide
velocity = MoveAndSlide(velocity);

Tout ce qui peut être fait avec move_and_slide() peut aussi être fait avec move_and_collide(), mais cela peut demander un peu plus de code. Néammoins, comme nous allons le voir dans les exemples ci-dessous, il y a des cas où move_and_slide() ne donne pas le résultat voulu.

Dans l'exemple ci-dessus, nous attribuons la vitesse que move_and_slide() renvoie dans la variable velocity. En effet, lorsque le personnage entre en collision avec l'environnement, la fonction recalcule la vitesse en interne pour refléter le ralentissement.

Par exemple, si votre personnage est tombé au sol, vous ne voulez pas qu'il accumule de la vitesse verticale à cause de l'effet de la gravité. Au lieu de cela, vous souhaitez que sa vitesse verticale soit remise à zéro.

move_and_slide() `` peut aussi recalculer la vitesse du corps cinématique plusieurs fois dans une boucle car, pour produire un mouvement fluide, il déplace le personnage et entre en collision jusqu'à cinq fois par défaut. À la fin du processus, la fonction renvoie la nouvelle vitesse du personnage que nous pouvons stocker dans notre variable ``velocity et utiliser lors de la trame suivante.

Exemples

Pour voir des exemples en action, téléchargez le projet de test : using_kinematic2d.zip.

Mouvement et murs

Si vous avez téléchargé le projet de test, cet exemple est dans "BasicMovement.tscn".

Pour cet exemple, ajoutez un KinematicBody2D avec deux enfants : une Sprite et une CollisionShape2D. Utilisez l'icône de Godot "icon.png" comme texture pour la Sprite (faites glisser depuis le Système de fichiers vers la propriété Texture de la Sprite). Dans la propriété Shape de la CollisionShape2D, sélectionnez "Nouveau RectangleShape2D" and redimensionnez le rectangle pour remplir l'image de la Sprite.

Note

Voir Vue d'ensemble du mouvement 2D pour des exemples d'implémentation de systèmes de mouvement 2D.

Attachez un script au KinematicBody2D et ajoutez le code suivant :

extends KinematicBody2D

var speed = 250
var velocity = Vector2()

func get_input():
    # Detect up/down/left/right keystate and only move when pressed.
    velocity = Vector2()
    if Input.is_action_pressed('ui_right'):
        velocity.x += 1
    if Input.is_action_pressed('ui_left'):
        velocity.x -= 1
    if Input.is_action_pressed('ui_down'):
        velocity.y += 1
    if Input.is_action_pressed('ui_up'):
        velocity.y -= 1
    velocity = velocity.normalized() * speed

func _physics_process(delta):
    get_input()
    move_and_collide(velocity * delta)
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    public int Speed = 250;
    private Vector2 _velocity = new Vector2();

    public void GetInput()
    {
        // Detect up/down/left/right keystate and only move when pressed
        _velocity = new Vector2();

        if (Input.IsActionPressed("ui_right"))
            _velocity.x += 1;

        if (Input.IsActionPressed("ui_left"))
            _velocity.x -= 1;

        if (Input.IsActionPressed("ui_down"))
            _velocity.y += 1;

        if (Input.IsActionPressed("ui_up"))
            _velocity.y -= 1;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        MoveAndCollide(velocity * delta);
    }
}

Jouez la scène et vous verrez que move_and_collide() marche comme prévu et bouge le corps le long du vecteur de vitesse. Maintenant voyons ce qui se passe quand on ajoute des obstacles. Ajoutez un StaticBody2D avec une forme de collision rectangulaire. Pour la rendre visible, vous pouvez utiliser une Sprite, un Polygon2D, ou bien activer l'option "Formes de collision visibles" du menu "Débogage".

Jouez la scène de nouveau et essayez de bouger vers les obstacles. Vous verrez que le KinematicBody2D ne peut pas les traverser. Néammoins, essayez de bouger vers les obstacles avec un angle et vous verrez qu'ils agissent comme de la colle ; il semble que le corps se coince.

Cela arrive parce qu'il n'y a pas de réaction à la collision. move_and_collide() arrête le mouvement du corps quand une collision se produit. On doit coder n'importe quelle réaction que l'on veut pour cette collision.

Essayez de changer la fonction en move_and_slide(velocity) et de rejouer la scène. Notez qu'on a enlevé le delta du calcul de la vitesse.

move_and_slide() fournit une réaction à la collision par défaut en faisant glisser le corps le long de l'objet de la collision. C'est utile pour beaucoup de types de jeux, et il est possible que ce soit le seul comportement dont vous ayez besoin.

Rebondir/se réfléchir

Et si vous ne vouliez pas de réponse de collision glissante ? Pour cet exemple ("BounceandCollide.tscn" dans le projet de test), nous avons un personnage qui tire des balles et nous voulons que ces balles rebondissent sur les murs.

Cet exemple utilise trois scènes, La scène principale contient le joueur (Player) et les murs (Walls). La balle (Bullet) et le mur (Wall) sont des scènes séparées pour pouvoir être instanciés.

Le joueur est contrôlé avec les touches w et s pour avancer et reculer. La visée se fait avec le pointeur de la souris. Voici le code du joueur, qui utilise move_and_slide() :

extends KinematicBody2D

var Bullet = preload("res://Bullet.tscn")
var speed = 200
var velocity = Vector2()

func get_input():
    # Add these actions in Project Settings -> Input Map.
    velocity = Vector2()
    if Input.is_action_pressed('backward'):
        velocity = Vector2(-speed/3, 0).rotated(rotation)
    if Input.is_action_pressed('forward'):
        velocity = Vector2(speed, 0).rotated(rotation)
    if Input.is_action_just_pressed('mouse_click'):
        shoot()

func shoot():
    # "Muzzle" is a Position2D placed at the barrel of the gun.
    var b = Bullet.instance()
    b.start($Muzzle.global_position, rotation)
    get_parent().add_child(b)

func _physics_process(delta):
    get_input()
    var dir = get_global_mouse_position() - global_position
    # Don't move if too close to the mouse pointer.
    if dir.length() > 5:
        rotation = dir.angle()
        velocity = move_and_slide(velocity)
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    private PackedScene _bullet = (PackedScene)GD.Load("res://Bullet.tscn");
    public int Speed = 200;
    private Vector2 _velocity = new Vector2();

    public void GetInput()
    {
        // add these actions in Project Settings -> Input Map
        _velocity = new Vector2();
        if (Input.IsActionPressed("backward"))
        {
            _velocity = new Vector2(-Speed/3, 0).Rotated(Rotation);
        }
        if (Input.IsActionPressed("forward"))
        {
            _velocity = new Vector2(Speed, 0).Rotated(Rotation);
        }
        if (Input.IsActionPressed("mouse_click"))
        {
            Shoot();
        }
    }

    public void Shoot()
    {
        // "Muzzle" is a Position2D placed at the barrel of the gun
        var b = (Bullet)_bullet.Instance();
        b.Start(GetNode<Node2D>("Muzzle").GlobalPosition, Rotation);
        GetParent().AddChild(b);
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        var dir = GetGlobalMousePosition() - GlobalPosition;
        // Don't move if too close to the mouse pointer
        if (dir.Length() > 5)
        {
            Rotation = dir.Angle();
            _velocity = MoveAndSlide(_velocity);
        }
    }
}

Et le code de la balle :

extends KinematicBody2D

var speed = 750
var velocity = Vector2()

func start(pos, dir):
    rotation = dir
    position = pos
    velocity = Vector2(speed, 0).rotated(rotation)

func _physics_process(delta):
    var collision = move_and_collide(velocity * delta)
    if collision:
        velocity = velocity.bounce(collision.normal)
        if collision.collider.has_method("hit"):
            collision.collider.hit()

func _on_VisibilityNotifier2D_screen_exited():
    queue_free()
using Godot;
using System;

public class Bullet : KinematicBody2D
{
    public int Speed = 750;
    private Vector2 _velocity = new Vector2();

    public void Start(Vector2 pos, float dir)
    {
        Rotation = dir;
        Position = pos;
        _velocity = new Vector2(speed, 0).Rotated(Rotation);
    }

    public override void _PhysicsProcess(float delta)
    {
        var collision = MoveAndCollide(_velocity * delta);
        if (collision != null)
        {
            _velocity = _velocity.Bounce(collision.Normal);
            if (collision.Collider.HasMethod("Hit"))
            {
                collision.Collider.Call("Hit");
            }
        }
    }

    public void OnVisibilityNotifier2DScreenExited()
    {
        QueueFree();
    }
}

L'action se passe dans _physics_process(). Après avoir utilisé move_and_collide(), si une collision s'est produite, un objet KinematicCollision2D est retourné (sinon, la valeur de retour est Nil).

Si une collision est retournée, on utilise la normale de la collision pour faire se réfléchir la vitesse des balles velocity avec la méthode Vector2.bounce().

Si l'objet de la collision (collider) a une méthode hit, on l'appelle aussi. Dans le projet d'exemple, on a ajouté un effet de couleur clignotante au mur pour montrer cela.

../../_images/k2d_bullet_bounce.gif

Mouvement de jeu de plateforme

Essayons un autre exemple populaire : le jeu de plateforme 2D. move_and_slide() est idéal pour obtenir rapidement un contrôleur de personnage fonctionnel. Si vous avez téléchargé le projet de test, vous pouvez trouver cela dans "Platformer.tscn".

Pour cet exemple, nous allons supposer que vous avez déjà créé des objets StaticBody2D. Ils peuvent être de n'importe quelle forme et de n'importe quelle taille. Dans ce projet d'exemple, nous allons utiliser Polygon2D pour créer des formes de plateformes.

Voici le code pour le corps du joueur :

extends KinematicBody2D

export (int) var run_speed = 100
export (int) var jump_speed = -400
export (int) var gravity = 1200

var velocity = Vector2()
var jumping = false

func get_input():
    velocity.x = 0
    var right = Input.is_action_pressed('ui_right')
    var left = Input.is_action_pressed('ui_left')
    var jump = Input.is_action_just_pressed('ui_select')

    if jump and is_on_floor():
        jumping = true
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    get_input()
    velocity.y += gravity * delta
    if jumping and is_on_floor():
        jumping = false
    velocity = move_and_slide(velocity, Vector2(0, -1))
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    [Export] public int RunSpeed = 100;
    [Export] public int JumpSpeed = -400;
    [Export] public int Gravity = 1200;

    Vector2 velocity = new Vector2();
    bool jumping = false;

    public void GetInput()
    {
        velocity.x = 0;
        bool right = Input.IsActionPressed("ui_right");
        bool left = Input.IsActionPressed("ui_left");
        bool jump = Input.IsActionPressed("ui_select");

        if (jump && IsOnFloor())
        {
            jumping = true;
            velocity.y = JumpSpeed;
        }

        if (right)
            velocity.x += RunSpeed;
        if (left)
            velocity.x -= RunSpeed;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        velocity.y += Gravity * delta;
        if (jumping && IsOnFloor())
            jumping = false;
        velocity = MoveAndSlide(velocity, new Vector2(0, -1));
    }
}
../../_images/k2d_platform.gif

Quand on utilise move_and_slide(), la fonction renvoie un vecteur qui représente le mouvement restant après que la collision glissante s'est produite. Assigner cette valeur à la vitesse du personnage velocity permet de le déplacer en douceur en montant et en descendant des pentes. Essayez d'enlever velocity = pour voir ce qu'il se passe si vous ne le faites pas.

Notez aussi que nous avons ajouté Vector2(0, -1) comme normale du sol. C'est le vecteur qui pointe directement vers le haut. Cela veut dire que si le personnage entre en collision avec un objet qui a cette normale, celui-ci sera considéré comme le sol.

Utiliser la normale du sol nous permet de faire marcher le saut, en utilisant is_on_floor(). Cette fonction ne renvoie true qu'après une collision de move_and_slide() où la normale du corps qui a collisionné est dans les 45 degrés du vecteur de sol donné. Vous pouvez contrôler l'angle maximum en ajustant le paramètre floor_max_angle.

Cela permet aussi d'implémenter d'autres fonctionnalités comme les sauts de murs en utilisant is_on_wall(), par exemple.