Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

광선 투사(Ray Cast)하기

소개

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

Godot stores all the low level game information in servers, while the scene is only a frontend. As such, ray casting is generally a lower-level task. For simple raycasts, nodes like RayCast3D and RayCast2D will work, as they return every frame what the result of a raycast is.

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

공간(Space)

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 Node3D.get_world_3d().space.

The resulting space RID can be used in PhysicsServer3D and PhysicsServer2D respectively for 3D and 2D.

공간에 접근하기

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.

To perform queries into physics space, the PhysicsDirectSpaceState2D and PhysicsDirectSpaceState3D must be used.

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

광선 투사 쿼리

For performing a 2D raycast query, the method PhysicsDirectSpaceState2D.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 query = PhysicsRayQueryParameters2D.create(Vector2(0, 0), Vector2(50, 100))
    var result = space_state.intersect_ray(query)

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)

콜리전이 발생할 경우 결과 딕셔너리에는 다음 데이터가 포함됩니다:

{
   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
}

The data is similar in 3D space, using Vector3 coordinates. Note that to enable collisions with Area3D, the boolean parameter collide_with_areas must be set to 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

To avoid self-intersection, the intersect_ray() parameters object can take an array of exceptions via its exclude property. This is an example of how to use it from a CharacterBody2D or any other collision object node:

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가 포함될 수 있습니다.

충돌 마스크

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 intersect_ray() parameters object can also be supplied a collision mask. For example, to use the same mask as the parent body, use the collision_mask member variable. The array of exceptions can be supplied as the last argument as well:

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)

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 CollisionObject3D 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.

To cast a ray from the screen, you need a Camera3D node. A Camera3D can be in two projection modes: perspective and orthogonal. Because of this, both the ray origin and direction must be obtained. This is because origin changes in orthogonal mode, while normal changes in perspective mode:

../../_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()``에서 실행해야 한다는 점을 기억하십시오.