pandemonium_engine_docs/03_usage/10_physics/05_ray_casting.md

200 lines
5.7 KiB
Markdown
Raw Normal View History

2023-01-12 20:49:14 +01:00
2024-04-29 21:20:12 +02:00
# Ray-casting
2024-04-29 21:20:12 +02:00
## Introduction
One of the most common tasks in game development is casting a ray (or
custom shaped object) and checking what it hits. This enables complex
behaviors, AI, etc. to take place. This tutorial will explain how to
do this in 2D and 3D.
2024-03-16 20:56:52 +01:00
Pandemonium stores all the low level game information in servers, while the
scene is just a frontend. As such, ray casting is generally a
lower-level task. For simple raycasts, node such as
2023-01-12 19:30:47 +01:00
`RayCast`
will work, as they will return every frame what the result of a raycast
is.
Many times, though, ray-casting needs to be a more interactive process
so a way to do this by code must exist.
2024-04-29 21:20:12 +02:00
## Space
2024-03-16 20:56:52 +01:00
In the physics world, Pandemonium stores all the low level collision and
physics information in a *space*. The current 2d space (for 2D Physics)
can be obtained by accessing
2023-01-12 19:30:47 +01:00
`CanvasItem.get_world_2d().space`.
For 3D, it's `Spatial.get_world().space`.
2023-01-12 19:30:47 +01:00
The resulting space `RID` can be used in
`PhysicsServer` and
`Physics2DServer` respectively for 3D and 2D.
2024-04-29 21:20:12 +02:00
## Accessing space
2024-03-16 20:56:52 +01:00
Pandemonium 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
2023-01-12 19:30:47 +01:00
`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
2023-01-12 19:30:47 +01:00
`Physics2DDirectSpaceState`
and `PhysicsDirectSpaceState`
must be used.
Use the following code in 2D:
2023-01-12 18:31:02 +01:00
gdscript GDscript
2023-01-12 18:31:02 +01:00
```
2024-05-04 14:08:00 +02:00
func _physics_process(delta):
var space_rid = get_world_2d().space
var space_state = Physics2DServer.space_get_direct_state(space_rid)
2023-01-12 18:31:02 +01:00
```
Or more directly:
2023-01-12 18:31:02 +01:00
gdscript GDScript
2023-01-12 18:31:02 +01:00
```
2024-05-04 14:08:00 +02:00
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
2023-01-12 18:31:02 +01:00
```
And in 3D:
2023-01-12 18:31:02 +01:00
gdscript GDScript
2023-01-12 18:31:02 +01:00
```
2024-05-04 14:08:00 +02:00
func _physics_process(delta):
var space_state = get_world().direct_space_state
2023-01-12 18:31:02 +01:00
```
2024-04-29 21:20:12 +02:00
## Raycast query
For performing a 2D raycast query, the method
2023-01-12 19:30:47 +01:00
`Physics2DDirectSpaceState.intersect_ray()`
may be used. For example:
2023-01-12 18:31:02 +01:00
gdscript GDScript
2023-01-12 18:31:02 +01:00
```
2024-05-04 14:08:00 +02:00
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))
2023-01-12 18:31:02 +01:00
```
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:
2023-01-12 18:31:02 +01:00
gdscript GDScript
2023-01-12 18:31:02 +01:00
```
2024-05-04 14:08:00 +02:00
if result:
print("Hit at point: ", result.position)
2023-01-12 18:31:02 +01:00
```
2023-01-12 19:43:03 +01:00
The `result` dictionary when a collision occurs contains the following
data:
2023-01-12 22:32:46 +01:00
```
2024-05-04 14:08:00 +02:00
{
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
}
2023-01-12 22:32:46 +01:00
```
The data is similar in 3D space, using Vector3 coordinates.
2024-04-29 21:20:12 +02:00
## Collision exceptions
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:
2023-01-12 20:16:00 +01:00
![](img/raycast_falsepositive.png)
2023-01-12 19:43:03 +01:00
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:
2023-01-12 18:31:02 +01:00
gdscript GDScript
2023-01-12 18:31:02 +01:00
```
2024-05-04 14:08:00 +02:00
extends KinematicBody2D
2024-05-04 14:08:00 +02:00
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
var result = space_state.intersect_ray(global_position, enemy_position, [self])
2023-01-12 18:31:02 +01:00
```
The exceptions array can contain objects or RIDs.
2024-04-29 21:20:12 +02:00
## Collision Mask
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.
2023-01-12 19:43:03 +01:00
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:
2023-01-12 18:31:02 +01:00
gdscript GDScript
2023-01-12 18:31:02 +01:00
```
2024-05-04 14:08:00 +02:00
extends KinematicBody2D
2024-05-04 14:08:00 +02:00
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)
2023-01-12 18:31:02 +01:00
```
2023-01-12 19:29:11 +01:00
See `doc_physics_introduction_collision_layer_code_example` for details on how to set the collision mask.
2024-04-29 21:20:12 +02:00
## 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
2023-01-12 19:30:47 +01:00
`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.
2023-01-12 19:30:47 +01:00
To cast a ray from the screen, you need a `Camera`
2023-01-12 19:43:03 +01:00
node. A `Camera` can be in two projection modes: perspective and
orthogonal. Because of this, both the ray origin and direction must be
2023-01-12 19:43:03 +01:00
obtained. This is because `origin` changes in orthogonal mode, while
`normal` changes in perspective mode:
2023-01-12 20:16:00 +01:00
![](img/raycast_projection.png)
To obtain it using a camera, the following code can be used:
2023-01-12 18:31:02 +01:00
gdscript GDScript
2023-01-12 18:31:02 +01:00
```
2024-05-04 14:08:00 +02:00
const ray_length = 1000
2024-05-04 14:08:00 +02:00
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
2023-01-12 18:31:02 +01:00
```
2023-01-12 20:57:31 +01:00
Remember that during `input()`, the space may be locked, so in practice
this query should be run in `physics_process()`.