Проміння

Вступ

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

Або пряміше:

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

І в 3D:

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

Запит 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)

Результат – словник. Якщо промінь ні в що не влучив, словник буде порожнім. Якщо він щось вдарив, він міститиме інформацію про зіткнення:

if result:
    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)

Винятки колізій

Загальний випадок використання відкидання променів полягає в тому, щоб персонаж міг збирати дані про навколишній світ. Однією з проблем є те, що той самий персонаж має колайдер, тому промінь визначить лише колайдер свого батька, як показано на наступному зображенні:

../../_images/raycast_falsepositive.webp

Щоб уникнути самоперетину, об’єкт параметрів 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)

Масив винятків може містити об’єкти або 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)

Дивіться Приклад коду, щоб дізнатися більше про те, як встановити маску зіткнення.

3D-трансляція променів з екрана

Перекидання променя від екрана до простору 3D фізики корисне для вибору об’єктів. У цьому немає особливої потреби, тому що CollisionObject3D має сигнал «input_event», який повідомить вам, коли на ньому клацнули, але якщо є бажання зробити це вручну, ось як.

Щоб відкинути промінь з екрана, вам потрібен вузол Camera3D. Camera3D може працювати в двох режимах проекції: перспективному та ортогональному. Через це необхідно визначити як початок, так і напрямок променя. Це пояснюється тим, що origin змінюється в ортогональному режимі, тоді як normal змінюється в режимі перспективи:

../../_images/raycast_projection.png

Щоб отримати його за допомогою камери, можна використати такий код:

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

Пам’ятайте, що під час _input() простір може бути заблоковано, тому на практиці цей запит слід виконувати в _physics_process().