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...
Ray-casting
Introduzione
Uno dei compiti più comuni nello sviluppo di videogiochi è lanciare un raggio (o un oggetto con una forma personalizzata) e verificare cosa colpisce. Questo consente di implementare comportamenti complessi, intelligenza artificiale, ecc. Questo tutorial spiegherà come farlo in 2D e 3D.
Godot memorizza tutte le informazioni di basso livello nei server, mentre la scena è solo un'interfaccia. Pertanto, il raycasting è generalmente un'attività di basso livello. Per i raycast più semplici, nodi come RayCast3D e RayCast2D basteranno, poiché restituiscono a ogni frame il risultato di un raycast.
Spesso, però, il ray-casting deve essere un processo più interattivo, pertanto deve esserci un modo per farlo tramite codice.
Spazio
Nel mondo della fisica, Godot memorizza tutte le informazioni di basso livello sulle collisioni e sulla fisica in uno spazio. Lo spazio 2D attuale (per la fisica 2D) si può ottenere accedendo a CanvasItem.get_world_2d().space. Per il 3D, è Node3D.get_world_3d().space.
Il RID risultante dello spazio si può utilizzare in PhysicsServer3D e PhysicsServer2D rispettivamente per 3D e 2D.
Accedere allo spazio
La fisica di Godot è eseguita normalmente nello stesso thread della logica di gioco, ma è possibile impostarla per eseguirla su un thread separato per funzionare più efficientemente. Pertanto, l'unico momento in cui l'accesso allo spazio è sicuro è durante il callback Node._physics_process(). L'accesso fuori da questa funzione potrebbe causare un errore dovuto al blocco dello spazio.
Per eseguire richieste nello spazio fisico, è necessario utilizzare PhysicsDirectSpaceState2D e PhysicsDirectSpaceState3D.
Use the following code in 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);
}
Or more directly:
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld2D().DirectSpaceState;
}
And in 3D:
func _physics_process(delta):
var space_state = get_world_3d().direct_space_state
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld3D().DirectSpaceState;
}
Richiesta di raycast
Per effettuare una richiesta di raycast 2D, è possibile utilizzare il metodo PhysicsDirectSpaceState2D.intersect_ray(). Ad esempio:
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);
}
Il risultato è un dizionario. Se nulla colpisce il raggio, il dizionario sarà vuoto. Se ha colpito qualcosa, conterrà informazioni sulla collisione:
if result:
print("Hit at point: ", result.position)
if (result.Count > 0)
{
GD.Print("Hit at point: ", result["position"]);
}
Il dizionario result in caso di collisione contiene i seguenti dati:
{
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
}
I dati sono simili nello spazio 3D, utilizzando un Vector3 per le coordinate. Si noti che per abilitare le collisioni con Area3D, il parametro booleano collide_with_areas deve essere impostato su 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);
}
Eccezioni di collisione
A common use case for ray casting is to enable a character to gather data about the world around it. One problem with this is that the same character has a collider, so the ray will only detect its parent's collider, as shown in the following image:
To avoid self-intersection, the intersect_ray() parameters object can take an
array of exceptions via its exclude property. This is an example of how to use it
from a CharacterBody2D or any other collision object node:
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);
}
}
L'array delle eccezioni può contenere oggetti o RID.
Maschera di Collisione
Sebbene il metodo delle eccezioni funzioni bene per escludere il corpo padre, diventa molto scomodo se è necessaria una lista di eccezioni ampia e/o dinamica. In questo caso, è molto più efficiente utilizzare il sistema di strati/maschere di collisione.
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);
}
}
Consulta Esempio in codice per i dettagli su come impostare la maschera di collisione.
3D ray casting from screen
Casting a ray from screen to 3D physics space is useful for object picking. There is not much need to do this because CollisionObject3D has an "input_event" signal that will let you know when it was clicked, but in case there is any desire to do it manually, here's how.
To cast a ray from the screen, you need a Camera3D
node. A Camera3D can be in two projection modes: perspective and
orthogonal. Because of this, both the ray origin and direction must be
obtained. This is because origin changes in orthogonal mode, while
normal changes in perspective mode:
To obtain it using a camera, the following code can be used:
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;
}
}
Ricorda che durante _input(), lo spazio potrebbe essere bloccato, quindi in pratica questa richiesta si dovrebbe eseguire in _physics_process().