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.
Checking the stable version of the documentation...
Проміння
Вступ
Одним із найпоширеніших завдань у розробці ігор є кидання променя (або об’єкта спеціальної форми) і перевірка того, на що він потрапляє. Це забезпечує складну поведінку, AI тощо. Цей підручник пояснює, як це зробити у 2D та 3D.
Godot зберігає всю інформацію про гру низького рівня на серверах, тоді як сцена є лише інтерфейсом. Таким чином, кастинг променів, як правило, є завданням нижчого рівня. Для простих raycastів працюватимуть такі вузли, як RayCast3D і RayCast2D, оскільки вони повертають кожен кадр результату raycast.
Однак багато разів відведення променів має бути більш інтерактивним процесом, тому має існувати спосіб зробити це за допомогою коду.
Простір
У світі фізики Godot зберігає всю інформацію про зіткнення та фізику низького рівня в простір. Поточний двовимірний простір (для 2D фізики) можна отримати, звернувшись до CanvasItem.get_world_2d().space. Для 3D це Node3D.get_world_3d().space.
Отриманий простір RID можна використовувати в PhysicsServer3D і PhysicsServer2D відповідно для 3D і 2D.
Доступ до простору
Фізика Godot запускається за замовчуванням у тому самому потоці, що й логіка гри, але її можна налаштувати на запуск в окремому потоці, щоб працювати ефективніше. Через це єдиний час, коли доступ до простору є безпечним, це під час зворотного виклику Node._physics_process(). Доступ до нього поза цією функцією може призвести до помилки через заблокований простір.
Для виконання запитів до фізичного простору необхідно використовувати PhysicsDirectSpaceState2D і PhysicsDirectSpaceState3D.
Використовуйте наступний код у 2D:
func _physics_process(delta):
var space_rid = get_world_2d().space
var space_state = PhysicsServer2D.space_get_direct_state(space_rid)
public override void _PhysicsProcess(double delta)
{
var spaceRid = GetWorld2D().Space;
var spaceState = PhysicsServer2D.SpaceGetDirectState(spaceRid);
}
Або пряміше:
func _physics_process(delta):
var space_state = get_world_2d().direct_space_state
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld2D().DirectSpaceState;
}
І в 3D:
func _physics_process(delta):
var space_state = get_world_3d().direct_space_state
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld3D().DirectSpaceState;
}
Запит Raycast
Для виконання запиту 2D raycast можна використовувати метод PhysicsDirectSpaceState2D.intersect_ray(). Наприклад:
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)
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld2D().DirectSpaceState;
// use global coordinates, not local to node
var query = PhysicsRayQueryParameters2D.Create(Vector2.Zero, new Vector2(50, 100));
var result = spaceState.IntersectRay(query);
}
Результат – словник. Якщо промінь ні в що не влучив, словник буде порожнім. Якщо він щось вдарив, він міститиме інформацію про зіткнення:
if result:
print("Hit at point: ", result.position)
if (result.Count > 0)
{
GD.Print("Hit at point: ", result["position"]);
}
Словник result містить такі дані, коли виникає колізія:
{
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
}
Дані схожі в 3D-просторі з використанням координат Vector3. Зверніть увагу, щоб увімкнути зіткнення з Area3D, логічний параметр collide_with_areas повинен бути встановлений на 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)
private const int RayLength = 1000;
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld3D().DirectSpaceState;
var cam = GetNode<Camera3D>("Camera3D");
var mousePos = GetViewport().GetMousePosition();
var origin = cam.ProjectRayOrigin(mousePos);
var end = origin + cam.ProjectRayNormal(mousePos) * RayLength;
var query = PhysicsRayQueryParameters3D.Create(origin, end);
query.CollideWithAreas = true;
var result = spaceState.IntersectRay(query);
}
Винятки колізій
Загальний випадок використання відкидання променів полягає в тому, щоб персонаж міг збирати дані про навколишній світ. Однією з проблем є те, що той самий персонаж має колайдер, тому промінь визначить лише колайдер свого батька, як показано на наступному зображенні:
Щоб уникнути самоперетину, об’єкт параметрів intersect_ray() може приймати масив винятків через свою властивість exclude. Це приклад того, як використовувати його з CharacterBody2D або будь-якого іншого вузла об’єкта зіткнення:
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)
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld2D().DirectSpaceState;
var query = PhysicsRayQueryParameters2D.Create(globalPosition, playerPosition);
query.Exclude = [GetRid()];
var result = spaceState.IntersectRay(query);
}
}
Масив винятків може містити об’єкти або RID.
Режим перешкоди
Хоча метод винятків добре працює для виключення батьківського тіла, він стає дуже незручним, якщо вам потрібен великий та/або динамічний список винятків. У цьому випадку набагато ефективніше використовувати систему шар/маска зіткнення.
Об'єкту параметрів intersect_ray() також можна надати маску зіткнення. Наприклад, щоб використовувати ту саму маску, що й у батьківського тіла, використовуйте змінну-член collision_mask. Масив винятків також можна надати як останній аргумент:
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)
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
public override void _PhysicsProcess(double delta)
{
var spaceState = GetWorld2D().DirectSpaceState;
var query = PhysicsRayQueryParameters2D.Create(globalPosition, targetPosition,
CollisionMask, [GetRid()]);
var result = spaceState.IntersectRay(query);
}
}
Дивіться Приклад коду, щоб дізнатися більше про те, як встановити маску зіткнення.
3D-трансляція променів з екрана
Перекидання променя від екрана до простору 3D фізики корисне для вибору об’єктів. У цьому немає особливої потреби, тому що CollisionObject3D має сигнал «input_event», який повідомить вам, коли на ньому клацнули, але якщо є бажання зробити це вручну, ось як.
Щоб відкинути промінь з екрана, вам потрібен вузол Camera3D. Camera3D може працювати в двох режимах проекції: перспективному та ортогональному. Через це необхідно визначити як початок, так і напрямок променя. Це пояснюється тим, що origin змінюється в ортогональному режимі, тоді як normal змінюється в режимі перспективи:
Щоб отримати його за допомогою камери, можна використати такий код:
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
private const float RayLength = 1000.0f;
public override void _Input(InputEvent @event)
{
if (@event is InputEventMouseButton eventMouseButton && eventMouseButton.Pressed && eventMouseButton.ButtonIndex == MouseButton.Left)
{
var camera3D = GetNode<Camera3D>("Camera3D");
var from = camera3D.ProjectRayOrigin(eventMouseButton.Position);
var to = from + camera3D.ProjectRayNormal(eventMouseButton.Position) * RayLength;
}
}
Пам’ятайте, що під час _input() простір може бути заблоковано, тому на практиці цей запит слід виконувати в _physics_process().