406 lines
42 KiB
Markdown
406 lines
42 KiB
Markdown
---
|
||
id: animation
|
||
title: Анимация
|
||
---
|
||
# Анимация
|
||
|
||
Анимационный клип меняет со временем свойства узла и его дочерних узлов: кадры спрайта, цвета, трансформации, поля скриптов. [Проигрыватель анимации](ref:animation.AnimationPlayer) воспроизводит один клип и сообщает скриптам о его событиях.
|
||
|
||
Время клипа, игровые события и все дорожки, кроме позы трансформации, идут фиксированными шагами, как физика. Повтор того же ввода даёт те же кадры, события и хеш состояния при любой частоте кадров.
|
||
|
||
func OnAnimEvent(id int, payload float) {
|
||
if anim.EventName(id) == "Hit" {
|
||
node.Child("Sword").Get<Hitbox2D>().Pulse()
|
||
}
|
||
}
|
||
|
||
---
|
||
id: animation/clips
|
||
title: Клипы
|
||
---
|
||
# Клипы
|
||
|
||
Клип — это файл `.aeanim` в папке ассетов. У него есть длина в секундах, режим повтора (`once`, `loop`, `pingPong`) и дорожки:
|
||
|
||
anim version=1 length=0.6 loop=loop {
|
||
frames "" fps=10 {
|
||
sprites=[asset("…run_0"), asset("…run_1"), asset("…run_2")]
|
||
}
|
||
track "" prop="SpriteRenderer.tint" kind=color {
|
||
key t=0 v=[1, 1, 1, 1] interp=linear {}
|
||
key t=0.3 v=[1, 0.6, 0.6, 1] {}
|
||
}
|
||
track "Arm" prop="transform.rotation" kind=float32 authority=simulation {
|
||
key t=0 v=0 {}
|
||
key t=0.3 v=0.8 tangent=linear {}
|
||
}
|
||
event t=0.3 name="Hit" payload=1 {}
|
||
}
|
||
|
||
| Часть | Значение |
|
||
|---|---|
|
||
| метка `track` | узел в виде пути от узла проигрывателя; `""` — сам узел |
|
||
| `prop` | свойство, с путями как у твинов: `Component.Field`, `Component.Field.a`, `transform.position`, `script/Name.Field` |
|
||
| `kind` | тип значения: `float32`, `int32`, `bool`, `enum`, `vec2`, `vec3`, `vec4`, `color`, `rect`, `quat`, `assetRef` |
|
||
| `authority` | `presentation` (по умолчанию) или `simulation`, см. [Время и источник истины](ref:animation/time) |
|
||
| `onEnd` | `hold` оставляет последнее значение, когда клип заканчивается или останавливается, `restore` возвращает значение, которое было до запуска игры |
|
||
| `root=true` | только для `""` с `prop="transform.position"` и `kind=vec2`: дорожка не записывается; вместо этого «Аниматор» с «Движением корня» двигает узел на её изменение |
|
||
| `key` | `t`, `v` и `interp=bezier` (по умолчанию), `linear` или `step`; `tangent=auto`, `linear` или `free` с `in` и `out` |
|
||
| `frames` | кадры спрайта: `sprites` и `fps` или `durations`, `flipX` по желанию; превращается в ступенчатые дорожки на `SpriteRenderer.sprite` и `SpriteRenderer.flipX` |
|
||
| `event` | событие в момент `t` с объявленным `name`, необязательными `payload` и `mode` |
|
||
|
||
Числа, векторы и цвета интерполируются; повороты в виде кватернионов идут по короткой дуге; `int` переключается ступенями, если у дорожки нет `numeric=true`; `bool`, `enum` и ссылки на ассеты переключаются ступенями. Спрайты — это ассеты спрайтов, обычно из группы атласа.
|
||
|
||
Движок поставляется с четырьмя клипами для тестов и набросков: `engine/anim/run` — восемь кадров бегущей фигуры (с `engine/sprites/runner_0` по `_7`) с двумя событиями отображения `Footstep`; `engine/anim/bob` — покачивание `transform.rotation` в симуляции с игровым событием `Hit` на правом повороте; `engine/anim/jump` — поза бегуна в шаге; `engine/anim/hit` — один взмах `transform.rotation` за 0,3 с. Встроенный контроллер играет jump в состоянии Jump и hit в состоянии Hit.
|
||
|
||
Клип, который не импортируется, сохраняет свою последнюю исправную версию, а ошибка появляется в «Проблемах»: [ANM001](ref:ANM001) — дорожка или файл, которые импортёр не может прочитать, [ANM002](ref:ANM002) — ключ или событие за пределами длины, [ANM003](ref:ANM003) — необъявленное событие.
|
||
|
||
---
|
||
id: animation/player
|
||
title: Проигрыватель анимации
|
||
---
|
||
# Проигрыватель анимации
|
||
|
||
[Проигрыватель анимации](ref:animation.AnimationPlayer) воспроизводит свой «Клип» с начала игры, если включено «Запуск при старте». «Скорость» масштабирует время; отрицательная скорость воспроизводит назад. «Повтор» заменяет режим повтора клипа.
|
||
|
||
В «Попробовать» бегун воспроизводит встроенный клип бега, а маятник — отдельный клип покачивания. Нажмите R, чтобы повторить бег, или B, чтобы изменить направление; скрипты показывают события отображения и игровые события.
|
||
|
||
| Метод | |
|
||
|---|---|
|
||
| `Play(clip)` | запускает клип с начала; `nil` снова воспроизводит текущий клип |
|
||
| `Stop()` | завершает клип; дорожки `restore` получают старые значения, выполняется `Finished(-1)` |
|
||
| `Pause()`, `Resume()` | приостанавливает и продолжает |
|
||
| `Seek(t)` | переходит к моменту клипа, не вызывая событий между ними |
|
||
| `SetSpeed(s)` | меняет скорость |
|
||
|
||
| Сигнал | |
|
||
|---|---|
|
||
| `Finished(cycle int)` | закончился клип без повтора (`0`) или вызван `Stop` (`-1`) |
|
||
| `Event(id int, payload float)` | клип дошёл до события |
|
||
|
||
Команды вступают в силу на следующем фиксированном шаге, поэтому скрипт может вызывать их из Update. «Время», «Играет» и «Цикл» показывают состояние во время игры.
|
||
|
||
Дорожка, узел которой отсутствует ([ANM005](ref:ANM005)) или свойство которой не совпадает с её типом ([ANM001](ref:ANM001)), выключена; остальной клип воспроизводится. Когда клип импортируется снова с теми же дорожками, проигрыватели сохраняют своё место; с другими дорожками они останавливаются ([ANM008](ref:ANM008)).
|
||
|
||
---
|
||
id: animation/time
|
||
title: Время и источник истины
|
||
---
|
||
# Время и источник истины
|
||
|
||
Проигрыватель в игровом времени продвигается фиксированными шагами и стоит, пока игра на паузе. На каждом шаге он записывает свои дорожки по времени клипа этого шага. Одно исключение: дорожки трансформации 2D-узла с `authority=presentation` (по умолчанию) показываются в момент между двумя шагами, поэтому движение остаётся плавным при любой частоте кадров и не меняет позицию узла в игре.
|
||
|
||
| Источник | Для трансформации | Для других свойств |
|
||
|---|---|---|
|
||
| `presentation` | только рисуется; скрипты, физика и сохранения видят трансформацию без анимации | записывается фиксированными шагами |
|
||
| `simulation` | узел действительно движется; кинематическое тело двигается своей скоростью, поэтому везёт то, что на нём стоит | записывается фиксированными шагами |
|
||
|
||
Анимация и другие системы никогда не управляют одним свойством вместе:
|
||
|
||
- Твин свойства, которым управляет анимация, останавливается с `Completed(false)`.
|
||
- Дорожка трансформации узла с [«Движением 2D»](ref:gameplay.Motion2D) или вторая дорожка свойства, которым управляет другая дорожка, выключена ([ANM004](ref:ANM004)).
|
||
- На динамическом теле выключены все дорожки трансформации; на кинематическом теле выключена дорожка трансформации отображения; статическое тело предупреждает, что его телепортируют ([ANM006](ref:ANM006)).
|
||
|
||
Со «Шкалой времени» `unscaled` проигрыватель идёт в реальном времени и продолжает играть, пока игра на паузе, — для меню и другого отображения. Он не входит в состояние игры: дорожки симуляции выключены, а игровые события приходят как события отображения ([ANM007](ref:ANM007)).
|
||
|
||
---
|
||
id: animation/events
|
||
title: События
|
||
---
|
||
# События
|
||
|
||
События объявляются в **Настройках проекта → Анимация** или в `project.aego`:
|
||
|
||
animation {
|
||
event "Hit" payload=float {}
|
||
event "Footstep" {}
|
||
}
|
||
|
||
`Footstep` и `Hit` (данные `float`) объявлены движком для его встроенных клипов; проект может использовать их без объявления.
|
||
|
||
У события в клипе есть режим:
|
||
|
||
| Режим | Когда срабатывает |
|
||
|---|---|
|
||
| `gameplay` (по умолчанию) | на фиксированном шаге, который его пересекает, один раз за цикл, одинаково при каждом повторе |
|
||
| `presentation` | когда его пересекает отрисованное время; для звуков и эффектов; не во время повтора |
|
||
| `preview` | только в предпросмотре редактора |
|
||
|
||
Скрипты узла проигрывателя получают `OnAnimEvent(id int, payload float)`, а проигрыватель испускает свой сигнал `Event(id, payload)`. Событие в момент 0 срабатывает, когда клип начинается, и в каждом новом цикле; событие в конце срабатывает один раз за цикл. Шаг, который покрывает несколько циклов, вызывает каждое событие один раз за цикл, по порядку. `Seek` ничего не вызывает.
|
||
|
||
---
|
||
id: section/animation
|
||
title: Анимация
|
||
---
|
||
Блок `animation` в `project.aego` принадлежит модулю Animation.
|
||
|
||
## События анимации
|
||
|
||
Объявляйте имена событий проекта в блоке `animation`. `Footstep` и `Hit` встроены в движок; повторное объявление не заменяет встроенное событие. Имя каждого события проекта должно быть непустым и уникальным. Необязательный `payload` принимает `none`, `int`, `float` или `bool`; по умолчанию — `none`.
|
||
|
||
```aetext
|
||
animation {
|
||
event "Land" payload=float {}
|
||
event "OpenDoor" {}
|
||
}
|
||
```
|
||
|
||
В **Настройках проекта → Анимация** добавляйте, переименовывайте и удаляйте события проекта и выбирайте тип данных. Изменения остаются в черновике настроек до нажатия **Применить**; для черновика работают Undo и Redo. Новые объявления действуют при следующем запуске игры. При переименовании или удалении объявления клипы автоматически не меняются: обновите имена их событий отдельно.
|
||
|
||
Клипы ссылаются на эти имена. Скрипты получают `OnAnimEvent(id, payload)` и могут преобразовывать имена через `anim.EventName` и `anim.EventID`; см. [События анимации](ref:animation/events). Тот же блок можно редактировать и в `project.aego`.
|
||
|
||
---
|
||
id: animation/animator
|
||
title: Аниматор
|
||
---
|
||
# Аниматор
|
||
|
||
[Аниматор](ref:animation.Animator) воспроизводит состояния контроллера — файла `.aectrl`: у каждого состояния есть клип, а переходы переключают состояния, когда меняются параметры. Скрипты задают параметры; остальное делает граф.
|
||
|
||
В «Попробовать» используйте A/D для движения, Space для прыжка и J для удара. Скрипт задаёт параметры контроллера и показывает текущее состояние.
|
||
|
||
func FixedUpdate(dt float) {
|
||
m := node.Get<Motion2D>()
|
||
a := node.Get<Animator>()
|
||
a.SetFloat("Speed", math.Abs(m.Velocity.X))
|
||
a.SetBool("Grounded", m.Grounded)
|
||
if input.Pressed("Attack") {
|
||
a.SetTrigger("Attack")
|
||
}
|
||
}
|
||
|
||
| Метод | |
|
||
|---|---|
|
||
| `SetBool`, `SetInt`, `SetFloat(name, value)` | задаёт параметр; переходы читают его на следующем фиксированном шаге |
|
||
| `SetTrigger(name)`, `ResetTrigger(name)` | одноразовый параметр: его сбрасывает переход, который его использует, или конец этого шага |
|
||
| `GetBool`, `GetInt`, `GetFloat(name)` | читает параметр |
|
||
| `Play(state)` | переключает на состояние на следующем фиксированном шаге, без смешивания |
|
||
| `CrossFade(state, seconds)` | плавно переходит в состояние |
|
||
| `IsState(name) bool`, `StateName(index) string` | текущее состояние или имя состояния по индексу |
|
||
|
||
| Сигнал | |
|
||
|---|---|
|
||
| `StateEntered(state int)` | начался переход в состояние |
|
||
| `StateExited(state int)` | состояние перестало играть, в конце смешивания |
|
||
| `Event(id int, payload float)` | состояние дошло до события своего клипа |
|
||
|
||
«Состояние», «Время», «Прогресс» и «Смешивание» показывают состояние во время игры. **Создать → Геймплей → Персонаж платформера** приходит с «Аниматором» на встроенном контроллере `engine/anim/platformer`: Idle, Run, Jump и Hit, с параметрами Speed, Grounded и Attack.
|
||
|
||
Задавайте параметры в `FixedUpdate`, когда они берутся из состояния игры, как выше: граф идёт фиксированными шагами, поэтому видит те же значения при каждом повторе с любой частотой кадров.
|
||
|
||
## «Аниматор» и «Движение 2D» вместе
|
||
|
||
Пример платформера управляет игроком из одного `FixedUpdate`: контроллер движет, аниматор показывает. Оба читают один и тот же шаг, поэтому повтор с любой частотой кадров даёт те же состояния и те же удары.
|
||
|
||
func FixedUpdate(dt float) {
|
||
m := node.Get<Motion2D>()
|
||
m.Move(input.Axis("Move"))
|
||
if input.Pressed("Jump") {
|
||
m.Jump()
|
||
}
|
||
a := node.Get<Animator>()
|
||
a.SetFloat("Speed", math.Abs(m.Velocity.X))
|
||
a.SetBool("Grounded", m.Grounded)
|
||
a.SetFloat("VerticalSpeed", m.Velocity.Y)
|
||
if input.Pressed("Attack") {
|
||
a.SetTrigger("Attack")
|
||
}
|
||
}
|
||
|
||
Значения описывают предыдущий шаг: скрипты выполняются до того, как двигается «Движение 2D». Урон доходит до аниматора через сигнал, `Health.Damaged` → `SetTrigger("Hurt")`, а переход `any` с `interrupt=true` показывает его даже посреди смешивания. «Отразить по X» спрайта остаётся за скриптом: оно следует за вводом, а не за клипом.
|
||
|
||
В `examples/topdown` работа разделена иначе: «Машина состояний» решает, что делает враг (Patrol, Chase, Dead), а её обработчики только задают параметры «Аниматора» (`Chasing`, `Die`); клипы решают, как он выглядит.
|
||
|
||
---
|
||
id: animation/controller
|
||
title: Контроллеры
|
||
---
|
||
# Контроллеры
|
||
|
||
controller version=1 {
|
||
entry="Idle"
|
||
param "Speed" kind=float {}
|
||
param "Grounded" kind=bool default=true {}
|
||
param "Attack" kind=trigger {}
|
||
state "Idle" clip=asset("…idle") {}
|
||
state "Run" clip=asset("…run") speed=1.2 {}
|
||
state "Hit" clip=asset("…hit") loop=once events=both {}
|
||
transition "Idle" to="Run" duration=0.1 {
|
||
cond "Speed" op=gt value=0.1 {}
|
||
}
|
||
transition "Run" to="Idle" duration=0.1 {
|
||
cond "Speed" op=le value=0.1 {}
|
||
}
|
||
transition "any" to="Hit" interrupt=true {
|
||
cond "Attack" {}
|
||
}
|
||
transition "Hit" to="Idle" exitTime=1 duration=0.05 {}
|
||
}
|
||
|
||
| Часть | Значение |
|
||
|---|---|
|
||
| `param` | `kind` — это `bool`, `int`, `float` или `trigger`; `default` для первых трёх |
|
||
| `state` | `clip`, `speed`, `loop` (`once`, `loop`, `pingPong`; по умолчанию из клипа), `events` (см. ниже) |
|
||
| `entry` | состояние, с которого начинает аниматор |
|
||
| `transition "From" to="To"` | `"any"` в качестве источника уходит из любого состояния, кроме цели |
|
||
| `cond` | `op` — это `eq`, `ne`, `gt`, `ge`, `lt` или `le` со `value`; без `op` bool или триггер должен быть установлен |
|
||
| `duration` | секунды смешивания; 0 переключает сразу |
|
||
| `exitTime` | переход ждёт, пока состояние не проиграет столько проходов своего клипа: 1 — один проход |
|
||
| `interrupt` | переход может прервать идущее смешивание |
|
||
|
||
На каждом фиксированном шаге аниматор продвигает свои состояния, затем ищет переход: сначала переходы `any` по порядку, затем переходы текущего состояния. Все условия должны выполняться. Во время смешивания рассматриваются только переходы с `interrupt`. Переходы без длительности могут следовать друг за другом, до восьми за один шаг; переход с длительностью завершает шаг. Импортёр проверяет имена, операторы и циклы мгновенных переходов ([ANC001](ref:ANC001), [ANC002](ref:ANC002), [ANC003](ref:ANC003), [ANC005](ref:ANC005)); состояние, клип которого отсутствует, ничего не воспроизводит ([ANC004](ref:ANC004)).
|
||
|
||
## Смешивание
|
||
|
||
Смешивание сводит уходящее и входящее состояния с весом, который следует `ease` контроллера, по умолчанию `inOutQuad`. Числа, векторы и цвета интерполируются, повороты идут по короткой дуге, спрайты и другие ступенчатые значения переключаются на половине веса. Свойство, которое анимирует только один из двух клипов, смешивается от значения, которое было у него в начале перехода. Когда переход с `interrupt` прерывает идущее смешивание, поза на экране в этот момент становится уходящей стороной, поэтому ничего не скачет.
|
||
|
||
## События во время смешивания
|
||
|
||
| `events` входящего состояния | Какие события срабатывают |
|
||
|---|---|
|
||
| `dominant` (по умолчанию) | события состояния, вес которого в момент события не меньше половины |
|
||
| `both` | события обоих состояний |
|
||
| `incoming` | только события входящего состояния |
|
||
|
||
Событие в начале входящего состояния происходит при весе 0, поэтому с `dominant` оно принадлежит уходящему состоянию: ставьте удар атаки туда, где атака уже видна полностью.
|
||
|
||
---
|
||
id: animation/root
|
||
title: Движение корня
|
||
---
|
||
# Движение корня
|
||
|
||
Клип может двигать самого персонажа: его дорожка корня — `track "" prop="transform.position" kind=vec2 root=true`. «Аниматор» не записывает эту дорожку; он двигает узел на столько, на сколько дорожка меняется за каждый шаг, поэтому цикл ходьбы продвигает персонажа, а не отбрасывает его назад на каждом повторе.
|
||
|
||
| «Движение корня» | Что движется |
|
||
|---|---|
|
||
| `none` | ничего; дорожка корня не используется |
|
||
| `toMotion` | узел, через его [«Движение 2D»](ref:gameplay.Motion2D): стены его останавливают, склоны и ступени несут, скорость остаётся прежней |
|
||
| `toTransform` | позиция узла; кинематическое тело двигается своей скоростью |
|
||
|
||
«Масштаб корня» умножает движение в пространстве родителя: задайте его X равным -1, пока персонаж смотрит влево, потому что отражение спрайта не поворачивает узел. С `toMotion` и без «Движения 2D» на узле «Корень без Движения 2D» двигает вместо него трансформацию; без него движение отбрасывается ([ANM009](ref:ANM009)). Во время смешивания движение двух состояний смешивается по его весу. Движение корня работает только в игровом времени.
|
||
|
||
---
|
||
id: animation/go
|
||
title: Go
|
||
---
|
||
# Go
|
||
|
||
p, _ := sdk.Get[sdk.AnimationPlayer](scene, node)
|
||
p.Play(sdk.AnimClip{ID: runClip})
|
||
sdk.AnimationOf(ctx).SetParam(hero, "Speed", 3)
|
||
|
||
`sdk.AnimationOf(ctx)` возвращает сервис игрового мира; `SetParam(node, name, value)` задаёт параметр «Аниматора» по имени, `true` и `false` как 1 и 0. Системы `animation.fixed` (PrePhysics, после `script.fixed` и якоря `engine.intents`, до `engine.motion`) и `animation.sample` (Animation, всегда) продвигают и рисуют проигрыватели и аниматоры. Система Go, которая задаёт параметры или команды, выполняется до `engine.intents`; система, которая читает движение корня, выполняется после `engine.motion`.
|
||
|
||
Движение корня доходит до персонажа через контракт `motion2d.receiver` (`contracts/motion2d`), который предоставляет «Движение 2D»; его может предоставить и другой контроллер.
|
||
|
||
---
|
||
id: animation/attack
|
||
title: Атака с кадром удара
|
||
---
|
||
# Атака с кадром удара
|
||
|
||
Урон атаки принадлежит одному кадру её клипа, а не нажатию кнопки: поставьте на этот кадр игровое событие и запускайте импульс хитбокса, когда оно приходит.
|
||
|
||
anim version=1 length=0.3 {
|
||
frames "" {
|
||
sprites=[asset("…swing_0"), asset("…swing_1"), asset("…swing_2")]
|
||
durations=[0.06, 0.06, 0.18]
|
||
}
|
||
event t=0.1 name="Hit" payload=1 {}
|
||
}
|
||
|
||
Контроллер входит в атаку из любого состояния по триггеру и выходит после одного прохода:
|
||
|
||
transition "any" to="Hit" interrupt=true {
|
||
cond "Attack" {}
|
||
}
|
||
transition "Hit" to="Idle" exitTime=1 duration=0.05 {}
|
||
|
||
Скрипт нажимает триггер и превращает событие в урон:
|
||
|
||
func Ready() {
|
||
hit = anim.EventID("Hit")
|
||
}
|
||
|
||
func OnAnimEvent(id int, payload float) {
|
||
if id == hit {
|
||
Slash.Get<Hitbox2D>().Pulse()
|
||
}
|
||
}
|
||
|
||
- Задайте «Хитбоксу 2D» `active=false` и `policy=oncePerActivation`: `Pulse` наносит один раунд урона, поэтому каждая атака бьёт каждую цель один раз.
|
||
- Второе нажатие во время атаки ничего не делает, потому что переход `any` никогда не уходит из своей же цели.
|
||
- Это игровое событие: оно срабатывает на фиксированном шаге, который его пересекает, одинаково при каждом повторе.
|
||
- Оставьте переход в атаку без `duration`: при правиле событий по умолчанию событие во время смешивания принадлежит стороне с большим весом, и событие в начале плавного появления ушло бы уходящему состоянию.
|
||
|
||
---
|
||
id: animation/panel
|
||
title: Панель «Анимация»
|
||
---
|
||
# Панель «Анимация»
|
||
|
||

|
||
|
||
**Окно → Анимация** редактирует клип «Проигрывателя анимации» выбранного узла или клипы контроллера его «Аниматора». **Создать → Анимация → Клип** (Ctrl+Shift+N, `clip`, Enter) создаёт односекундный повторяющийся клип рядом со сценой и даёт узлу без проигрывателя проигрыватель, который его воспроизводит; **Создать → Анимация → Контроллер** делает то же с «Аниматором» и контроллером с одним состоянием Idle. Файл новый, поэтому шагом отмены становится только добавленный компонент.
|
||
|
||
**Предпросмотр** показывает клип на узле в момент курсора времени, не трогая сцену: сохранения, игра и отмена видят значения, которые действительно есть в сцене. Поля, которыми управляет клип, становятся в Инспекторе доступными только для чтения, с жёлтой полосой и подсказкой «управляется клипом». **Запись** превращает правки в ключи: значение, введённое в Инспекторе, или перемещение на панели «Сцена» ставит ключ у курсора, а не меняет сцену. Когда «Автоключ» выключен, нажмите K.
|
||
|
||
Таблица ключей показывает по строке на дорожку и ромб на ключ; дорожки спрайтов показывают кадры миниатюрами, когда хватает места. Перетаскивайте ключи, чтобы двигать их, тяните линейку, чтобы перематывать, дважды щёлкните дорожку событий, чтобы добавить событие. Колесо масштабирует время вокруг мыши, Shift+колесо сдвигает. **Кривые** рисует выбранную числовую дорожку кривой, по одной компоненте за раз (x/y, r/g/b/a); перетаскивайте точки и ручки касательных, дважды щёлкните, чтобы добавить ключ, щёлкните правой кнопкой, чтобы удалить. Всё перетаскивание — один шаг отмены, а остальные компоненты отредактированного ключа сохраняют свои значения.
|
||
|
||
| Клавиша | Действие |
|
||
|---|---|
|
||
| Space | воспроизводит клип или ставит его на паузу |
|
||
| K | ставит ключи на все поля выбранного узла, значение которых отличается от клипа у курсора |
|
||
| Delete | удаляет выбранные ключи |
|
||
| Ctrl+C, Ctrl+V | копирует ключи и вставляет их у курсора; вставка в другой клип создаёт недостающие дорожки |
|
||
|
||
Клавиши работают, только пока мышь над панелью. В меню ключа есть ещё **Взять из сцены**: оно даёт выбранным ключам значения, которые их свойства имеют в сцене.
|
||
|
||
Перетащите спрайт или ячейки листа из «Ассетов» на дорожку спрайта, чтобы поставить на них ключи: первый встаёт там, где вы отпустили, остальные идут следом по одному на 1/12 с, а клип удлиняется, если они выходят за его конец. В «Кривых» график делит линейку с таблицей ключей, поэтому ключ стоит под своим временем.
|
||
|
||
Чтобы скрыть спрайт на часть клипа, поставьте ключ его `tint` с прозрачным цветом: `#00000000` и `#ffffff00` оба ничего не рисуют при любом режиме смешивания. Затухание от `#ffffffff` к `#ffffff00` оставляет цвет белым, пока он гаснет; затухание к `#00000000` проходит через серый. Так атака платформера показывает свою дугу удара, а скрипт игрока снова прячет дугу, когда атака прерывается.
|
||
|
||
В «Предпросмотре» и «Записи» панель «Сцена» рисует путь дорожки `transform.position` узла, с ключами в виде точек: перетащите точку, чтобы сдвинуть этот ключ. События предпросмотра вспыхивают на пути, когда воспроизведение их пересекает. Пока вы тянете гизмо перемещения в «Записи», узел следует за мышью, а не за клипом, и ключ ставится, когда вы отпускаете кнопку.
|
||
|
||
---
|
||
id: animation/sheets
|
||
search_names: [нарезать спрайты, нарезка спрайтов, спрайтшит, покадровая анимация]
|
||
title: Листы спрайтов и покадровая анимация
|
||
---
|
||
# Листы спрайтов и покадровая анимация
|
||
|
||
Выберите PNG в «Ассетах», чтобы открыть его инспектор: лист с его ячейками.
|
||
|
||
- **Нарезать по сетке** вырезает ячейки одного размера, после смещения и с отступами между ними, ряд за рядом. **Количество** останавливает нарезку после стольких ячеек.
|
||
- **Нарезать по альфе** находит каждую отдельную фигуру, у которой альфа выше порога, и упорядочивает их рядами сверху, каждый ряд слева.
|
||
- **Нарезать** записывает ячейки в `.meta` листа. Каждая ячейка становится ассетом спрайта с именем вида `assets/hero.png#hero_000` и собственным id. Отмена возвращает прежние ячейки.
|
||
|
||
Повторная нарезка с тем же числом ячеек сохраняет все id, поэтому клипы и сцены, которые используют ячейки, остаются целыми. Удаление листа удаляет его ячейки. Нарезанный лист нельзя упаковать в группу атласа.
|
||
|
||

|
||
|
||
Раздел **Импорт** над листом редактирует, как импортируется PNG: фильтр (nearest для пиксель-арта, linear для гладкой графики), sRGB, MIP-уровни, группа атласа с её отступами и выдавливание краёв. Изменения ждут **Применить**, которое переписывает `.meta` и заново импортирует текстуру; **Вернуть** отбрасывает их. «Применить» — один шаг отмены того же вида, что и нарезка, поэтому отмена восстанавливает и настройки, и ячейки в том порядке, в котором вы их меняли.
|
||
|
||
В «Ассетах» у нарезанного листа на плитке есть стрелка: она раскрывает ячейки сразу после листа, каждую с миниатюрой. Выделите ячейки через Ctrl+щелчок и Shift+щелчок, затем **Покадровая анимация из выделения** использует их в порядке имён. Перетащите ячейку на поле `sprite` в Инспекторе или на дорожку спрайта в панели «Анимация». Ячейки нельзя переименовать, переместить или удалить по отдельности: вместо этого нарежьте лист заново.
|
||
|
||
Чтобы анимировать кадры, выберите узел, оставьте лист выбранным в «Ассетах» (или открытым в инспекторе) и выберите **Создать → Анимация → Покадровая анимация из выделения** (Ctrl+Shift+N, `flip`, Enter). Клип показывает ячейки одну за другой с частотой 12 кадров в секунду, как ступенчатые ключи на `SpriteRenderer.sprite`. Если у «Проигрывателя анимации» узла уже есть клип, кадры попадают в этот клип; иначе рядом со сценой создаётся новый повторяющийся клип с «Проигрывателем анимации», который его воспроизводит. Панель «Анимация» показывает кадры миниатюрами.
|
||
|
||
---
|
||
id: animation/animator-panel
|
||
title: Панель «Аниматор»
|
||
---
|
||
# Панель «Аниматор»
|
||
|
||

|
||
|
||
**Окно → Аниматор** показывает контроллер «Аниматора» выбранного узла в виде графа: по блоку на состояние, метка **Вход**, которая указывает на состояние, с которого начинает «Аниматор», и метка **Любое состояние** для переходов, которые могут уйти из любого состояния. Стрелки — это переходы, подписанные своими условиями; два перехода между одними и теми же состояниями в противоположных направлениях расходятся дугами. Колесо масштабирует вокруг мыши; тяните фон или держите среднюю кнопку, чтобы сдвинуть вид; **Показать всё** в меню фона вписывает граф.
|
||
|
||
Перетащите состояние, «Вход» или «Любое», чтобы передвинуть его; позиция сохраняется как `pos` состояния и `entryPos` / `anyPos` контроллера, поэтому раскладка графа хранится в файле. Протяните от точки справа у состояния (или у «Любого») к другому состоянию, чтобы добавить переход. Щёлкните фон правой кнопкой, чтобы добавить состояние или запустить **Авторасстановку**, которая ставит все состояния столбцами от входа. Щёлкните состояние правой кнопкой, чтобы сделать его входом или удалить вместе с его переходами; щёлкните переход правой кнопкой, чтобы поднять или опустить его или удалить.
|
||
|
||
Левая сторона редактирует выделение: клип и скорость состояния или длительность, время выхода, флаг прерывания и условия перехода. Переходы проверяются по порядку, поэтому стрелки ↑ ↓ рядом с заголовком переносят выбранный переход раньше или позже. Под инспектором — параметры; + добавляет параметр любого типа, − удаляет его.
|
||
|
||
Каждое изменение — один шаг отмены в файле контроллера. Изменение, сделанное вне редактора, в текстовом редакторе или через `aego mcp`, появляется, как только файл заново импортирован.
|
||
|
||
Во время игры состояние, в котором находится «Аниматор», подсвечено кольцом, которое заполняется по мере воспроизведения клипа, а параметры становятся живыми: введите float, переключите bool или нажмите **Сработать** для триггера, чтобы управлять идущей игрой.
|