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 將所有底層遊戲資訊儲存在伺服器中,場景只是一個前端。因此,光線投射通常是較底層的工作。對於簡單的光線投射,使用 RayCastRayCast2D 節點就可以了,因為它們將每一影格都返回光線投射的結果。

但是, 很多時候, 射線投射應該是一個更具互動性的過程, 因此必須存在通過程式碼執行此操作的方法.

空格

在物理世界中,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.

要對物理空間執行查詢, 必須使用 Physics2DDirectSpaceStatePhysicsDirectSpaceState .

在 2D 中使用以下程式碼:

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

或者更直接:

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

在 3D 中:

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

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)

結果是一個字典. 如果射線沒有擊中任何東西, 字典將是空的. 如果它確實碰撞到了物體, 將包含碰撞資訊碰撞:

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

碰撞例外

光線投射的常見用例是使角色能夠收集有關其周圍世界的資料。這種情況的一個問題是該角色上有碰撞體,因此光線只會偵測到其父節點上的碰撞體,如下圖所示:

../../_images/raycast_falsepositive.webp

為了避免自相交, 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)

例外陣列可以包含物件或 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)

關於如何設定碰撞遮罩, 請參閱 計時器範例 .

來自螢幕的 3D 光線投射

將一條射線從螢幕上投射到3D物理空間, 對於物件的選取是很有用, 但沒有太多必要這樣做, 因為 CollisionObject 有一個 "input_event" 訊號, 會讓你知道它是什麼時候被點擊的, 但是如果有想要手動操作需要, 可這樣.

要從螢幕投射光線, 您需要 Camera 節點. 相機 可以是兩種投影模式: 透視和正交. 因此, 必須獲得射線原點和方向. 這是因為 origin 在正交模式下改變, 而 normal 在透視模式下改變:

../../_images/raycast_projection.png

要使用相機獲取它, 可以使用以下程式碼:

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

請記住,在 _input() 期間空間可能被鎖定,所以實踐中應該在 _physics_process() 中運作這個查詢。