Проміння
Вступ
Одним із найпоширеніших завдань у розробці ігор є кидання променя (або об’єкта спеціальної форми) і перевірка того, на що він потрапляє. Це забезпечує складну поведінку, 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"]);
}
Словник «результат», коли виникає колізія, містить такі дані:
{
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().