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.

Ray-casting

Introduzione

Uno dei compiti più comuni nello sviluppo di videogiochi è proiettare 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 effettuare richieste nello spazio fisico, è necessario utilizzare PhysicsDirectSpaceState2D e PhysicsDirectSpaceState3D.

Usa il seguente codice in 2D:

func _physics_process(delta):
    var space_rid = get_world_2d().space
    var space_state = PhysicsServer2D.space_get_direct_state(space_rid)

O più direttamente:

func _physics_process(delta):
    var space_state = get_world_2d().direct_space_state

E in 3D:

func _physics_process(delta):
    var space_state = get_world_3d().direct_space_state

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)

Il risultato è un dizionario. Se il raggio non colpisce nulla, il dizionario sarà vuoto. Se ha colpito qualcosa, conterrà informazioni sulla collisione:

if result:
    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)

Eccezioni di collisione

Un caso d'uso comune del ray casting è quello di consentire a un personaggio di raccogliere dati sul mondo circostante. Un problema in questo caso è che il personaggio stesso ha un collisore, quindi il raggio rileverà solo il collisore del suo genitore, come mostrato nell'immagine seguente:

../../_images/raycast_falsepositive.webp

Per evitare auto-intersezioni, l'oggetto passato in intersect_ray() può accettare un array di eccezioni tramite la sua proprietà exclude. Questo è un esempio di come utilizzarlo da un nodo CharacterBody2D o da qualsiasi altro nodo oggetto di collisione:

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)

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.

All'oggetto passato in intersect_ray() può essere fornita anche una maschera di collisione. Ad esempio, per utilizzare la stessa maschera del corpo padre, usa la variabile membro collision_mask. L'array di eccezioni può essere fornito anche come ultimo argomento:

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)

Consulta Esempio in codice per i dettagli su come impostare la maschera di collisione.

Ray casting 3D dallo schermo

Proiettare un raggio dallo schermo allo spazio fisico 3D è utile per selezionare gli oggetti. Non c'è molto bisogno di farlo così perché CollisionObject3D ha un segnale "input_event" che fa sapere quando viene cliccato, ma nel caso in cui si desideri farlo manualmente, ecco come.

Per proiettare un raggio dallo schermo, è necessario un nodo Camera3D. Una Camera3D può essere in due modalità di proiezione: prospettica e ortogonale. Per questo motivo, bisogna ottenere sia l'origine sia la direzione del raggio. Questo perché l'origine (origin) cambia in modalità ortogonale, mentre la normale (normal) cambia in modalità prospettica:

../../_images/raycast_projection.png

Per ottenerlo tramite una telecamera, è possibile utilizzare il seguente codice:

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

Ricorda che durante _input(), lo spazio potrebbe essere bloccato, quindi in pratica questa richiesta si dovrebbe eseguire in _physics_process().