Ray-casting

Introdução

Uma das tarefas mais comuns no desenvolvimento de um jogo é conjurar um raio (ou um objeto de formato personalizado) e verificar o que ele atinge. Isto permite comportamentos, IA, etc. complexos acontecerem. Este tutorial explicará como fazer isso em 2D e em 3D.

Godot armazena todas as informações de baixo nível em servidores, enquanto a cena é apenas uma interface. Assim, conjuração de um raio é geralmente uma tarefa de baixo nível. Para simples conjurações, nós como RayCast e RayCast2D funcionam, já que eles informam o resultado da conjuração a cada quadro.

Muitas vezes, entretanto, conjurar um raio precisa de um processo mais interativo, então deve existir uma maneira de fazer isso através de código.

Espaço

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 Spatial.get_world().space.

The resulting space RID can be used in PhysicsServer and Physics2DServer respectively for 3D and 2D.

Acessando o espaço

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 Physics2DDirectSpaceState and PhysicsDirectSpaceState must be used.

Use the following code in 2D:

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

Ou mais diretamente:

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

E em 3D:

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

Consulta de Raycast

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))

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)

The result dictionary when a collision occurs contains the following data:

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

Exceções de colisão

A common use case for ray casting is to enable a character to gather data about the world around it. One problem with this is that the same character has a collider, so the ray will only detect its parent's collider, as shown in the following image:

../../_images/raycast_falsepositive.png

To avoid self-intersection, the intersect_ray() function can take an optional third parameter which is an array of exceptions. This is an example of how to use it from a KinematicBody2D or any other collision object node:

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])

The exceptions array can contain objects or RIDs.

Máscara de Colisão

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)

See Exemplo de código for details on how to set the collision mask.

3D ray casting from screen

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.

Para converter um raio da tela, você precisa de um nó: ref: Camera <class_Camera>. Uma Camera pode estar em dois modos de projeção: perspectiva e ortogonal. Por causa disso, tanto a origem quanto a direção do raio devem ser obtidas. Isto é porque origin muda no modo ortogonal, enquanto normal muda no modo de perspectiva:

../../_images/raycast_projection.png

To obtain it using a camera, the following code can be used:

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

Remember that during _input(), the space may be locked, so in practice this query should be run in _physics_process().