射线检测
项目开发中最常见的任务之一是发射射线(或自定义形状的对象)并检查其击中的内容. 这可以产生复杂的行为, 如AI等。对于简单的光线投射,使用RayCast和RayCast2D节点就可以了,因为它们将每一帧都返回光线投射的结果。但是, 很多时候, 射线投射应该是一个更具交互性的过程, 因此必须存在通过代码执行此操作的方法。
RayCast节点
它用于检测 3D 空间,以便找到沿光线路径最近的对象。需要设置cast_to属性,该属性用于表示相对于RayCast节点的目标位置。
RayCast 可以忽略某些对象,方法是通过 add_exception 将它们添加到例外列表中,或者通过使用碰撞层和掩码设置,进行适当的过滤。
RayCast 可以设置检测的对象是Area或PhysicsBody。
只有启用的RayCast节点才能检测空间并报告碰撞。
RayCast 计算每个物理帧的交集,并将结果缓存起来,以便稍后使用,直到下一帧。如果物理帧之间(或同一帧期间)需要多次检测,请在调整光线投射后使用force_raycast_update。
物理空间
什么是物理空间
在物理世界中,IdeaXR将所有低级的碰撞和物理信息存储在一个空间中。可以通过访问Spatial.get_world().space获得当前空间。对于2D对象, 则为CanvasItem.get_world_2d().space获取当前2D空间。
结果返回的空间RID可在3D的PhysicsServer和2D的Physics2DServer中使用。
如何获取物理空间
IdeaXR物理系统默认与游戏逻辑运行在同一个线程中, 但也可以设置为在一个单独的线程上运行, 以便更高效地工作. 由于这一点, 只有在Node._physics_process()回调期间访问空间才是安全的。从这个函数之外访问它可能会因为空间被锁定而导致错误。
要对物理空间执行查询, 必须使用 Physics2DDirectSpaceState 和 PhysicsDirectSpaceState对象。以3D为例:
func _physics_process(delta):
var space_state = get_world().direct_space_state
射线检测碰撞
碰撞查询
为了执行三维raycast射线查询, 可以使用方法 PhysicsDirectSpaceState.intersect_ray()。例如:
func _physics_process(delta):
var space_state = get_world().direct_space_state
# use global coordinates, not local to node
var result = space_state.intersect_ray(Vector3(0, 0, 0), Vector3(50, 0, 100))
结果是一个字典. 如果射线没有击中任何东西, 字典将是空的. 如果它确实碰撞到了物体, result变量将包含碰撞信息:
{
position: Vector3 # point in world space for collision
normal: Vector3 # 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
}
使用排除(exception)数组
射线投射的常见用例是使角色能够收集有关其周围世界的数据。这种情况的一个问题是该角色上有碰撞体,因此射线只会检测到其父节点上的碰撞体。为了避免自相交, intersect_ray() 函数可以采用可选的第三个参数, 这是一个排除数组。这是如何从KinematicBody或任何其他碰撞对象节点使用它的示例:
extends KinematicBody
func _physics_process(delta):
var space_state = get_world().direct_space_state
# 此处将节点本身,节点$StaticBody以及$StaticBody2添加到了exception数组中,将不会被检测到
var result = space_state.intersect_ray(global_position, enemy_position, [self,$StaticBody,$StaticBody2])
exception数组可以包含对象或 RID。
使用碰撞遮罩
虽然exception数组适用于排除父体, 但如果需要大型或动态的exception数组, 则会变得非常不方便. 在这种情况下, 使用碰撞层/遮罩系统要高效得多。 intersect_ray() 的第四个可选参数是一个碰撞掩码。例如,要使用与父级相同的掩码,请使用collision_mask成员变量:
extends KinematicBody
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)
intersect_ray() 的第四个可选参数是一个碰撞掩码. 例如, 要使用与父级相同的掩码, 请使用 collision_mask 成员变量。对于碰撞遮罩更详细的介绍请参考:碰撞层级与遮罩
从屏幕鼠标发射射线检测
将一条射线从屏幕上投射到3D物理空间, 对于对象的选取是很有用, 但没有太多必要这样做, 因为 CollisionObject 有一个 "input_event" 信号, 会让你知道它是什么时候被点击的, 但是如果有想要手动操作需要, 可按照下面的方式来做。
要从屏幕投射光线, 您需要Camera节点。相机可以是两种投影模式: 透视和正交。 因此, 必须获得射线原点和方向。这是因为origin在正交模式下改变, 而normal在透视模式下改变:
要使用相机获取它, 可以使用以下代码:
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
请记住,在 _input() 期间空间可能被锁定,所以实践中应该在_physics_process()中运行这个查询。