Raycasting¶
Einführung¶
Eine der häufigsten Aufgaben in der Spieleentwicklung besteht darin, einen (Licht-) Strahl (oder ein benutzerdefiniertes Objekt) zu werfen und zu überprüfen, was darauf trifft. Dies ermöglicht komplexe Verhaltensweisen, KI usw. In dieser Anleitung wird erklärt, wie dies in 2D und 3D funktioniert.
Godot speichert alle Low-Level-Spielinformationen auf Servern, während die Szene nur ein Frontend ist. Daher ist das Raycasting (Strahlenwerfen) im Allgemeinen eine untergeordnete Aufgabe. Bei einfachen Raycasts funktionieren Nodes wie RayCast und RayCast2D, da sie jedem Frame das Ergebnis eines Raycasts zurückgeben.
In vielen Fällen muss Raycasting jedoch ein interaktiverer Prozess sein, sodass eine Möglichkeit vorhanden sein muss, dies per Code zu tun.
Leerzeichen¶
In der Physikwelt speichert Godot alle Kollisions- und Physikinformationen auf niedriger Ebene in einem Raum. Der aktuelle 2D-Raum (für 2D-Physik) kann durch Zugriff auf Folgendes abgerufen werden CanvasItem.get_world_2d().space. Für 3D ist es Spatial.get_world().space.
The resulting space RID can be used in PhysicsServer and Physics2DServer respectively for 3D and 2D.
Zugriff auf den Raum¶
Godot physics runs by default in the same thread as game logic, but may be set to run on a separate thread to work more efficiently. Due to this, the only time accessing space is safe is during the Node._physics_process() callback. Accessing it from outside this function may result in an error due to space being locked.
Um Abfragen im physikalischen Raum durchzuführen, müssen Physics2DDirectSpaceState und PhysicsDirectSpaceState verwendet werden.
Verwenden Sie den folgenden Code in 2D:
func _physics_process(delta):
var space_rid = get_world_2d().space
var space_state = Physics2DServer.space_get_direct_state(space_rid)
public override void _PhysicsProcess(float delta)
{
var spaceRid = GetWorld2d().Space;
var spaceState = Physics2DServer.SpaceGetDirectState(spaceRid);
}
Oder direkter:
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
}
Und in 3D:
func _physics_process(delta):
var space_state = get_world().direct_space_state
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld().DirectSpaceState;
}
Raycast-Abfrage¶
Um eine 2D-Raycast-Abfrage durchzuführen, kann die Methode Physics2DDirectSpaceState.intersect_ray() verwendet werden. Zum Beispiel:
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
# use global coordinates, not local to node
var result = space_state.intersect_ray(Vector2(0, 0), Vector2(50, 100))
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
// use global coordinates, not local to node
var result = spaceState.IntersectRay(new Vector2(), new Vector2(50, 100));
}
Das Ergebnis ist ein Verzeichnis. Wenn der Strahl auf nichts trifft, wird das Verzeichnis leer sein. Wenn er etwas trifft, wird das Verzeichnis die Kollisionsinformationen enthalten:
if result:
print("Hit at point: ", result.position)
if (result.Count > 0)
GD.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 unter Verwendung von Vector3-Koordinaten ähnlich.
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 dasselbe Zeichen einen Kollider hat, sodass der Strahl nur den Kollider seines Elternteils erkennt, wie in der folgenden Abbildung gezeigt:
To avoid self-intersection, the intersect_ray()
function can take an
optional third parameter which is an array of exceptions. This is an
example of how to use it from a KinematicBody2D or any other
collision object node:
extends KinematicBody2D
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
var result = space_state.intersect_ray(global_position, enemy_position, [self])
class Body : KinematicBody2D
{
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
var result = spaceState.IntersectRay(globalPosition, enemyPosition, new Godot.Collections.Array { this });
}
}
Das Ausnahme-Array kann Objekte oder RIDs enthalten.
Kollisionsmaske¶
Während die Ausnahmemethode zum Ausschließen des übergeordneten Körpers 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 Kollisionsschichten- bzw. Maskensystem zu verwenden.
The optional fourth argument for intersect_ray()
is a collision mask. For
example, to use the same mask as the parent body, use the collision_mask
member variable:
extends KinematicBody2D
func _physics_process(delta):
var space_state = get_world().direct_space_state
var result = space_state.intersect_ray(global_position, enemy_position,
[self], collision_mask)
class Body : KinematicBody2D
{
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
var result = spaceState.IntersectRay(globalPosition, enemyPosition,
new Godot.Collections.Array { this }, CollisionMask);
}
}
Siehe Code Beispiel für Details zum Festlegen der Kollisionsmaske.
3D-Raycasting vom Bildschirm¶
Casting a ray from screen to 3D physics space is useful for object picking. There is not much need to do this because CollisionObject 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 Camera
node. A Camera
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:
Um sie mit einer Kamera zu erhalten, kann der folgende Code verwendet werden:
const ray_length = 1000
func _input(event):
if event is InputEventMouseButton and event.pressed and event.button_index == 1:
var camera = $Camera
var from = camera.project_ray_origin(event.position)
var to = from + camera.project_ray_normal(event.position) * ray_length
private const float rayLength = 1000;
public override void _Input(InputEvent @event)
{
if (@event is InputEventMouseButton eventMouseButton && eventMouseButton.Pressed && eventMouseButton.ButtonIndex == 1)
{
var camera = GetNode<Camera>("Camera");
var from = camera.ProjectRayOrigin(eventMouseButton.Position);
var to = from + camera.ProjectRayNormal(eventMouseButton.Position) * rayLength;
}
}
Remember that during _input()
, the space may be locked, so in practice
this query should be run in _physics_process()
.