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.

광선 투사(Ray Cast)하기

소개

게임 개발에서 일반적인 과제로 광선을 (또는 커스텀 모양 오브젝트를) 투사하고, 닿은 것이 무엇인지 확인하는 것입니다. 광선 투사를 하면 복잡한 동작도 만들 수 있는데, 예를 들면 AI가 있습니다. 이 튜토리얼에서는 어떻게 광선 추적을 하는지 2D와 3D에서 설명하겠습니다.

Godot는 서버에 저수준 게임 정보를 저장합니다. 씬은 단지 프론트엔드입니다. 마찬가지로 광선 추적은 일반적으로 더 저수준의 과제입니다. 간단한 광선 추적으로 RayCastRayCast2D 노드로 가능합니다. 매 프레임마다 광선 추적의 결과를 반환하죠.

하지만 많은 경우에서 광선 추적은 더 상호작용적인 처리가 필요하기에, 코드로 해결해야 합니다.

공간(Space)

물리 세계에서, Godot는 모든 저수준 콜리전과 물리 정보를 공간(Space)에 저장합니다. (2D 물리의) 현재 2d 공간은 CanvasItem.get_world_2d().space으로 접근해서 가져올 수 있습니다. 3D의 경우는 Spatial.get_world().space으로 가져올 수 있습니다.

결과 공간인 RID는, 3D에는 PhysicsServer에, 2D에는 Physics2DServer에 각각 사용할 수 있습니다.

공간에 접근하기

Godot는 기본적으로 게임 로직과 동일한 스레드에서 실행되지만, 보다 효율적으로 작동하기 위해 별도의 스레드에서 실행되도록 설정할 수 있습니다. 이러한 이유로, 공간에 접근하는 시간은 Node._physics_process() 콜백 함수 동안은 안전합니다. 이 함수 외부에서 접근할 경우 공간이 잠겨 있기 때문에 오류가 발생할 수 있습니다.

물리 공간에 대한 쿼리를 수행하려면 Physics2DDirectSpaceStatePhysicsDirectSpaceState 를 사용해야 합니다.

2D에서는 다음 코드를 사용합니다:

func _physics_process(delta):
    var space_rid = get_world_2d().space
    var space_state = PhysicsServer2D.space_get_direct_state(space_rid)

더욱더 직접적인 코드:

func _physics_process(delta):
    var space_state = get_world_2d().direct_space_state

3D에서는 다음 코드를 사용합니다:

func _physics_process(delta):
    var space_state = get_world_3d().direct_space_state

광선 투사 쿼리

2D 광선 투사 쿼리를 수행하려면 Physics2DDirectSpaceState.intersect_ray() 방법을 사용할 수 있습니다. 예를 들면 다음과 같습니다:

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)

결과는 딕셔너리입니다. 만약 광선이 아무 것도 건드리지 않으면, 딕셔너리는 텅 비게 될 것입니다. 만약 무언가에 맞는다면 콜리전 정보가 포함됩니다:

if result:
    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 공간에서 유사합니다. Area3D와의 충돌을 활성화하려면 부울 매개변수 ``collide_with_areas``를 ``true``로 설정해야 합니다.

const RAY_LENGTH = 1000

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)

콜리전 예외

광선 투사의 일반적인 용도는 캐릭터가 주변 세계에 대한 데이터를 수집할 수 있도록 하는 것입니다. 이것의 한 가지 문제점은 다음 이미지와 같이 같은 캐릭터가 충돌체를 가지고 있어서, 광선은 부모의 충돌체만 감지한다는 것입니다:

../../_images/raycast_falsepositive.webp

자체 교차를 방지하기 위해, intersect_ray() 함수는 예외 배열인 선택적 세 번째 매개변수를 취할 수 있습니다. 이것은 KinematicBody2D 또는 다른 콜리전 오브젝트 노드에서 사용하는 방법의 예입니다:

extends CharacterBody2D

func _physics_process(delta):
    var space_state = get_world_2d().direct_space_state
    var query = PhysicsRayQueryParameters2D.create(global_position, player_position)
    query.exclude = [self]
    var result = space_state.intersect_ray(query)

예외 배열에는 오브젝트 또는 RID가 포함될 수 있습니다.

콜리전 마스크

예외 방법은 부모 body를 제외하고도 잘 작동하지만, 많은 and/or 동적 예외 목록이 필요한 경우 매우 불편하게 됩니다. 이 경우 콜리전 레이어/마스크 시스템을 사용하는 것이 훨씬 효율적입니다.

intersect_ray()의 네 번째 선택적인 인수는 콜리전 마스크입니다. 예를 들어 부모 body와 동일한 마스크를 사용하려면 collision_mask 멤버 변수를 사용해야 합니다:

extends CharacterBody2D

func _physics_process(delta):
    var space_state = get_world_2d().direct_space_state
    var query = PhysicsRayQueryParameters2D.create(global_position, target_position,
        collision_mask, [self])
    var result = space_state.intersect_ray(query)

충돌 마스크 설정 방법에 대한 자세한 내용은 :ref:`doc_physics_introduction_collision_layer_code_example`를 참조하세요.

화면에서의 3D ray casting(광선 투사)

화면에서 3D 물리학 공간으로 광선을 투사하는 것은 물체를 선택하는 데 유용합니다. CollisionObject 에는 "input_event" 신호가 있어 클릭 시 이를 알 수 있지만, 수동으로 수행할 수 있는 방법이 있습니다.

화면에서 광선을 캐스팅하려면 Camera 노드가 필요합니다. 카메라 는 두 가지 투영 모드일 수 있습니다: 원근법과 직교. 이로 인해 광선 원점과 방향을 모두 얻어야 합니다. 이는 직교 모드의 원래 변화, 원근법 모드에서의 정상 변화 때문입니다:

../../_images/raycast_projection.png

카메라를 사용하여 얻기 위해선, 다음 코드를 사용할 수 있습니다:

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

_input() 동안 공간이 잠겨 있을 수 있으므로 실제로 이 쿼리는 ``_physics_process()``에서 실행해야 한다는 점을 기억하세요.