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.
The resulting space RID can be used in PhysicsServer3D and PhysicsServer2D respectively for 3D and 2D.
获取空间¶
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 = 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 查询¶
For performing a 2D raycast query, the method PhysicsDirectSpaceState2D.intersect_ray() may be used. For example:
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
}
The data is similar in 3D space, using Vector3 coordinates. Note that to enable collisions
with Area3D, the boolean parameter collide_with_areas
must be set to true
.
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)
碰撞例外¶
光线投射的常见用例是使角色能够收集有关其周围世界的数据。这种情况的一个问题是该角色上有碰撞体,因此光线只会检测到其父节点上的碰撞体,如下图所示:
To avoid self-intersection, the intersect_ray()
parameters object can take an
array of exceptions via its exclude
property. This is an example of how to use it
from a CharacterBody2D or any other collision object node:
extends CharacterBody2D
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
var query = PhysicsRayQueryParameters2D.create(global_position, enemy_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, enemyPosition);
query.Exclude = new Godot.Collections.Array<Rid> { GetRid() };
var result = spaceState.IntersectRay(query);
}
}
例外数组可以包含对象或 RID。
碰撞遮罩¶
虽然例外方法适用于排除父体, 但如果需要大型和/或动态的例外列表, 则会变得非常不方便. 在这种情况下, 使用碰撞层/遮罩系统要高效得多.
The intersect_ray()
parameters object can also be supplied a collision mask.
For example, to use the same mask as the parent body, use the collision_mask
member variable. The array of exceptions can be supplied as the last argument as well:
extends CharacterBody2D
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
var query = PhysicsRayQueryParameters2D.create(global_position, enemy_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, enemyPosition,
CollisionMask, new Godot.Collections.Array<Rid> { GetRid() });
var result = spaceState.IntersectRay(query);
}
}
关于如何设置碰撞掩码, 请参阅 代码示例 .
来自屏幕的 3D 光线投射¶
Casting a ray from screen to 3D physics space is useful for object picking. There is not much need to do this because CollisionObject3D 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 Camera3D
node. A Camera3D
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:
要使用相机获取它, 可以使用以下代码:
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()
中运行这个查询。