Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Raycasting

Einführung

Eine der häufigsten Aufgaben in der Spieleentwicklung besteht darin, einen (Licht-) Strahl (oder ein Objekt mit benutzerdefinierter Geometrie) zu werfen und zu überprüfen, auf was er trifft. Dies ermöglicht komplexe Verhaltensweisen, KI usw. In diesem Tutorial wird erklärt, wie dies in 2D und 3D funktioniert.

Godot speichert alle Low-Level-Spielinformationen in Servern, während die Szene nur ein Frontend ist. Daher ist das Raycasting im Allgemeinen eine Aufgabe der unteren Ebene. Für einfache Raycasts funktionieren Nodes wie RayCast3D und RayCast2D, da sie in jedem Frame zurückgeben, was das Ergebnis eines Raycasts ist.

In vielen Fällen muss Raycasting jedoch ein interaktiverer Prozess sein, sodass eine Möglichkeit vorhanden sein muss, dies per Code zu tun.

Space

In der Physikwelt speichert Godot alle Kollisions- und Physikinformationen auf niedriger Ebene in einem Space. Der aktuelle 2D-Space (für 2D-Physik) kann durch Zugriff auf Folgendes abgerufen werden CanvasItem.get_world_2d().space. Für 3D ist es Node3D.get_world_3d().space.

Der resultierende Space RID kann in PhysicsServer3D und PhysicsServer2D jeweils für 3D und 2D verwendet werden.

Zugriff auf den Space

Die Godot-Physik läuft standardmäßig im selben Thread wie die Spiellogik, kann aber zur effizienteren Arbeit in einem separaten Thread ausgeführt werden. Aus diesem Grund ist der einzige Zeitpunkt, zu dem der Zugriff auf den Space sicher ist, während des Node._physics_process()-Callbacks. Der Zugriff von außerhalb dieser Funktion kann zu einem Fehler führen, da der Space gesperrt ist.

Um Abfragen im Physik-Space durchzuführen, müssen PhysicsDirectSpaceState2D und PhysicsDirectSpaceState3D verwendet werden.

Verwenden Sie den folgenden Code in 2D:

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

Oder direkter:

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

Und in 3D:

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

Raycast-Abfrage

Zur Durchführung einer 2D-Raycast-Abfrage kann die Methode PhysicsDirectSpaceState2D.intersect_ray() verwendet werden. Ein Beispiel:

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)

Das Ergebnis ist ein Dictionary. Wenn der Strahl auf nichts trifft, wird das Dictionary leer sein. Wenn er etwas trifft, wird das Dictionary die Kollisionsinformationen enthalten:

if result:
    print("Hit at point: ", result.position)

Das Ergebnis-Dictionary enthält bei einer Kollision die folgenden Daten:

{
   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
}

Die Daten sind im 3D-Raum ähnlich und verwenden Vector3-Koordinaten. Um Kollisionen mit Area3D zu ermöglichen, muss der Bool-Parameter collide_with_areas auf true gesetzt werden.

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)

Kollisionsausnahmen

Ein häufiger Anwendungsfall für das Raycasting besteht darin, einem Charakter das Sammeln von Daten über die Welt um ihn herum zu ermöglichen. Ein Problem dabei ist, dass derselbe Charakter einen Collider hat, sodass der Strahl nur den Collider seines Parents erkennt, wie in der folgenden Abbildung gezeigt:

../../_images/raycast_falsepositive.webp

Um Selbstüberschneidungen zu vermeiden, kann das intersect_ray()-Parameterobjekt über seine exclude-Property ein Array von Ausnahmen annehmen. Dies ist ein Beispiel, wie man es von einem CharacterBody2D oder einem anderen Kollisionsobjekt-Node aus benutzen kann:

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)

Das Ausnahmen-Array kann Objekte oder RIDs enthalten.

Kollisionsmaske

Während die Ausnahmemethode zum Ausschließen des Parent-Bodys gut funktioniert, ist es sehr unpraktisch, wenn Sie eine große oder dynamische Liste von Ausnahmen benötigen. In diesem Fall ist es viel effizienter, das Kollisionsebenen- bzw. Maskensystem zu verwenden.

Das intersect_ray()-Parameterobjekt kann auch mit einer Kollisionsmaske versehen werden. Um zum Beispiel die gleiche Maske wie der übergeordnete Body zu verwenden, benutzen Sie die collision_mask-Membervariable. Das Ausnahmenarray kann auch als letztes Argument übergeben werden:

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)

Siehe Code-Beispiel für Details zum Festlegen der Kollisionsmaske.

3D-Raycasting vom Bildschirm

Das Werfen eines Strahls vom Bildschirm in den 3D-Physik-Space ist nützlich für die Auswahl von Objekten. Es gibt keinen großen Bedarf, dies zu tun, weil CollisionObject3D ein "input_event"-Signal hat, das Sie wissen lässt, wenn es angeklickt wurde, aber für den Fall, dass Sie es manuell tun möchten, finden Sie hier eine Beschreibung.

Um einen Strahl vom Bildschirm aus zu werfen, braucht man einen Camera3D-Node. Eine Camera3D kann sich in zwei Projektionsmodi befinden: Perspektive und Orthogonal. Aus diesem Grund müssen sowohl der Ursprung als auch die Richtung des Strahls ermittelt werden. Das liegt daran, dass sich origin im orthogonalen Modus ändert, während sich normal im perspektivischen Modus ändert:

../../_images/raycast_projection.png

Um sie mit einer Kamera zu erhalten, kann der folgende Code verwendet werden:

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

Denken Sie daran, dass während _input() der Space gesperrt sein kann, so dass in der Praxis diese Abfrage in _physics_process() ausgeführt werden sollte.