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.

Проміння

Вступ

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

Словник 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)

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

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

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