发射射线
简介
游戏开发中最常见的任务之一是发射射线(或自定义形状的对象)并检查其击中的内容。 这可以产生复杂的行为,如AI等。 本教程将介绍如何在2D和3D中执行此操作。
Godot将所有低级游戏信息存储在服务器中,而场景只是一个前端。 因此,发射射线通常是较低级别的任务。 对于简单的射线发射,使用 RayCast 和 RayCast2D 节点就可以了,因为它们将每一帧都返回射线投射的结果。
但是,很多时候,射线投射应该是一个更具交互性的过程,因此必须存在通过代码执行此操作的方法。
空间
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 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中使用以下代码:
GDscript
C#
func _physics_process(delta):
var space_rid = get_world_2d().space
var space_state = Physics2DServer.space_get_direct_state(space_rid)
public override void _PhysicsProcess(float delta)
{
var spaceRid = GetWorld2d().Space;
var spaceState = Physics2DServer.SpaceGetDirectState(spaceRid);
}
或者更直接:
GDScript
C#
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
}
在3D中:
GDScript
C#
func _physics_process(delta):
var space_state = get_world().direct_space_state
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld().DirectSpaceState;
}
Raycast查询
For performing a 2D raycast query, the method Physics2DDirectSpaceState.intersect_ray() may be used. For example:
GDScript
C#
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
# use global coordinates, not local to node
var result = space_state.intersect_ray(Vector2(0, 0), Vector2(50, 100))
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));
}
The result is a dictionary. If the ray didn’t hit anything, the dictionary will be empty. If it did hit something, it will contain collision information:
GDScript
C#
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
}
使用Vector3坐标,数据在3D空间中类似。
碰撞异常
光线投射的常见用例是使角色能够收集有关其周围世界的数据。 这个问题的一个问题是同一个角色有一个对撞机,因此光线只会检测其父对手,如下图所示:
为了避免自相交, intersect_ray()
函数可以采用可选的第三个参数,这是一个异常数组。 这是如何从KinematicBody2D或任何其他碰撞对象节点使用它的示例:
GDScript
C#
extends KinematicBody2D
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
var result = space_state.intersect_ray(global_position, enemy_position, [self])
class Body : KinematicBody2D
{
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
var result = spaceState.IntersectRay(globalPosition, enemyPosition, new object[] { this });
}
}
例外数组可以包含对象或RID。
碰撞遮罩
虽然例外方法适用于排除父体,但如果需要大型和/或动态的例外列表,则会变得非常不方便。 在这种情况下,使用碰撞层/遮罩系统要高效得多。
The optional fourth argument for intersect_ray()
is a collision mask. For example, to use the same mask as the parent body, use the collision_mask
member variable:
GDScript
C#
extends KinematicBody2D
func _physics_process(delta):
var space_state = get_world().direct_space_state
var result = space_state.intersect_ray(global_position, enemy_position,
[self], 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);
}
}
See Code example for details on how to set the collision mask.
来自屏幕的3D射线投射
Casting a ray from screen to 3D physics space is useful for object picking. There is not much need to do this because CollisionObject 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.
要从屏幕投射光线,您需要 Camera 节点。 相机``可以是两种投影模式:透视和正交。 因此,必须获得射线原点和方向。 这是因为 ``origin
在正交模式下改变,而 normal
在透视模式下改变:
要使用相机获取它,可以使用以下代码:
GDScript
C#
const ray_length = 1000
func _input(event):
if event is InputEventMouseButton and event.pressed and event.button_index == 1:
var camera = $Camera
var from = camera.project_ray_origin(event.position)
var to = from + camera.project_ray_normal(event.position) * ray_length
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()
中运行。