Up to date
This page is up to date for Godot 4.2
.
If you still find outdated information, please open an issue.
發射射線¶
前言¶
遊戲開發中最常見的工作之一是發射射線(或自訂形狀的物件)並檢查其擊中的內容. 這可以產生複雜的行為, 如AI等. 本教學將介紹如何在2D和3D中執行此操作.
Godot 將所有底層遊戲資訊儲存在伺服器中,場景只是一個前端。因此,光線投射通常是較底層的工作。對於簡單的光線投射,使用 RayCast 和 RayCast2D 節點就可以了,因為它們將每一影格都返回光線投射的結果。
但是, 很多時候, 射線投射應該是一個更具互動性的過程, 因此必須存在通過程式碼執行此操作的方法.
空格¶
在物理世界中,Godot將所有低級的碰撞和物理資訊儲存在一個 空間 中. 目前的2D空間, 對於2D物理, 可以通過存取 CanvasItem.get_world_2d().space 獲得. 對於3D, 則為 Spatial.get_world().space .
結果空間 RID 可在3D的 PhysicsServer 和2D的 Physics2DServer 中.
獲取空間¶
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.
要對物理空間執行查詢, 必須使用 Physics2DDirectSpaceState 和 PhysicsDirectSpaceState .
在 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 = Physics2DServer.SpaceGetDirectState(spaceRid);
}
或者更直接:
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld2D().DirectSpaceState;
}
在 3D 中:
func _physics_process(delta):
var space_state = get_world_3d().direct_space_state
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld3D().DirectSpaceState;
}
Raycast 查詢¶
為了執行二維 raycast射線查詢, 可以使用方法 Physics2DDirectSpaceState.intersect_ray() . 例如:
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);
}
結果是一個字典. 如果射線沒有擊中任何東西, 字典將是空的. 如果它確實碰撞到了物體, 將包含碰撞資訊碰撞:
if result:
print("Hit at point: ", result.position)
if (result.Count > 0)
GD.Print("Hit at point: ", result["position"]);
發生碰撞時,result
字典包含以下資料:
{
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
}
3D 空間中的資料類似,使用 Vector3 座標。請注意,要啟用與 Area3D 的碰撞,布林參數「collide_with_areas」必須設為「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)
碰撞例外¶
光線投射的常見用例是使角色能夠收集有關其周圍世界的資料。這種情況的一個問題是該角色上有碰撞體,因此光線只會偵測到其父節點上的碰撞體,如下圖所示:
為了避免自相交, intersect_ray()
函式可以採用可選的第三個參數, 這是一個排除陣列. 這是如何從KinematicBody2D或任何其他碰撞物件節點使用它的範例:
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 = new Godot.Collections.Array<Rid> { GetRid() };
var result = spaceState.IntersectRay(query);
}
}
例外陣列可以包含物件或 RID。
碰撞遮罩¶
雖然例外方法適用於排除父體, 但如果需要大型和/或動態的例外列表, 則會變得非常不方便. 在這種情況下, 使用碰撞層/遮罩系統要高效得多.
intersect_ray()
的第四個可選參數是一個碰撞遮罩. 例如, 要使用與父級相同的遮罩, 請使用 collision_mask
成員變數:
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, new Godot.Collections.Array<Rid> { GetRid() });
var result = spaceState.IntersectRay(query);
}
}
關於如何設定碰撞遮罩, 請參閱 計時器範例 .
來自螢幕的 3D 光線投射¶
將一條射線從螢幕上投射到3D物理空間, 對於物件的選取是很有用, 但沒有太多必要這樣做, 因為 CollisionObject 有一個 "input_event" 訊號, 會讓你知道它是什麼時候被點擊的, 但是如果有想要手動操作需要, 可這樣.
要從螢幕投射光線, 您需要 Camera 節點. 相機
可以是兩種投影模式: 透視和正交. 因此, 必須獲得射線原點和方向. 這是因為 origin
在正交模式下改變, 而 normal
在透視模式下改變:
要使用相機獲取它, 可以使用以下程式碼:
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;
}
}
請記住,在 _input()
期間空間可能被鎖定,所以實踐中應該在 _physics_process()
中運作這個查詢。