Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Lancer de rayons
Introduction
Une des tâches les plus courantes dans le développement de jeu est de lancer un rayon (ou un objet de forme personnalisée) et de vérifier ce qu'il touche. Cela permet de mettre en place des comportements complexes, de l'IA, etc... Ce tutoriel explique comment faire cela en 2D et en 3D.
Godot stocke toutes les informations de jeu de bas niveau dans des serveurs, bien que la scène n'est qu'une interface. En tant que tel, le ray casting est généralement une tâche de bas niveau. Pour les raycasts simples, les nœuds tels que RayCast3D et RayCast2D fonctionneront, car ils retourneront à chaque trame le résultat du raycast.
Souvent, cependant, le ray casting doit être un processus plus interactif, donc il doit exister un moyen de le faire par le code.
Espace
Dans le monde physique, Godot stocke toutes les informations de collision et de physique de bas niveau dans un espace. L'espace 2d actuel (pour la physique 2D) peut être obtenu en accédant à CanvasItem.get_world_2d().space. Pour la 3D, c'est Node3D.get_world_3d().space.
L'espace résultant RID peut être utilisé dans PhysicsServer3D et PhysicsServer2D respectivement pour 3D et 2D.
Accéder à l'espace
La physique de Godot s'exécute par défaut dans le même thread que la logique du jeu, mais peut être définie pour fonctionner sur un thread séparé afin de fonctionner plus efficacement. De ce fait, le seul moment où l'accès à l'espace est sûr est pendant le callback Node._physics_process(). L'accès depuis l'extérieur de cette fonction peut entraîner une erreur due à un espace verrouillé.
Pour effectuer des requêtes dans l'espace physique, il faut utiliser PhysicsDirectSpaceState2D et PhysicsDirectSpaceState3D.
Utilisez le code suivant en 2D :
func _physics_process(delta):
var space_rid = get_world_2d().space
var space_state = PhysicsServer2D.space_get_direct_state(space_rid)
public override void _PhysicsProcess(double delta)
{
var spaceRid = GetWorld2D().Space;
var spaceState = PhysicsServer2D.SpaceGetDirectState(spaceRid);
}
Ou plus directement :
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld2D().DirectSpaceState;
}
Et en 3D :
func _physics_process(delta):
var space_state = get_world_3d().direct_space_state
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld3D().DirectSpaceState;
}
Requête Raycast
Pour effectuer une requête 2D raycast, la méthode PhysicsDirectSpaceState2D.intersect_ray() peut être utilisée. Par exemple :
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
# use global coordinates, not local to node
var query = PhysicsRayQueryParameters2D.create(Vector2(0, 0), Vector2(50, 100))
var result = space_state.intersect_ray(query)
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld2D().DirectSpaceState;
// use global coordinates, not local to node
var query = PhysicsRayQueryParameters2D.Create(Vector2.Zero, new Vector2(50, 100));
var result = spaceState.IntersectRay(query);
}
Le résultat est un dictionnaire. Si le rayon n'a rien touché, le dictionnaire sera vide. S'il a heurté quelque chose, il contiendra des informations sur la collision :
if result:
print("Hit at point: ", result.position)
if (result.Count > 0)
{
GD.Print("Hit at point: ", result["position"]);
}
Le dictionnaire result en cas de collision contient les données suivantes :
{
position: Vector2 # point in world space for collision
normal: Vector2 # normal in world space for collision
collider: Object # Object collided or null (if unassociated)
collider_id: ObjectID # Object it collided against
rid: RID # RID it collided against
shape: int # shape index of collider
metadata: Variant() # metadata of collider
}
The data is similar in 3D space, using Vector3 coordinates. Note that to enable collisions
with Area3D, the boolean parameter collide_with_areas must be set to true.
const RAY_LENGTH = 1000
func _physics_process(delta):
var space_state = get_world_3d().direct_space_state
var cam = $Camera3D
var mousepos = get_viewport().get_mouse_position()
var origin = cam.project_ray_origin(mousepos)
var end = origin + cam.project_ray_normal(mousepos) * RAY_LENGTH
var query = PhysicsRayQueryParameters3D.create(origin, end)
query.collide_with_areas = true
var result = space_state.intersect_ray(query)
private const int RayLength = 1000;
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld3D().DirectSpaceState;
var cam = GetNode<Camera3D>("Camera3D");
var mousePos = GetViewport().GetMousePosition();
var origin = cam.ProjectRayOrigin(mousePos);
var end = origin + cam.ProjectRayNormal(mousePos) * RayLength;
var query = PhysicsRayQueryParameters3D.Create(origin, end);
query.CollideWithAreas = true;
var result = spaceState.IntersectRay(query);
}
Exceptions de collision
Un cas d'utilisation courant pour le ray casting est de permettre à un personnage de recueillir des données sur le monde qui l'entoure. Un problème avec cela est que ce même personnage a un collisionneur, donc le rayon ne détectera que le collisionneur de son parent, comme le montre l'image suivante :
Pour éviter l'auto-intersection, la fonction intersect_ray() peut prendre tableau d'exceptions via sa propriété exclude. Voici un exemple d'utilisation à partir d'un nœud CharacterBody2D ou de tout autre nœud d'objet de collision :
extends CharacterBody2D
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
var query = PhysicsRayQueryParameters2D.create(global_position, player_position)
query.exclude = [self]
var result = space_state.intersect_ray(query)
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld2D().DirectSpaceState;
var query = PhysicsRayQueryParameters2D.Create(globalPosition, playerPosition);
query.Exclude = [GetRid()];
var result = spaceState.IntersectRay(query);
}
}
Le tableau des exceptions peut contenir des objets ou des RIDs.
Masque de collisions
Bien que la méthode des exceptions fonctionne bien pour exclure le corps parent, elle devient très peu pratique si vous avez besoin d'une liste large et/ou dynamique d'exceptions. Dans ce cas, il est beaucoup plus efficace d'utiliser le système de masques/couche de collision.
The intersect_ray() parameters object can also be supplied a collision mask.
For example, to use the same mask as the parent body, use the collision_mask
member variable. The array of exceptions can be supplied as the last argument as well:
extends CharacterBody2D
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
var query = PhysicsRayQueryParameters2D.create(global_position, target_position,
collision_mask, [self])
var result = space_state.intersect_ray(query)
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld2D().DirectSpaceState;
var query = PhysicsRayQueryParameters2D.Create(globalPosition, targetPosition,
CollisionMask, [GetRid()]);
var result = spaceState.IntersectRay(query);
}
}
Voir Exemple en code pour plus de détails sur la manière de régler le masque de collision.
Ray casting 3D à partir de l'écran
Le Ray casting depuis l'écran vers l'espace physique 3D est utile pour la sélection d'objets. Il n'est pas nécessaire de le faire car CollisionObject3D a un signal "input_event" qui vous indiquera quand il a été cliqué, mais si vous souhaitez le faire manuellement, voici comment.
Pour lancer un rayon depuis l'écran, vous avez besoin d'un nœud Camera3D. Une Camera3D peut être dans deux modes de projection : perspective et orthogonale. Pour cette raison, l'origine et la direction du rayon doivent être obtenues. Ceci est dû au fait que l'origin change en mode orthogonal, alors que la normal change en mode perspective :
Pour l'obtenir à l'aide d'une caméra, le code suivant peut être utilisé :
const RAY_LENGTH = 1000.0
func _input(event):
if event is InputEventMouseButton and event.pressed and event.button_index == 1:
var camera3d = $Camera3D
var from = camera3d.project_ray_origin(event.position)
var to = from + camera3d.project_ray_normal(event.position) * RAY_LENGTH
private const float RayLength = 1000.0f;
public override void _Input(InputEvent @event)
{
if (@event is InputEventMouseButton eventMouseButton && eventMouseButton.Pressed && eventMouseButton.ButtonIndex == MouseButton.Left)
{
var camera3D = GetNode<Camera3D>("Camera3D");
var from = camera3D.ProjectRayOrigin(eventMouseButton.Position);
var to = from + camera3D.ProjectRayNormal(eventMouseButton.Position) * RayLength;
}
}
Rappelez-vous que pendant _input(), l'espace peut être verrouillé, donc en pratique cette requête doit être exécutée dans _physics_process().