介紹
遊戲開發中最常見的任務之一是投射光線(或自定義形狀的物體)并檢查其撞擊。這樣就可以進行複雜的行為,AI等。本教程将說明如何在2D和3D中執行此操作。
Godot将所有低級遊戲資訊存儲在伺服器中,而場景隻是前端。是以,射線投射通常是較低級别的任務。對于簡單的射線廣播,諸如RayCast和RayCast2D之類的節點 将起作用,因為它們将在每一幀中傳回射線廣播的結果。
但是,很多時候,光線投射必須是一個更具互動性的過程,是以必須存在一種通過代碼進行光線投射的方法。
空間
在實體世界中,戈多特将所有低級碰撞和實體資訊存儲在一個空間中。可以通過通路CanvasItem.get_world_2d()。space擷取目前的2d空間(用于2D實體) 。對于3D,它是Spatial.get_world()。space。
生成的空間RID可以分别在 PhysicsServer和 Physics2DServer中用于3D和2D。
進入空間
Godot實體預設情況下與遊戲邏輯在同一線程中運作,但可以設定為在單獨的線程上運作以更有效地工作。是以,唯一安全的通路空間時間是在 Node._physics_process() 回調期間。由于空間被鎖定,從此功能外部通路它可能會導緻錯誤。
要對實體空間執行查詢, 必須使用Physics2DDirectSpaceState 和PhysicsDirectSpaceState。
在2D中使用以下代碼:
public override void _PhysicsProcess(float delta)
{
var spaceRid = GetWorld2d().Space;
var spaceState = Physics2DServer.SpaceGetDirectState(spaceRid);
}
或更直接地:
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
}
在3D中:
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld().DirectSpaceState;
}
Raycast查詢
為了執行2D射線廣播查詢, 可以使用Physics2DDirectSpaceState.intersect_ray()方法 。例如:
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));
}
結果是一個字典。如果射線沒有擊中任何東西,則字典将為空。如果确實撞到了東西,它将包含碰撞資訊:
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
}
使用Vector3坐标,資料在3D空間中相似。
碰撞異常
射線投射的一個常見用例是使角色能夠收集有關其周圍世界的資料。這樣做的一個問題是,同一個角色具有對撞機,是以,光線将僅檢測其父級的對撞機,如下圖所示:

為了避免自相交,該intersect_ray()函數可以采用可選的第三個參數,該參數是一組異常。這是如何從KinematicBody2D或任何其他碰撞對象節點使用它的示例:
class Body : KinematicBody2D
{
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
var result = spaceState.IntersectRay(globalPosition, enemyPosition, new object[] { this });
}
}
異常數組可以包含對象或RID。
防撞面罩
盡管exception方法可以很好地排除父正文,但是如果您需要大量和/或動态的exception清單,它将變得非常不便。在這種情況下,使用碰撞層/遮罩系統效率更高。
可選的第四個參數intersect_ray()是碰撞蒙版。例如,要使用與父實體相同的蒙版,請使用collision_mask 成員變量:
class Body : KinematicBody2D
{
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
var result = spaceState.IntersectRay(globalPosition, enemyPosition,
new object[] { this }, CollisionMask);
}
}
有關如何設定碰撞遮罩的詳細資訊,請參見代碼示例。
螢幕上的3D射線投射
将光線從螢幕投射到3D實體空間對于拾取對象很有用。不需要這樣做,因為 CollisionObject 有一個“ input_event”信号,可以讓您知道何時單擊它,但是如果有手動操作的願望,請按以下步驟操作。
要從螢幕投射光線,您需要一個Camera 節點。ACamera可以采用兩種投影模式:透視和正交。是以,必須同時獲得射線的起點和方向。這是因為origin在正交模式下會normal發生變化,而在透視模式下會 發生變化:
要使用相機擷取它,可以使用以下代碼:
private const float rayLength = 1000;
public override void _Input(InputEvent @event)
{
if (@event is InputEventMouseButton eventMouseButton && eventMouseButton.Pressed && eventMouseButton.ButtonIndex == 1)
{
var camera = (Camera)GetNode("Camera");
var from = camera.ProjectRayOrigin(eventMouseButton.Position);
var to = from + camera.ProjectRayNormal(eventMouseButton.Position) * rayLength;
}
}
請記住,在期間_input(),該空間可能被鎖定,是以實際上該查詢應在中運作_physics_process()。