Files

264 lines
22 KiB
Markdown

---
id: physics
group: physics
group_title: Физика 2D
title: Физика 2D
---
# Физика 2D
Aego симулирует твёрдые 2D-тела собственным решателем. Тело — это узел с компонентом **«Тело 2D»**; форму ему задают коллайдеры на этом узле и его детях. Скрипты реагируют на контакты и опрашивают мир.
Физика работает во время игры и в `aego run`. В режиме правки ничего не движется, а панель «Сцена» показывает контуры коллайдеров и их проблемы.
Чтобы попробовать:
1. **Создать → Физика → Твёрдое тело** даёт падающий ящик.
2. **Создать → Физика → Статический коллайдер** даёт для него пол.
3. Нажмите «Играть».
---
id: physics/units
title: Единицы и оси
---
# Единицы и оси
Физика работает в единицах мира. Одна единица — это любой размер, который вы выберете для своей игры; лучше всего ведут себя формы от 0.1 до 10 единиц. `render.pixelsPerUnit` в `project.aego` используется только для размера импортированных спрайтов по умолчанию, `Camera2D.Zoom` по умолчанию и **«Подогнать под спрайт»**; физика его никогда не читает, поэтому его изменение не меняет того, как что-либо падает.
Оси совпадают с осями 2D-сцены: +X вправо, +Y вниз, положительные углы поворачивают по часовой стрелке. Гравитация по умолчанию — `(0, 9.81)`, она направлена вниз по экрану. Нормали и скорости в событиях и запросах используют те же оси, поэтому у пола под телом нормаль с отрицательным Y.
---
id: physics/components
title: Компоненты
---
# Компоненты
Эти компоненты делают узел частью симуляции. У каждого есть своя страница в справочнике со всеми полями.
| Компонент | Назначение |
|---|---|
| [«Тело 2D»](ref:physics.Body2D) | делает узел телом: `static`, `kinematic` или `dynamic` |
| [Прямоугольник](ref:physics.BoxCollider2D), [круг](ref:physics.CircleCollider2D), [капсула](ref:physics.CapsuleCollider2D), [многоугольник](ref:physics.PolygonCollider2D) и [«Коллайдер-линия 2D»](ref:physics.EdgeCollider2D) | формы |
| [«Соединение на расстоянии 2D»](ref:physics.DistanceJoint2D) | держит расстояние до тела `Target`, жёстко или как пружина |
| [«Шарнир 2D»](ref:physics.RevoluteJoint2D) | крепит к телу `Target` в точке `Anchor`, с необязательным ограничителем и мотором |
У каждого коллайдера есть `Offset`, `IsTrigger`, `Friction`, `Restitution` и `Density`.
- Капсулы лежат вдоль локальной оси X узла. `Length` включает закругления.
- Многоугольники выпуклые, с числом точек от 3 до 8.
- Коллайдеры-линии — это ломаные для геометрии уровня. `Loop` замыкает их. `OneSided` делает их твёрдыми только с той стороны, куда закручиваются точки: точки, идущие слева направо, тверды сверху.
`Type` тела определяет, как оно движется: `static` никогда не движется, `kinematic` движется по своей скорости и не замечает сил, `dynamic` реагирует на силы и контакты. Его скорость и то, активно ли оно, — состояние во время игры; оно перечислено на странице [«Тело 2D»](ref:physics.Body2D).
---
id: physics/hierarchy
title: Тела и иерархия
---
# Тела и иерархия
Коллайдер принадлежит ближайшему узлу с компонентом «Тело 2D» — самому узлу коллайдера или выше по иерархии. Поэтому одно тело можно собрать из нескольких дочерних узлов, у каждого свой коллайдер; масса, центр масс и инерция складываются из всех них. Коллайдер без тела выше становится отдельным статическим телом.
Позиция, поворот и равномерный масштаб коллайдера относительно его тела запекаются в форму. Прямоугольники и многоугольники принимают и неравномерный масштаб; кругам и капсулам нужен равномерный, иначе они сообщают [PHY001](ref:PHY001). Узлы под 3D-родителем не симулируются ([PHY005](ref:PHY005)).
Трансформация движущегося тела принадлежит физике. Записывайте её из `FixedUpdate` или через `Teleport`, и она применится как телепорт на следующем шаге с сохранением скорости. Запись из `Update` сообщается один раз для каждого тела ([PHY007](ref:PHY007)) и применяется на следующем шаге. Статические и кинематические тела следуют за своим узлом каждый шаг. Если во время игры перенести движущееся тело к другому родителю, оно создаётся заново с той же мировой позой и скоростью ([PHY006](ref:PHY006)).
---
id: physics/layers
title: Слои столкновений
---
# Слои
До 32 именованных слоёв и пары, которые сталкиваются, объявляются в `project.aego` или в **Настройках проекта → Физика**:
physics {
layers=[Default, Ground, Player, Enemy]
collide {
Player=[Ground, Enemy]
Enemy=[Ground, Player]
}
}
Матрица симметрична: `Player=[Enemy]` позволяет и врагам задевать игроков. Без блока `collide` каждый слой сталкивается со всеми остальными. Неизвестное имя слоя в `collide` — ошибка загрузки.
Тело хранит свой слой как индекс, поэтому при переименовании слоя все тела остаются на нём; если стереть имя, индекс сохраняется. Две формы сталкиваются, когда `Mask` каждого из тел принимает слой другого и матрица разрешает эту пару. Если во время игры изменить слой или маску, запрещённые ими контакты заканчиваются на следующем шаге.
---
id: physics/events
title: События
---
# События
Объявите метод с одним из этих имён в любом скрипте на узле тела или на узле коллайдера:
func OnCollisionEnter(other NodeRef, c physics.Contact)
func OnCollisionStay(other NodeRef, c physics.Contact)
func OnCollisionExit(other NodeRef, c physics.Contact)
func OnTriggerEnter(other NodeRef)
func OnTriggerExit(other NodeRef)
Событие получают оба участника, каждый со своей стороны. `other` — узел другого тела; `c.Normal` направлена от другого тела к получателю, поэтому пол под вами сообщает `c.Normal.Y < 0`. `c.SelfCollider` и `c.OtherCollider` называют узлы коллайдеров. События выполняются после шага физики, до того как `script.events_fixed` завершает фиксированный тик.
`OnCollisionStay` вызывается каждый шаг, пока два тела соприкасаются, и только если его обрабатывает какой-нибудь скрипт; лучше считать Enter и Exit. Exit приходит и тогда, когда тело удалено или выключено; `other.IsAlive()` тогда ложно.
Удалять узлы внутри обработчика безопасно: удаление происходит после того, как отработали все обработчики.
---
id: physics/scripting
title: Скрипты
---
# Скрипты
Скрипты двигают тела через компонент «Тело 2D» и спрашивают у мира, что где находится, через `aego/physics`.
```aes
body := node.Get<Body2D>()
v := body.Velocity
body.SetVelocity(Vec2{x * Speed, v.Y})
body.ApplyImpulse(JumpImpulse)
```
## Движение тела
- Силы и момент действуют во время следующего шага физики. Вызывайте `ApplyForce` и `ApplyTorque` из `FixedUpdate`.
- Импульсы меняют скорость сразу, с учётом массы. Они подходят для прыжков и ударов.
- `MoveTo` приводит кинематическое тело в позу к концу следующего шага. `Teleport` переносит тело без проверки пути и сохраняет его скорость.
Все методы перечислены на странице [«Тело 2D»](ref:physics.Body2D).
## Запросы
Лучи, проверки отрезком и проверки перекрытия не замечают собственное тело вызывающего и пропускают триггеры. Они работают только во время игры. Функции, которые заполняют срез `out`, записывают не больше `len(out)` элементов, поэтому задайте размер среза один раз через `append` и используйте его повторно.
Все функции перечислены на странице [aego/physics](ref:aego/physics).
---
id: physics/settings
title: Настройки
---
# Настройки
![Настройки проекта → Физика с предпросмотром гравитации и контактов](shots/settings-physics.png)
**Настройки проекта → Физика** правят блок `physics` в `project.aego`: гравитацию, подшаги, итерации, жёсткость контактов, пороги сна, слои и матрицу. Пустое или нулевое поле берёт значение движка по умолчанию, оно показано серым. Подшаги и оба числа итераций принимают значения от 1 до 64. Изменения применяются при следующем запуске игры.
Высоким свободно стоящим стопкам и быстрым моторам нужен жёсткий профиль: `substeps=8` и `contactHertz=60`, примерно вдвое дороже для решателя. При стандартных 30 Гц и 4 подшагах свободная колонна выдерживает примерно двенадцать ящиков; кладка со смещением, как у кирпичей, стоит гораздо выше.
---
id: physics/editor
title: Инструменты редактора
---
# Инструменты редактора
Редактор рисует коллайдеры, контакты и силы и позволяет менять форму коллайдеров маркерами. Эти инструменты работают в панели «Сцена», а некоторые — и поверх идущей игры.
![Контуры коллайдеров и контакты поверх панели «Игра» во время игры](shots/physics-debug.png)
## Маркеры
Выберите узел с коллайдером, чтобы перетаскивать его стороны, радиус, длину или вершины. Двойной щелчок по ребру многоугольника добавляет вершину; Alt-щелчок по вершине удаляет её. Соединения показывают точки крепления на обоих телах. Каждое перетаскивание — один шаг отмены.
**«Подогнать под спрайт»** в «Инспекторе» коллайдеров-прямоугольников, кругов и капсул подгоняет форму под спрайт узла.
## Оверлеи
- Меню **Физика** включает отладочный оверлей: формы, контакты, ограничивающие прямоугольники, соединения, острова и дерево широкой фазы. В режиме правки рисуются только формы.
- **Векторы** (`V` в панели «Сцена») рисуют скорость, угловую скорость, силы, импульсы контактов, гравитацию и поля скриптов с `@export(gizmo="vector")`. Перетащите кончик стрелки поля, чтобы изменить значение.
- **Игра → Оверлеи** рисует те же слои поверх идущей игры, с игровой камерой.
## Проблемы и профилирование
- **Проблемы** перечисляют ошибки коллайдеров и соединений со ссылкой на узел. Сломанная геометрия не даёт запустить игру.
- **Профилировщик → Физика** показывает тела, пары, острова и время каждого этапа во время игры.
---
id: physics/lifecycle
title: Жизненный цикл
---
# Жизненный цикл
Что происходит с телами, формами и событиями контактов, когда сцена меняется во время игры.
| Действие во время игры | Результат |
|---|---|
| Удалить узел тела | его формы покидают мир на следующем шаге, события Exit — на том же шаге |
| Удалить дочерний коллайдер | форма удаляется, масса и инерция пересчитываются |
| Выключить узел | все формы под ним выключаются с событиями Exit; при включении Enter приходит на следующем шаге |
| Удалить «Тело 2D» | дочерние коллайдеры становятся статическими телами |
| Удалить коллайдер | форма удаляется с событием Exit |
| Изменить слой или маску | запрещённые пары заканчиваются на том же шаге |
| Изменить флаг триггера или геометрию | контакты формы заканчиваются, новые начинаются на следующем шаге |
| Добавить тело или коллайдер | подхватывается на следующем шаге и начинает в покое |
| Перенести движущееся тело к другому родителю | создаётся заново с мировой позой и скоростью, [PHY006](ref:PHY006) |
| Горячая перезагрузка скриптов | мир продолжает работать, обработчики привязываются заново по имени |
| Нативная пересборка | мир игры создаётся заново вместе с редактором |
| Остановить | мир уничтожается без событий Exit |
---
id: physics/world
title: Мир
---
# Мир
Физический мир продвигает все тела вместе с фиксированной частотой, по умолчанию 60 раз в секунду, и никогда — по времени кадра. Эта страница описывает, как он двигает тела и что значат параметры материалов.
## Шаги
Каждый шаг один раз находит контакты, а затем решает их в подшагах, по умолчанию в 4. Подшаг применяет гравитацию, расталкивает перекрывающиеся формы, двигает тела и гасит толчок, чтобы он не превратился в отскок. Больше подшагов — жёстче контакты; см. [Настройки](ref:physics/settings).
Тело проходит за шаг не больше заданного расстояния и угла. Маленьким быстрым телам стоит включить `Bullet`: их контакты заглядывают вперёд на расстояние, которое они проходят за один шаг.
Каждое тело и каждая форма проверяются при создании. Недопустимые числа, отрицательные размеры, вогнутые многоугольники и отрезки нулевой длины отклоняются с ошибкой, и мир остаётся прежним.
## Материалы
| Параметр | Значение | Когда соприкасаются две формы |
|---|---|---|
| `Friction` | 0 — лёд, 1 — резина | `sqrt(a·b)` |
| `Restitution` | 0 — остановка, 1 — отскок с полной скоростью | `max(a, b)` |
| `Density` | масса на квадратную единицу | — |
Очень медленные отскоки не восстанавливаются, поэтому тела в покое остаются в покое.
## Сон
Группа соприкасающихся тел засыпает, когда все они какое-то время двигались медленно. Группу будит контакт с активным телом, сила, импульс, телепорт или новое соединение.
## Детерминизм
Одна и та же игра на том же процессоре с тем же вводом каждый раз даёт один и тот же результат. На этом держится перемотка игры в редакторе.
---
id: physics/pitfalls
title: Частые ошибки
---
# Частые ошибки
Это ошибки, из-за которых физика чаще всего ощущается неправильной. Для каждой сказано, что вы видите и что нужно изменить.
## Динамическое тело дрожит при движении
Если двигать динамическое тело через `SetPosition` из `Update`, вы боретесь с решателем. Используйте `SetVelocity`, силы или `Teleport` из `FixedUpdate`.
## Прыжок иногда теряется
Читайте действие `input.Pressed("Jump")` прямо в `FixedUpdate`. Нажатие сохраняется до первого фиксированного шага, даже если в кадре отрисовки не было ни одного шага, и не повторяется в следующих шагах. `input.Down` означает удержание, а не новое нажатие: это не замена событию прыжка. Если другой контекст ввода поглощает действие, проверьте его и назначения клавиш. См. [Ввод: кадры и фиксированные шаги](ref:input).
## Коллайдер-круг сообщает о проблеме
Кругам и капсулам нужен равномерный масштаб. Меняйте поля коллайдера, а не растягивайте узел.
## Объекты проваливаются сквозь тонкий пол
Отметьте маленькие быстрые тела как `Bullet` или сделайте пол толще.
## Высокие башни шатаются
Используйте жёсткий профиль из раздела [Настройки](ref:physics/settings), сдвигайте ящики, как кирпичи, или соберите башню как одно тело.
## Ничего не сталкивается
Проверьте маски обоих тел и матрицу слоёв, а также то, что узел 2D и не находится под 3D-родителем.
## Движение кажется невесомым
Гравитация 9.81 предполагает, что единица — это примерно метр. Спрайт 64 пикселя при масштабе 64 — это одна единица.