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

O Godot armazena todas as informações de jogo de baixo nível em servidores, enquanto a cena é apenas um frontend. Como tal, o ray casting é geralmente uma tarefa de nível mais baixo. Para raycasts simples, nós como RayCast3D e RayCast2D funcionarão, pois eles retornam a cada quadro qual é o resultado de um raycast.

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

No mundo da física, o Godot armazena todas as informações de colisão e física de baixo nível em um espaço (space). O espaço 2d atual (para física 2D) pode ser obtido acessando CanvasItem.get_world_2d().space. Para 3D, é Node3D.get_world_3d().space.

O RID do espaço resultante pode ser usado em PhysicsServer3D e PhysicsServer2D, respectivamente para 3D e 2D.

Acessando o espaço

A física do Godot roda por padrão na mesma thread que a lógica do jogo, mas pode ser configurada para rodar em uma thread separada para funcionar com mais eficiência. Devido a isso, o único momento em que acessar o espaço é seguro é durante o callback Node._physics_process(). Acessá-lo de fora desta função pode resultar em um erro devido ao espaço estar bloqueado (locked).

Para realizar consultas no espaço de física, o PhysicsDirectSpaceState2D e o PhysicsDirectSpaceState3D devem ser usados.

Use o seguinte código em 2D:

func _physics_process(delta):
    var space_rid = get_world_2d().space
    var space_state = PhysicsServer2D.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_3d().direct_space_state

Consulta de Raycast

Para realizar uma consulta de raycast 2D, o método PhysicsDirectSpaceState2D.intersect_ray() pode ser usado. Por exemplo:

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)

O resultado é um dicionário. Se o raio não atingir nada, o dicionário estará vazio. Se atingir algo, conterá informações de colisão:

if result:
    print("Hit at point: ", result.position)

O dicionário result quando ocorre uma colisão contém os seguintes dados:

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

Os dados são semelhantes no espaço 3D, usando coordenadas Vector3. Note que para ativar colisões com o Area3D, o parâmetro booleano collide_with_areas deve ser definido como 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)

Exceções de colisão

Um caso de uso comum para ray casting (lançamento de raio) é permitir que um personagem colete dados sobre o mundo ao seu redor. Um problema com isso é que o mesmo personagem tem um colisor, então o raio detectará apenas o colisor do seu pai, como mostrado na imagem a seguir:

../../_images/raycast_falsepositive.webp

Para evitar a autointerseção, o objeto de parâmetros de intersect_ray() pode receber uma lista de exceções através de sua propriedade exclude. Este é um exemplo de como usá-lo a partir de um CharacterBody2D ou de qualquer outro nó de objeto de colisão:

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)

A matriz de exceções pode conter objetos ou RIDs.

Máscara de Colisão

Embora o método de exceções funcione bem para excluir o corpo pai, ele se torna muito inconveniente se você precisar de uma lista de exceções grande e/ou dinâmica. Neste caso, é muito mais eficiente usar o sistema de camadas/máscaras de colisão.

O objeto de parâmetros de intersect_ray() também pode receber uma máscara de colisão. Por exemplo, para usar a mesma máscara do corpo pai, use a variável de membro collision_mask. A matriz de exceções também pode ser fornecida como o último argumento:

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)

Veja Exemplo de código para detalhes sobre como definir a máscara de colisão.

Projeção de raios 3D a partir da tela

Lançar um raio da tela para o espaço físico 3D é útil para a seleção de objetos. Não há muita necessidade de fazer isso porque CollisionObject3D tem um sinal 'input_event' que avisará quando foi clicado, mas caso haja algum desejo de fazer isso manualmente, aqui está como.

Para lançar um raio a partir da tela, você precisa de um nó Camera3D. Uma Camera3D 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. Isso ocorre porque a origin muda no modo ortogonal, enquanto a normal muda no modo perspectiva:

../../_images/raycast_projection.png

To obtê-lo usando uma câmera, o seguinte código pode ser usado:

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

Lembre-se de que durante o _input(), o espaço pode estar travado, portanto, na prática, esta consulta deve ser executada no _physics_process().