Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
发射射线
前言
通过发射射线(和自定义形状的对象)来检测命中的物体是游戏开发中最常见的任务之一。这是 AI 等复杂行为的基础。本教程将介绍在 2D 和 3D 中的实现方法。
Godot stores all the low-level game information in servers, while the scene is only a frontend. As such, ray casting is generally a lower-level task. For simple raycasts, nodes like RayCast3D and RayCast2D will work, as they return every frame what the result of a raycast is.
但是很多时候,射线投射需要更具交互性,因此必须存在通过代码执行此操作的方法。
空间
In the physics world, Godot stores all the low-level collision and physics information in a space. The current 2d space (for 2D Physics) can be obtained by accessing CanvasItem.get_world_2d().space. For 3D, it's Node3D.get_world_3d().space.
对于 3D 和 2D,得到的空间 RID 可分别在 PhysicsServer3D 和 PhysicsServer2D 中使用。
获取空间
Godot 物理默认与游戏逻辑在同一个线程中运行,但可以设置为在单独的线程中运行以提高效率。因此,唯一安全访问空间的时间是在 Node._physics_process() 回调期间。从该函数之外访问空间可能会产生一个错误,因为空间会被锁定。
To perform queries into physics space, the PhysicsDirectSpaceState2D and PhysicsDirectSpaceState3D must be used.
在 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 = PhysicsServer2D.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 查询
要执行 2D 射线查询,可以使用 PhysicsDirectSpaceState2D.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)
private const int RayLength = 1000;
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld3D().DirectSpaceState;
var cam = GetNode<Camera3D>("Camera3D");
var mousePos = GetViewport().GetMousePosition();
var origin = cam.ProjectRayOrigin(mousePos);
var end = origin + cam.ProjectRayNormal(mousePos) * RayLength;
var query = PhysicsRayQueryParameters3D.Create(origin, end);
query.CollideWithAreas = true;
var result = spaceState.IntersectRay(query);
}
碰撞例外
光线投射的常见用例是使角色能够收集有关其周围世界的数据。这种情况的一个问题是该角色上有碰撞体,因此光线只会检测到其父节点上的碰撞体,如下图所示:

为了避免自相交,intersect_ray()
参数对象可以通过其 exclude
属性获取一个排除数组。这是一个如何从 CharacterBody2D 或任何其他碰撞对象节点使用它的示例:
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 = [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, [GetRid()]);
var result = spaceState.IntersectRay(query);
}
}
关于如何设置碰撞掩码, 请参阅 代码示例 .
来自屏幕的 3D 光线投射
将射线从屏幕投射到 3D 物理空间对于拾取对象非常有用。没有必要这样做,因为 CollisionObject3D 有一个“input_event”信号,可以让你知道何时点击它,但如果你希望手动执行该操作,可这样。
要从屏幕投射光线,你需要一个 Camera3D 节点。Camera3D
可以有两种投影模式:透视和正交。因此,必须获取射线原点和方向。这是因为 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()
中运行这个查询。