Up to date

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

Ray casting

Introducción

Una de las tareas más comunes en el desarrollo de juegos es lanzar un rayo (o un objeto con forma personalizada) y comprobar con qué choca. Esto permite que ocurran comportamientos complejos (IA, etcétera). Este tutorial explicará cómo hacer esto en 2D y 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.

Sin embargo, muchas veces el ray-casting debe ser un proceso más interactivo, así que es necesario que exista una forma de hacer esto por código.

Espacio

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.

Accediendo al espacio

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.

Usa el siguiente código en 2D:

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

O más directamente:

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

Y en 3D:

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

Consulta de Raycast

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)

El resultado es un diccionario. Si el rayo no le dio a nada, el diccionario estará vacío. Si chocó con algo, contendrá información de colisión:

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

El diccionario resultante result cuando ocurre una colisión contiene los siguientes datos:

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

Excepciones de colisión

Un caso de uso común de ray casting es un permitir que un personaje reúna datos sobre el mundo que lo rodea. Un problema que surge en estos casos es que ese mismo personaje tiene un Colisionador, por lo que el rayo sólo detectará el Colisionador de su padre, como se muestra en la siguiente imagen:

../../_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)

La matriz de excepciones puede contener objetos o identificadores relativos (RIDs).

Máscara de colisión

Mientras que el método de excepciones funciona bien para excluir el cuerpo padre, resulta muy incómodo si se necesita una lista grande y dinámica de las excepciones. En ese caso, es mucho más eficiente utilizar el sistema de máscaras/capas de colisión.

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)

Ver Ejemplo de código para detalles sobre cómo configurar la máscara de colisión.

Ray casting 3D desde la pantalla

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

Para obtenerlo mediante una cámara, se puede utilizar el siguiente código:

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

Recuerda que durante un input(), el espacio puede estar bloqueado, así que en la práctica esta consulta debe ser ejecutada en _physics_process().