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...
Вступ до курсу фізики
При розробці ігор часто потрібно знати, коли два об'єкти в грі перетинаються або контактують. Це називається виявленням зіткнень. Коли виявлено зіткнення, ви зазвичай хочете, щоб щось сталося. Це називається реакція на зіткнення.
Godot пропонує низку об'єктів зіткнень у 2D та 3D, щоб забезпечити як виявлення зіткнень, так і реакцію на них. Спроба вирішити, який з них використовувати для вашого проекту, може бути заплутаною. Ви можете уникнути проблем і спростити розробку, якщо зрозумієте, як працює кожен з них і які їхні переваги та недоліки.
У цьому посібнику ви дізнаєтесь:
Чотири типи об'єктів зіткнення Godot
Як працює кожен об'єкт колізії
Коли і чому варто обирати один тип замість іншого
Примітка
У прикладах цього документу використовуються 2D-об'єкти. Кожен 2D-фізичний об'єкт і форма зіткнення має прямий еквівалент у 3D, і в більшості випадків вони працюють майже так само.
Попередження
Physics in Godot, regardless of physics engine, is not deterministic, the nature of physics engine determinism is very complex and has to do with many factors, this means physics is not guaranteed to run the same way for seemingly identical situations.
Об'єкти зіткнення
Godot пропонує чотири типи об'єктів зіткнення, які розширюють CollisionObject2D. Останні три, перелічені нижче, є фізичними тілами і додатково розширюють PhysicsBody2D.
- Area2D
Вузли
Area2Dзабезпечують виявлення та вплив. Вони можуть виявляти, коли об'єкти накладаються один на одного, і можуть випромінювати сигнали, коли тіла входять або виходять. ВузолArea2Dтакож можна використовувати для перевизначення фізичних властивостей, таких як гравітація або демпфування, у визначеній області.
- StaticBody2D
Статичне тіло – це тіло, яке не переміщується фізичним двигуном. Він бере участь у виявленні зіткнення, але не рухається у відповідь на зіткнення. Найчастіше вони використовуються для об’єктів, які є частиною середовища або яким не потрібна динамічна поведінка.
- RigidBody2D
Це вузол, який реалізує змодельовану 2D фізику. Ви не керуєте
RigidBody2Dнапряму, натомість ви застосовуєте до нього сили (гравітація, імпульси тощо), і фізичний механізм обчислює результуючий рух. Read more about using rigid bodies.
- CharacterBody2D
Тіло, яке забезпечує виявлення зіткнень, але не має фізики. Усі реакції на рух і зіткнення мають бути реалізовані в коді.
Матеріал з фізики
Статичні тіла та тверді тіла можна налаштувати на використання PhysicsMaterial. Це дозволяє регулювати тертя та відскок об’єкта, а також встановлювати, чи він абсорбуючий та/або грубий.
Форми зіткнень
Фізичне тіло може містити будь-яку кількість об’єктів Shape2D як дітей. Ці форми використовуються для визначення меж зіткнення об’єкта та виявлення контакту з іншими об’єктами.
Примітка
Щоб виявити зіткнення, принаймні один Shape2D має бути призначений об’єкту.
Найпоширенішим способом призначення форми є додавання CollisionShape2D або CollisionPolygon2D як дочірнього об’єкта. Ці вузли дозволяють малювати фігуру безпосередньо в робочій області редактора.
Важливо
Будьте обережні, щоб ніколи не масштабувати форми зіткнень у редакторі. Властивість «Масштаб» в інспекторі має залишатися (1, 1). Змінюючи розмір форми зіткнення, ви завжди повинні використовувати маркери розміру, не маркери масштабу Node2D. Масштабування фігури може призвести до неочікуваної поведінки зіткнення.
Зворотний виклик фізичного процесу
Фізична система працює з фіксованою частотою (за замовчуванням 60 ітерацій на секунду). Ця частота зазвичай відрізняється від частоти кадрів, яка коливається залежно від того, що відтворюється, і доступних ресурсів.
Важливо, щоб увесь код, пов'язаний з фізикою, виконувався з цією фіксованою швидкістю. Тому Godot розрізняє between physics and idle processing. Код, який виконується кожен кадр, називається холостою обробкою, а код, який виконується на кожному фізичному тику, називається фізичною обробкою. Godot надає два різних способи зворотного виклику, по одному для кожної з цих швидкостей обробки.
Зворотний виклик Node._physics_process() викликається перед кожним кроком фізики. Тут слід запускати будь-який код, який потребує доступу до властивостей тіла. Цьому методу буде передано параметр delta, який є числом з плаваючою комою, що дорівнює часу, який минув у секундах з моменту останнього кроку. Якщо використовується стандартна частота оновлення фізики 60 Гц, вона зазвичай дорівнюватиме 0,01666... (але не завжди, див. нижче).
Примітка
Рекомендується завжди використовувати параметр дельта, коли це необхідно для ваших фізичних розрахунків, щоб гра поводилася правильно, якщо ви змінюєте швидкість оновлення фізики або якщо пристрій гравця не може встигати.
Шари та маски зіткнення
Однією з найпотужніших, але часто неправильно зрозумілих функцій зіткнень є система рівня зіткнень. Ця система дозволяє будувати складні взаємодії між різноманітними об’єктами. Ключовими поняттями є шари та маски. Кожен CollisionObject2D має 32 різні фізичні рівні, з якими він може взаємодіяти.
Давайте по черзі розглянемо кожну з властивостей:
- Collision_layer
Це описує шари, в яких з’являється об’єкт. За замовчуванням усі тіла знаходяться на шарі
1.
- Collision_mask
Це описує, які шари тіло буде сканувати на предмет зіткнень. Якщо об’єкта немає в одному з шарів маски, тіло його проігнорує. За замовчуванням усі тіла сканують шар
1.
Ці властивості можна налаштувати за допомогою коду або редагуючи їх в інспекторі.
Відстежувати, для чого ви використовуєте кожен шар, може бути важко, тому вам може бути корисно призначити імена шарам, які ви використовуєте. Назви можна призначити в меню Налаштування проекту > Назви шарів > 2D Physics.
Приклад GUI
У вашій грі є чотири типи вузлів: стіни, гравець, ворог і монета. І гравець, і ворог повинні стикатися зі стінами. Вузол Player повинен виявляти зіткнення як з Enemy, так і з Coin, але Enemy і Coin повинні ігнорувати один одного.
Почніть із назви шарів 1–4 «стіни», «гравець», «вороги» та «монети» та розмістіть кожен тип вузла у відповідному шарі за допомогою властивості «Шар». Потім установіть для кожного вузла властивість «Маска», вибравши шари, з якими він має взаємодіяти. Наприклад, параметри програвача виглядатимуть так:
Приклад коду
У викликах функцій шари вказуються як бітова маска. Якщо функція вмикає всі шари за замовчуванням, маску шару буде задано як 0xffffffff. Ваш код може використовувати двійкову, шістнадцяткову чи десяткову систему запису для масок шарів, залежно від ваших уподобань.
Кодовий еквівалент наведеного вище прикладу, де були ввімкнені шари 1, 3 та 4, буде таким:
# Example: Setting mask value for enabling layers 1, 3 and 4
# Binary - set the bit corresponding to the layers you want to enable (1, 3, and 4) to 1, set all other bits to 0.
# Note: Layer 32 is the first bit, layer 1 is the last. The mask for layers 4, 3 and 1 is therefore:
0b00000000_00000000_00000000_00001101
# (This can be shortened to 0b1101)
# Hexadecimal equivalent (1101 binary converted to hexadecimal).
0x000d
# (This value can be shortened to 0xd.)
# Decimal - Add the results of 2 to the power of (layer to be enabled - 1).
# (2^(1-1)) + (2^(3-1)) + (2^(4-1)) = 1 + 4 + 8 = 13
#
# We can use the `<<` operator to shift the bit to the left by the layer number we want to enable.
# This is a faster way to multiply by powers of 2 than `pow()`.
# Additionally, we use the `|` (binary OR) operator to combine the results of each layer.
# This ensures we don't add the same layer multiple times, which would behave incorrectly.
(1 << 1 - 1) | (1 << 3 - 1) | (1 << 4 - 1)
# The above can alternatively be written as:
# pow(2, 1 - 1) + pow(2, 3 - 1) + pow(2, 4 - 1)
Ви також можете встановлювати біти незалежно, викликаючи set_collision_layer_value(layer_number, value) або set_collision_mask_value(layer_number, value) для будь-якого заданого CollisionObject2D наступним чином:
# Example: Setting mask value to enable layers 1, 3, and 4.
var collider: CollisionObject2D = $CollisionObject2D # Any given collider.
collider.set_collision_mask_value(1, true)
collider.set_collision_mask_value(3, true)
collider.set_collision_mask_value(4, true)
Експорт анотацій можна використовувати для експорту бітових масок у редакторі за допомогою зручного графічного інтерфейсу:
@export_flags_2d_physics var layers_2d_physics
Додаткові експортні анотації доступні для шарів візуалізації та навігації як у 2D, так і в 3D. Перегляньте Експорт бітових прапорців.
Площа2D
Вузли області забезпечують виявлення та вплив. Вони можуть виявляти, коли об’єкти перекриваються, і видавати сигнали, коли тіла входять або виходять. Області також можна використовувати для перевизначення фізичних властивостей, таких як сила тяжіння або демпфування, у визначеній області.
Є три основні способи використання Area2D:
Перевизначення фізичних параметрів (наприклад, сили тяжіння) у певному регіоні.
Виявлення того, коли інші тіла входять або виходять з регіону або які тіла зараз знаходяться в регіоні.
Перевірка інших областей на перекриття.
За замовчуванням області також отримують введення за допомогою миші та сенсорного екрана.
Static Body2D
Статичне тіло – це тіло, яке не переміщується фізичним двигуном. Він бере участь у виявленні зіткнення, але не рухається у відповідь на зіткнення. Однак він може передавати рух або обертання тілу, що стикається, так ніби воно рухається, використовуючи властивості constant_linear_velocity і constant_angular_velocity.
Вузли StaticBody2D найчастіше використовуються для об’єктів, які є частиною середовища або яким не потрібна динамічна поведінка.
Приклад використання для StaticBody2D:
Платформи (включаючи рухомі платформи)
Конвеєрні стрічки
Стіни та інші перешкоди
Rigid Body2D
Це вузол, який реалізує змодельовану 2D фізику. Ви не керуєте RigidBody2D безпосередньо. Натомість ви прикладаєте до нього силу, і фізичний механізм обчислює результуючий рух, включаючи зіткнення з іншими тілами та реакції на зіткнення, такі як підстрибування, обертання тощо.
Ви можете змінити поведінку твердого тіла за допомогою таких властивостей, як «Маса», «Тертя» або «Відскок», які можна встановити в інспекторі.
На поведінку тіла також впливають властивості світу, встановлені в Налаштування проекту > Фізика або введення Area2D, яке перекриває глобальні фізичні властивості.
Коли тверде тіло знаходиться в стані спокою і деякий час не рухається, воно засинає. Спляче тіло діє як статичне тіло, і його сили не розраховуються фізичним механізмом. Тіло прокинеться під час застосування сил через зіткнення або через код.
Використання RigidBody2D
Однією з переваг використання твердого тіла є те, що багато поведінки можна отримати «безкоштовно» без написання коду. Наприклад, якщо ви робите гру в стилі «Angry Birds» з падаючими блоками, вам потрібно буде лише створити RigidBody2D і налаштувати їхні властивості. Укладання, падіння та підстрибування автоматично обчислюватимуться фізичним механізмом.
Однак, якщо ви хочете мати певний контроль над тілом, вам слід бути обережними: зміна position, linear_velocity або інших фізичних властивостей твердого тіла може призвести до неочікуваної поведінки. Якщо вам потрібно змінити будь-яку властивість, пов’язану з фізикою, вам слід використовувати зворотний виклик _integrate_forces() замість _physics_process(). У цьому зворотному виклику ви маєте доступ до PhysicsDirectBodyState2D тіла, що дозволяє безпечно змінювати властивості та синхронізувати їх із фізичною системою.
Наприклад, ось код космічного корабля в стилі "Астероїди":
extends RigidBody2D
var thrust = Vector2(0, -250)
var torque = 20000
func _integrate_forces(state):
if Input.is_action_pressed("ui_up"):
state.apply_force(thrust.rotated(rotation))
else:
state.apply_force(Vector2())
var rotation_direction = 0
if Input.is_action_pressed("ui_right"):
rotation_direction += 1
if Input.is_action_pressed("ui_left"):
rotation_direction -= 1
state.apply_torque(rotation_direction * torque)
using Godot;
public partial class Spaceship : RigidBody2D
{
private Vector2 _thrust = new Vector2(0, -250);
private float _torque = 20000;
public override void _IntegrateForces(PhysicsDirectBodyState2D state)
{
if (Input.IsActionPressed("ui_up"))
{
state.ApplyForce(_thrust.Rotated(Rotation));
}
else
{
state.ApplyForce(new Vector2());
}
var rotationDir = 0;
if (Input.IsActionPressed("ui_right"))
{
rotationDir += 1;
}
if (Input.IsActionPressed("ui_left"))
{
rotationDir -= 1;
}
state.ApplyTorque(rotationDir * _torque);
}
}
Зверніть увагу, що ми не встановлюємо властивості linear_velocity або angular_velocity безпосередньо, а скоріше застосовуємо сили (thrust та torque) до тіла та дозволяємо фізичному движку розрахувати результуючий рух.
Примітка
Коли тверде тіло переходить у сплячий стан, функція _integrate_forces() не викликається. Щоб перевизначити цю поведінку, вам потрібно буде підтримувати тіло в стані неспання, створюючи зіткнення, застосовуючи до нього силу або вимикаючи властивість can_sleep. Майте на увазі, що це може негативно вплинути на продуктивність.
Контактна звітність
За замовчуванням тверді тіла не відстежують контакти, оскільки для цього може знадобитися величезний обсяг пам’яті, якщо в сцені багато тіл. Щоб увімкнути звітування про контакти, установіть для властивості max_contacts_reported ненульове значення. Потім контакти можна отримати за допомогою PhysicsDirectBodyState2D.get_contact_count() і пов’язаних функцій.
Моніторинг контактів за допомогою сигналів можна ввімкнути за допомогою властивості contact_monitor. Перегляньте RigidBody2D список доступних сигналів.
CharacterBody2D
CharacterBody2D тіла виявляють зіткнення з іншими тілами, але на них не впливають такі фізичні властивості, як сила тяжіння чи тертя. Натомість користувач має керувати ними за допомогою коду. Фізичний механізм не рухатиме тіло персонажа.
Під час переміщення тіла персонажа не слід безпосередньо встановлювати його положення. Замість цього ви використовуєте методи move_and_collide() або move_and_slide(). Ці методи переміщують тіло по заданому вектору, і воно миттєво зупиняється, якщо буде виявлено зіткнення з іншим тілом. Після зіткнення тіла будь-яка реакція на зіткнення повинна бути закодована вручну.
Реакція на зіткнення символів
Після зіткнення ви можете захотіти, щоб тіло відскочило, ковзало вздовж стіни або змінило властивості об’єкта, якого воно вдарило. Спосіб обробки реакції на зіткнення залежить від методу, який ви використали для переміщення CharacterBody2D.
move_and_collide
Під час використання move_and_collide() функція повертає об’єкт KinematicCollision2D, який містить інформацію про зіткнення та тіло, що стикається. Ви можете використовувати цю інформацію для визначення відповіді.
Наприклад, якщо ви хочете знайти точку в просторі, де відбулося зіткнення:
extends PhysicsBody2D
var velocity = Vector2(250, 250)
func _physics_process(delta):
var collision_info = move_and_collide(velocity * delta)
if collision_info:
var collision_point = collision_info.get_position()
using Godot;
public partial class Body : PhysicsBody2D
{
private Vector2 _velocity = new Vector2(250, 250);
public override void _PhysicsProcess(double delta)
{
var collisionInfo = MoveAndCollide(_velocity * (float)delta);
if (collisionInfo != null)
{
var collisionPoint = collisionInfo.GetPosition();
}
}
}
Або відскочити від предмета, що стикається:
extends PhysicsBody2D
var velocity = Vector2(250, 250)
func _physics_process(delta):
var collision_info = move_and_collide(velocity * delta)
if collision_info:
velocity = velocity.bounce(collision_info.get_normal())
using Godot;
public partial class Body : PhysicsBody2D
{
private Vector2 _velocity = new Vector2(250, 250);
public override void _PhysicsProcess(double delta)
{
var collisionInfo = MoveAndCollide(_velocity * (float)delta);
if (collisionInfo != null)
{
_velocity = _velocity.Bounce(collisionInfo.GetNormal());
}
}
}
move_and_slide
Ковзання є загальною реакцією на зіткнення; Уявіть собі гравця, який рухається вздовж стін у грі «згори вниз» або бігає вгору та вниз по схилах у платформері. Хоча цю відповідь можна закодувати самостійно після використання move_and_collide(), move_and_slide() надає зручний спосіб реалізувати рух ковзання без написання коду.
Попередження
move_and_slide() автоматично включає часовий крок у свій розрахунок, тому вам не слід множити вектор швидкості на дельту. Це не стосується гравітації, оскільки це прискорення, залежить від часу та потребує масштабування дельта.
Наприклад, використовуйте наступний код, щоб створити персонажа, який може ходити по землі (включаючи схили) і стрибати, стоячи на землі:
extends CharacterBody2D
var run_speed = 350
var jump_speed = -1000
var gravity = 2500
func get_input():
velocity.x = 0
var right = Input.is_action_pressed('ui_right')
var left = Input.is_action_pressed('ui_left')
var jump = Input.is_action_just_pressed('ui_select')
if is_on_floor() and jump:
velocity.y = jump_speed
if right:
velocity.x += run_speed
if left:
velocity.x -= run_speed
func _physics_process(delta):
velocity.y += gravity * delta
get_input()
move_and_slide()
using Godot;
public partial class Body : CharacterBody2D
{
private float _runSpeed = 350;
private float _jumpSpeed = -1000;
private float _gravity = 2500;
private void GetInput()
{
var velocity = Velocity;
velocity.X = 0;
var right = Input.IsActionPressed("ui_right");
var left = Input.IsActionPressed("ui_left");
var jump = Input.IsActionPressed("ui_select");
if (IsOnFloor() && jump)
{
velocity.Y = _jumpSpeed;
}
if (right)
{
velocity.X += _runSpeed;
}
if (left)
{
velocity.X -= _runSpeed;
}
Velocity = velocity;
}
public override void _PhysicsProcess(double delta)
{
var velocity = Velocity;
velocity.Y += _gravity * (float)delta;
Velocity = velocity;
GetInput();
MoveAndSlide();
}
}
Перегляньте Кінематичний персонаж (2D) для отримання додаткової інформації про використання move_and_slide(), включаючи демонстраційний проект із детальним кодом.