发射射线

前言

游戏开发中最常见的任务之一是发射射线(或自定义形状的对象)并检查其击中的内容. 这可以产生复杂的行为, 如AI等. 本教程将介绍如何在2D和3D中执行此操作. RayCast 和 RayCast2D 节点就可以了,因为它们将每一帧都返回光线投射的结果。 但是, 很多时候, 射线投射应该是一个更具交互性的过程, 因此必须存在通过代码执行此操作的方法.

空间

空间 中. 当前的2D空间, 对于2D物理, 可以通过访问 CanvasItem.get_world_2d().space 获得. 对于3D, 则为 Spatial.get_world().space . RID 可在3D的 PhysicsServer 和2D的 Physics2DServer 中.

获取空间

Node._physics_process() 回调期间访问空间才是安全的. 从这个函数之外访问它可能会因为空间被 锁定 而导致错误. Physics2DDirectSpaceState 和 PhysicsDirectSpaceState . 在2D中使用以下代码: GDscript C#

  1. func _physics_process(delta): var space_rid = get_world_2d().space var space_state = Physics2DServer.space_get_direct_state(space_rid)
  1. public override void _PhysicsProcess(float delta){ var spaceRid = GetWorld2d().Space; var spaceState = Physics2DServer.SpaceGetDirectState(spaceRid);}

或者更直接: GDScript C#

  1. func _physics_process(delta): var space_state = get_world_2d().direct_space_state
  1. public override void _PhysicsProcess(float delta){ var spaceState = GetWorld2d().DirectSpaceState;}

在3D中: GDScript C#

  1. func _physics_process(delta): var space_state = get_world().direct_space_state
  1. public override void _PhysicsProcess(float delta){ var spaceState = GetWorld().DirectSpaceState;}

Raycast查询

Physics2DDirectSpaceState.intersect_ray() . 例如: GDScript C#

  1. 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))
  1. 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));}

结果是一个字典. 如果射线没有击中任何东西, 字典将是空的. 如果它确实碰撞到了物体, 将包含碰撞信息碰撞: GDScript C#

  1. if result: print("Hit at point: ", result.position)
  1. if (result.Count > 0) GD.Print("Hit at point: ", result["position"]);

result 字典包含以下数据:

  1. { 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#

  1. extends KinematicBody2Dfunc _physics_process(delta): var space_state = get_world_2d().direct_space_state var result = space_state.intersect_ray(global_position, enemy_position, [self])
  1. class Body : KinematicBody2D{ public override void _PhysicsProcess(float delta) { var spaceState = GetWorld2d().DirectSpaceState; var result = spaceState.IntersectRay(globalPosition, enemyPosition, new Godot.Collections.Array { this }); }}

例外数组可以包含对象或RID.

碰撞遮罩

虽然例外方法适用于排除父体, 但如果需要大型和/或动态的例外列表, 则会变得非常不方便. 在这种情况下, 使用碰撞层/遮罩系统要高效得多. intersect_ray() 的第四个可选参数是一个碰撞掩码. 例如, 要使用与父级相同的掩码, 请使用 collision_mask 成员变量: GDScript C#

  1. extends KinematicBody2Dfunc _physics_process(delta): var space_state = get_world().direct_space_state var result = space_state.intersect_ray(global_position, enemy_position, [self], collision_mask)
  1. class Body : KinematicBody2D{ public override void _PhysicsProcess(float delta) { var spaceState = GetWorld2d().DirectSpaceState; var result = spaceState.IntersectRay(globalPosition, enemyPosition, new Godot.Collections.Array { this }, CollisionMask); }}

代码示例 .

来自屏幕的 3D 光线投射

CollisionObject 有一个 “input_event” 信号, 会让你知道它是什么时候被点击的, 但是如果有想要手动操作需要, 可这样. Camera 节点. 相机 可以是两种投影模式: 透视和正交. 因此, 必须获得射线原点和方向. 这是因为 origin 在正交模式下改变, 而 normal 在透视模式下改变: 要使用相机获取它, 可以使用以下代码: GDScript C#

  1. const ray_length = 1000func _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
  1. private const float rayLength = 1000;public override void _Input(InputEvent @event){ if (@event is InputEventMouseButton eventMouseButton && eventMouseButton.Pressed && eventMouseButton.ButtonIndex == 1) { var camera = GetNode<Camera>("Camera"); var from = camera.ProjectRayOrigin(eventMouseButton.Position); var to = from + camera.ProjectRayNormal(eventMouseButton.Position) * rayLength; }}

_input() 期间, 空间可能被锁定, 所以实际上这个查询应该在 _physics_process() 中运行.