광선 투사(Ray Cast)하기¶
소개¶
게임 개발에서 일반적인 과제로 광선을 (혹은 맞춤 모양 오브젝트를) 투사하고, 닿은 것이 무엇인지 확인하는 것입니다. 광선 투사를 하면 복잡한 동작도 만들 수 있는데, 예를 들면 AI가 있습니다. 이 튜토리얼에서는 어떻게 광선 추적을 하는지 2D와 3D에서 설명하겠습니다.
Godot는 서버에 로우 레벨 게임 정보를 저장합니다. 씬은 단지 프론트엔드입니다. 마찬가지로 광선 추적은 일반적으로 더 로우 레벨의 과제입니다. 간단한 광선 추적으로 RayCast와 RayCast2D 노드로 가능합니다. 매 프레임마다 광선 추적의 결과를 반환하죠.
하지만 많은 경우에서 광선 추적은 더 상호작용적인 처리가 필요하기에, 코드로 해결해야 합니다.
공간(Space)¶
물리 세계에서, Godot는 모든 로우 레벨 콜리전과 물리 정보를 공간(Space)에 저장합니다. (2D 물리의) 현재 2d 공간은 CanvasItem.get_world_2d().space으로 접근해서 가져올 수 있습니다. 3D의 경우는 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에서는 다음 코드를 사용합니다:
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);
}
더욱더 직접적인 코드:
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld2d().DirectSpaceState;
}
3D에서는 다음 코드를 사용합니다:
func _physics_process(delta):
var space_state = get_world().direct_space_state
public override void _PhysicsProcess(float delta)
{
var spaceState = GetWorld().DirectSpaceState;
}
광선 투사 쿼리¶
For performing a 2D raycast query, the method Physics2DDirectSpaceState.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 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:
if result:
print("Hit at point: ", result.position)
if (result.Count > 0)
GD.Print("Hit at point: ", result["position"]);
콜리전이 발생할 경우 결과
딕셔너리에는 다음 데이터가 포함됩니다:
{
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 또는 다른 콜리전 오브젝트 노드에서 사용하는 방법의 예입니다:
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 Godot.Collections.Array { this });
}
}
예외 배열에는 오브젝트 또는 RID가 포함될 수 있습니다.
충돌 마스크¶
While the exceptions method works fine for excluding the parent body, it becomes very inconvenient if you need a large and/or dynamic list of exceptions. In this case, it is much more efficient to use the collision layer/mask system.
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:
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 Godot.Collections.Array { this }, CollisionMask);
}
}
See Code example for details on how to set the collision mask.
화면에서의 3D ray casting(광선 투사)¶
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 노드가 필요합니다. 카메라
는 두 가지 투영 모드일 수 있습니다: 원근법과 직교. 이로 인해 광선 원점과 방향을 모두 얻어야 합니다. 이는 직교 모드의 원래
변화, 원근법 모드에서의 정상
변화 때문입니다:

카메라를 사용하여 얻기 위해선, 다음 코드를 사용할 수 있습니다:
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 = GetNode<Camera>("Camera");
var from = camera.ProjectRayOrigin(eventMouseButton.Position);
var to = from + camera.ProjectRayNormal(eventMouseButton.Position) * rayLength;
}
}
_input()
동안 공간이 잠겨 있을 수 있으므로 실제로 이 쿼리는 ``_physics_process()``에서 실행해야 한다는 점을 기억하십시오.