Introduction à la physique

Dans le développement d'un jeu, vous avez souvent besoin de savoir quand deux objets du jeu se croisent ou entrent en contact. C'est ce qu'on appelle la détection des collisions. Lorsqu'une collision est détectée, vous voulez généralement que quelque chose se produise. C'est ce qu'on appelle la réponse à la collision.

Godot offre un certain nombre d'objets de collision en 2D et 3D pour fournir à 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 fonctionne et quels sont ses avantages et ses inconvénients.

Dans ce guide, vous apprendrez :

  • Les quatre types d'objets de collision de Godot
  • Comment chaque objet de collision fonctionne
  • Quand et pourquoi choisir un type plutôt qu'un autre

Note

Les exemples de ce document utilisent des objets 2D. Chaque objet physique 2D et chaque forme de collision a un équivalent direct en 3D et dans la plupart des cas, ils fonctionnent de la même manière.

Objets de collisions

Godot offre 4 types de corps physiques, qui sont des extensions de CollisionObject2D :

  • Area2D
    Les nœuds Area2D fournissent la détection et l'influence. Ils peuvent détecter quand les objets se chevauchent et peuvent émettre des signaux quand des corps entrent ou sortent. Un Area2D peut aussi être utilisé pour outrepasser les propriétés physiques, telles que la gravité ou l'amortissement, dans une zone définie.

Les 3 autres corps sont des extensions de PhysicsBody2D :

  • StaticBody2D
    Un corps statique est un corps qui n'est pas déplacé par le moteur physique. Il participe à la détection des collisions, mais ne bouge pas en réponse à la collision. Ils sont le plus souvent utilisés pour des objets qui font partie de l'environnement ou qui n'ont pas besoin d'avoir un comportement dynamique.
  • RigidBody2D (Corps rigide 2D)
    C'est le nœud qui implémente la physique 2D simulée. Vous ne contrôlez pas directement un RigidBody2D, mais vous lui appliquez des forces (gravité, impulsions, etc.) et le moteur physique calcule le mouvement résultant. En savoir plus sur l'utilisation des RigidBody.
  • KinematicBody2D (Corps cinématique 2D)
    Un corps qui fournit la détection de collision, mais n'a pas de physique. Toute réponse au mouvement ou à la collision doit être implémentée dans le code.

Formes de collisions

Un corps physique peut contenir n'importe quel nombre d'objets Shape2D comme enfants. Ces formes sont utilisées pour définir les limites de collision de l'objet et pour détecter le contact avec d'autres objets.

Note

Afin de détecter les collisions, au moins un Shape2D doit être assigné à l'objet.

La façon la plus courante d'assigner une forme est d'ajouter un CollisionShape2D ou CollisionPolygon2D comme un enfant de l'objet. Ces nœuds vous permettent de dessiner la forme directement dans l'espace de travail de l'éditeur.

Important

Faites attention à ne jamais mettre à l'échelle vos formes de collision dans l'éditeur. La propriété "Scale" de l'Inspecteur doit rester "(1, 1)". Lorsque vous changez la taille de la forme de la collision, vous devez toujours utiliser les poignées de taille, pas les poignées graduées Node2D. La mise à l'échelle d'une forme peut entraîner un comportement de collision inattendu.

../../_images/player_coll_shape1.png

Rappel des processus physiques

Le moteur physique peut générer plusieurs threads pour améliorer les performances, de sorte qu'il peut utiliser jusqu'à une image complète pour traiter la physique. Pour cette raison, la valeur des variables d'état d'un corps telles que position ou linear velocity peut ne pas être exacte pour la trame courante.

Afin d'éviter cette inexactitude, tout code qui doit accéder aux propriétés d'un corps doit être exécuté dans le rappel Node._physics_process(), qui est appelé avant chaque étape physique à une fréquence d'images constante (60 fois par seconde par défaut). Cette méthode recevra un paramètre delta, qui est un nombre à virgule flottante égal au temps passé en secondes depuis la dernière étape. Lorsque vous utilisez le taux de mise à jour physique par défaut de 60 Hz, il sera généralement égal à 0.01666... (mais pas toujours, voir ci-dessous).

Note

Il est recommandé de toujours utiliser le paramètre delta lorsque cela est pertinent dans vos calculs physiques, afin que le jeu se comporte correctement si vous modifiez le taux de mise à jour physique ou si l'appareil du joueur ne peut pas suivre.

Niveaux et masques de collisions

L'une des fonctionnalités de collision les plus puissantes, mais souvent mal comprise, est le système de couche de collision. Ce système vous permet de construire des interactions complexes entre une variété d'objets. Les concepts clés sont les couches et les masques. Chaque CollisionObject2D a 20 couches physiques différentes avec lesquelles il peut interagir.

Examinons chacune des propriétés à tour de rôle :

  • collision_layer
    Elle décrit les calques dans lesquels l'objet apparaît in. Par défaut, tous les corps sont sur la couche 1.
  • collision_mask
    Cette section décrit les couches que le corps va scanner pour détecter les collisions. Si un objet n'est pas dans l'une des couches du masque, le corps l'ignore. Par défaut, tous les corps scannent la couche 1.

Ces propriétés peuvent être configurées via le code ou en les modifiant dans l'inspecteur.

Il peut être difficile de garder une trace de ce que vous utilisez pour chaque calque, il peut donc être utile d'attribuer des noms aux calques que vous utilisez. Les noms peuvent être assignés dans Project Settings -> Layer Names.

../../_images/physics_layer_names.png

Exemple :

Vous avez quatre types de nœuds dans votre jeu : Murs, joueur, ennemi et pièce de monnaie. Le joueur et l'ennemi doivent entrer en collision avec les murs. Le nœud Player doit détecter les collisions avec l'ennemi et la pièce, mais l'ennemi et la pièce doivent s'ignorer mutuellement.

Commencez par nommer les couches 1-4 "murs", "joueur", "ennemis" et "pièces" et placez chaque type de nœud dans sa couche respective en utilisant la propriété "Layer". Définissez ensuite la propriété "Mask" de chaque nœud en sélectionnant les calques avec lesquels il doit interagir. Par exemple, les paramètres du joueur ressemblent à ceci :

../../_images/player_collision_layers.png ../../_images/player_collision_mask.png

Area2D

Les nœuds de zone assurent la détection et l'influence. Ils peuvent détecter quand les objets se chevauchent et émettre des signaux quand les corps entrent ou sortent. Les zones peuvent également être utilisées pour outrepasser les propriétés physiques, telles que la gravité ou l'amortissement, dans une zone définie.

Il y a trois utilisations principales pour Area2D :

  • Remplacer les paramètres physiques primordiaux (comme la gravité) dans une région donnée.
  • Détecter quand d'autres corps entrent ou sortent d'une région ou quels corps se trouvent actuellement dans une région.
  • Vérification des chevauchements avec d'autres zones.

Par défaut, les zones reçoivent également les entrées de la souris et de l'écran tactile.

StaticBody2D

Un corps statique est un corps qui n'est pas déplacé par le moteur physique. Il participe à la détection des collisions, mais ne bouge pas en réponse à la collision. Cependant, il peut donner du mouvement ou de la rotation à un corps en collision comme si il se déplaçait, en utilisant ses propriétés constant_linear_velocity et constant_angular_velocity.

Les nœuds StaticBody2D sont le plus souvent utilisés pour des objets qui font partie de l'environnement ou qui n'ont pas besoin d'avoir un comportement dynamique.

Exemples d'utilisation de StaticBody2D :

  • Plates-formes (y compris les plates-formes mobiles)
  • Convoyeur à courroies
  • Murs et autres obstacles

RigidBody2D

C'est le nœud qui implémente la physique 2D simulée. Vous ne contrôlez pas un RigidBody2D directement. Au lieu de cela, vous lui appliquez des forces et le moteur physique calcule le mouvement résultant, y compris les collisions avec d'autres corps, et les réactions de collision, telles que le rebondissement, la rotation, etc.

Vous pouvez modifier le comportement d'un corps rigide via des propriétés telles que "Mass", "Friction" ou "Bounce", qui peuvent être définies dans l'inspecteur.

Le comportement du corps est également affecté par les propriétés du monde, telles que définies dans Project Settings -> Physics, ou en entrant un Area2D qui surcharge les propriétés physiques globales.

Quand un corps rigide est au repos et n'a pas bougé depuis un moment, il s'endort. Un corps endormi agit comme un corps statique, et ses forces ne sont pas calculées par le moteur physique. Le corps se réveille lorsque des forces sont appliquées, soit par une collision, soit par un code.

Modes corps rigides

Un corps rigide peut être réglé sur l'un des quatre modes :

  • Rigid - Le corps se comporte comme un objet physique. Il entre en collision avec d'autres corps et réagit aux forces qui lui sont appliquées. C'est le mode par défaut.
  • Static - Le corps se comporte comme un StaticBody2D et ne bouge pas.
  • Character - Similaire au mode "Rigid", mais le corps ne peut pas tourner.
  • Kinematic - Le corps se comporte comme un KinematicBody2D et doit être déplacé par code.

Utiliser RigidBody2D

Un des avantages de l'utilisation d'un corps rigide est qu'il est possible d'avoir beaucoup de comportement "gratuitement" sans écrire de code. Par exemple, si vous créez un jeu de type "Angry Birds" avec des blocs tombants, vous n'aurez qu'à créer RigidBody2Ds et ajuster leurs propriétés. L'empilement, la chute et le rebondissement seraient automatiquement calculés par le moteur physique.

Cependant, si vous souhaitez avoir un certain contrôle sur le corps, vous devriez faire attention - modifier la position, la linear_velocity ou d'autres propriétés physiques d'un corps rigide peut entraîner un comportement inattendu. Si vous avez besoin de modifier l'une des propriétés liées à la physique, vous devez utiliser le _integrate_forces() callback au lieu de _physics_process(). Dans ce rappel, vous avez accès au Physics2DDirectBodyState, qui permet de modifier en toute sécurité les propriétés et les synchroniser avec le moteur physique.

Par exemple, voici le code d'un vaisseau spatial de type "Astéroïdes" :

extends RigidBody2D

var thrust = Vector2(0, 250)
var torque = 20000

func _integrate_forces(state):
    if Input.is_action_pressed("ui_up"):
        applied_force = thrust.rotated(rotation)
    else:
        applied_force = Vector2()
    var rotation_dir = 0
    if Input.is_action_pressed("ui_right"):
        rotation_dir += 1
    if Input.is_action_pressed("ui_left"):
        rotation_dir -= 1
    applied_torque = rotation_dir * torque
class Spaceship : RigidBody2D
{
    private Vector2 _thrust = new Vector2(0, 250);
    private float _torque = 20000;

    public override void _IntegrateForces(Physics2DDirectBodyState state)
    {
        if (Input.IsActionPressed("ui_up"))
            SetAppliedForce(_thrust.Rotated(Rotation));
        else
            SetAppliedForce(new Vector2());

        var rotationDir = 0;
        if (Input.IsActionPressed("ui_right"))
            rotationDir += 1;
        if (Input.IsActionPressed("ui_left"))
            rotationDir -= 1;
        SetAppliedTorque(rotationDir * _torque);
    }
}

Notez que nous ne réglons pas directement les propriétés linear_velocity ou angular_velocity, mais appliquons plutôt des forces (thrust et torque) au corps et laissons le moteur physique calculer le mouvement résultant.

Note

Quand un corps rigide s'endort, la fonction _integrate_forces() ne sera pas appelée. Pour outrepasser ce comportement, vous devrez garder le corps éveillé en créant une collision, en lui appliquant une force ou en désactivant la propriété can_sleep. Sachez que cela peut avoir un effet négatif sur la performance.

Rapport de contact

Par défaut, les corps rigides ne gardent pas la trace des contacts, car cela peut nécessiter une énorme quantité de mémoire si de nombreux corps sont dans la scène. Pour activer la déclaration des contacts, définissez la propriété contacts_reported sur une valeur non nulle. Les contacts peuvent alors être obtenus via Physics2DDirectBodyState.get_contact_count() et fonctions associées.

La surveillance des contacts par signaux peut être activée via la propriété contact_monitor. Voir RigidBody2D pour la liste des signaux disponibles.

KinematicBody2D

KinematicBody2D corps détectent les collisions avec d'autres corps, mais ne sont pas affectés par des propriétés physiques comme la gravité ou la friction. Ils doivent plutôt être contrôlés par l'utilisateur à l'aide d'un code. Le moteur physique ne déplacera pas un corps cinématique.

Lorsque vous déplacez un corps cinématique, vous ne devez pas régler sa position directement. 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 il s'arrêtera instantanément si une collision est détectée avec un autre corps. Après la collision du corps, toute réaction à une collision doit être codée manuellement.

Réponse cinématique à la collision

Après une collision, vous pouvez vouloir que le corps rebondisse, glisse le long d'un mur ou modifie les propriétés de l'objet touché. La façon dont vous réagissez à la collision dépend de la méthode que vous avez utilisée pour déplacer le KinematicBody2D.

move_and_collide

Lorsque vous utilisez move_and_collide(), la fonction retourne un objet KinematicCollision2D, qui contient des informations sur la collision et le corps en collision. Vous pouvez utiliser ces informations pour déterminer la réponse.

Par exemple, si vous voulez trouver le point dans l'espace où la collision s'est produite :

extends KinematicBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        var collision_point = collision_info.position
class Body : KinematicBody2D
{
    private Vector2 _velocity = new Vector2(250, 250);

    public override void _PhysicsProcess(float delta)
    {
        var collisionInfo = MoveAndCollide(_velocity * delta);
        if (collisionInfo != null)
        {
            var collisionPoint = collisionInfo.GetPosition();
        }
    }
}

Ou pour rebondir sur l'objet en collision :

extends KinematicBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        velocity = velocity.bounce(collision_info.normal)
class Body : KinematicBody2D
{
    private Vector2 _velocity = new Vector2(250, 250);

    public override void _PhysicsProcess(float delta)
    {
        var collisionInfo = MoveAndCollide(_velocity * delta);
        if (collisionInfo != null)
            _velocity = _velocity.Bounce(collisionInfo.Normal);
    }
}

move_and_slide

Le glissement est une réaction de collision courante ; imaginez un joueur se déplaçant le long des murs dans un jeu descendant ou courant le long des pentes dans un jeu de plates-formes. Bien qu'il soit possible de coder cette réponse vous-même après avoir utilisé move_and_collide(), move_and_slide() offre un moyen pratique d'implémenter un mouvement glissant sans écrire beaucoup de code.

Avertissement

move_and_slide() inclut automatiquement le pas de temps dans son calcul, il ne faut donc pas multiplier le vecteur vitesse par delta.

Par exemple, utilisez le code suivant pour créer un personnage capable de marcher sur le sol (y compris sur les pentes) et de sauter debout sur le sol :

extends KinematicBody2D

var run_speed = 350
var jump_speed = -1000
var gravity = 2500

var velocity = Vector2()

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 is_on_floor() and jump:
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    velocity.y += gravity * delta
    get_input()
    velocity = move_and_slide(velocity, Vector2(0, -1))
class Body : KinematicBody2D
{
    private float _runSpeed = 350;
    private float _jumpSpeed = -1000;
    private float _gravity = 2500;

    private Vector2 _velocity = new Vector2();

    private void GetInput()
    {
        _velocity.x = 0;

        var right = Input.IsActionPressed("ui_right");
        var left = Input.IsActionPressed("ui_left");
        var jump = Input.IsActionPressed("ui_select");

        if (IsOnFloor() && jump)
            _velocity.y = _jumpSpeed;
        if (right)
            _velocity.x += _runSpeed;
        if (left)
            _velocity.x -= _runSpeed;
    }

    public override void _PhysicsProcess(float delta)
    {
        _velocity.y += _gravity * delta;
        GetInput();
        _velocity = MoveAndSlide(velocity, new Vector2(0,-1));
    }
}

Voir Personnage cinématique (2D) pour plus de détails sur l'utilisation de move_and_slide(), qui inclut un projet de démonstration avec le code détaillé.