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 è 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)

Or more directly:

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

And 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 nulla colpisce il raggio, 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

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:

../../_images/raycast_falsepositive.webp

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)

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)

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:

../../_images/raycast_projection.png

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

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