Ми часто бачимо: персонаж атакує, ви викликаєте SetTrigger("Attack"), але анімація не відтворюється. Або відтворюється з кадровою затримкою. Або запускається двічі поспіль. Все тому, що тригер в Animator Controller має специфічну поведінку: якщо його нікому «спожити» в поточному кадрі, він залишається в черзі і вистрілює в наступному стані — там, де ви його вже не чекаєте. Це не баг Unity, це архітектурне рішення, яке потребує розуміння. Наша команда геймдев-інженерів з 10+ річним досвідом допомагає розібратися і налаштувати анімаційну систему правильно. Замовте аудит анімаційної архітектури — оцінимо за 1–2 дні. Результат — передбачувана система тригерів під ключ, яка зекономила нашим клієнтам до 70% часу на налагодження. Вартість аудиту починається від 10 000 рублів, а економія на налагодженні може сягати 50 000 рублів.
Чому SetTrigger ламається в типових сценаріях?
SetTrigger + Any State перехід. Якщо у вас є Any State → Attack з Trigger "Attack", і персонаж знаходиться в стані Stunned, яке також має Any State → Stunned з вищим пріоритетом — SetTrigger("Attack") буде спожитий переходом в Stunned, якщо обидва тригери виставлені в один кадр. Пріоритети переходів з Any State критичні і часто не документовані.
ResetTrigger в неправильний момент. Якщо код викликає animator.ResetTrigger("Attack") в OnStateEnter атакуючого стану "для надійності" — ви втрачаєте повторний запит на атаку. Правильне місце скидання — кінець анімації через StateMachineBehaviour.OnStateExit.
Animator не активний або culled. Якщо Animator.cullingMode = AlwaysAnimate вимкнено і об'єкт вийшов за межі Camera Frustum — Animator перестає оновлюватися. SetTrigger не губиться, але застосовується із затримкою при поверненні в frustum. Така помилка може коштувати проєкту до 2 тижнів дедлайну. Понад 80% проєктів з мобільними персонажами стикаються з цим.
Методи усунення багів тригерів
Bool замість Trigger для тривалих станів. Стрільба, біг, прицілювання — не Trigger, а Bool. SetBool("IsRunning", true/false) дає передбачувану поведінку. Trigger використовуємо лише для одноразових імпульсних подій: стрибок, удар, смерть. Через цю помилку ми бачили до 70% анімаційних багів в проєктах клієнтів.
| Тип параметра |
Застосування |
Ризик скидання |
| Trigger |
Імпульс (удар, стрибок) |
Високий — черга неочевидна |
| Bool |
Тривалий стан (біг, стрільба) |
Низький — значення утримується |
Integer-параметри для комбо. Якщо у персонажа є ланцюжок атак, ComboIndex: int — надійніше набору тригерів Attack1/Attack2/Attack3. Перехід перевіряє ComboIndex == 1, ComboIndex == 2. Код інкрементує лічильник і скидає по таймауту. Це усуває цілий клас проблем з «загубленими» тригерами.
Animation Events для синхронізації геймплею. Запуск хітбокса, звуку удару, спавну снаряда — через Animation Event, а не через coroutine з таймером. Animation Event гарантує прив'язку до конкретного кадру анімації незалежно від швидкості відтворення. Навіть при slow-motion (animator.speed *= 0.5) подія спрацює в правильний кадр.
Офіційна документація Unity: Animator Parameters
Реальний кейс: файтинг, 6 персонажів, у кожного по 3–5 атак. Початкова реалізація через SetTrigger + coroutine-таймери давала розсинхронізацію хітбоксів при зміні швидкості анімації. Перехід на Animation Events + StateMachineBehaviour повністю усунув проблему. Бонус: налаштування таймінгу через Animation Window без правки коду.
Інструкція: як налагодити тригер за 5 кроків
- Увімкніть Animator Window в Play Mode.
- Перевірте, які параметри активні при відтворенні анімації.
- Переконайтеся, що Any State переходи мають коректні пріоритети.
- Замініть тригери на Bool для станів довших за 0.5 секунди.
- Винесіть синхронізацію в Animation Events.
Коли варто переходити на Playables API?
Для складних сценаріїв — кат-сцени, процедурні анімації, динамічний blend — Unity Playables API дає більше контролю. AnimationMixerPlayable дозволяє мікшувати кілька анімацій з явними вагами. AnimatorControllerPlayable — використовувати існуючий Animator Controller всередині графа. Але для стандартних персонажів достатньо Animator Controller. Перехід виправданий, якщо State Machine стає занадто складною.
Приклад коду: налаштування AnimationMixerPlayable
var playableGraph = PlayableGraph.Create();
var mixer = AnimationMixerPlayable.Create(playableGraph, 2);
playableGraph.Connect(AnimationClipPlayable.Create(playableGraph, clip1), 0, mixer, 0);
playableGraph.Connect(AnimationClipPlayable.Create(playableGraph, clip2), 0, mixer, 1);
mixer.SetInputWeight(0, 0.3f);
mixer.SetInputWeight(1, 0.7f);
var output = AnimationPlayableOutput.Create(playableGraph, "Output", animator);
output.SetSourcePlayable(mixer);
playableGraph.Play();
Інструменти діагностики
- Animator Window — поточний стан, активні переходи, значення параметрів. Вмикається через Window → Animation → Animator.
- Profiler → Animation module — CPU час Animator. Якщо Animation Evaluation > 3 мс з 10 персонажами — проблема: занадто складні State Machine.
- Frame Debugger — перевірка, що анімація застосовується в правильний кадр.
Процес налаштування системи анімаційних тригерів
-
Аудит існуючої State Machine: кількість станів, переходів, параметрів, Layers. Документування логіки. Виявлення проблем через Play Mode тестування з увімкненим Animator Window.
-
Специфікація: які параметри (Trigger/Bool/Int/Float), хто і коли їх встановлює, де скидає. Це запобігає конфліктам між AI, Input і Network.
| Масштаб задачі |
Орієнтовні терміни |
| Налагодження конкретної анімаційної проблеми |
1–3 дні |
| Налаштування системи тригерів для одного персонажа |
3–7 днів |
| Розробка архітектури анімацій для всієї гри |
2–5 тижнів |
Що входить у налаштування
- Аналіз поточної анімаційної архітектури
- Документація логіки переходів
- Налаштування параметрів і тригерів
- Впровадження Animation Events і StateMachineBehaviour
- Тестування на цільових платформах
- Навчання команди роботі з новою системою
- Пост-релізна підтримка 30 днів
Зв'яжіться з нами, щоб обговорити ваш проєкт. Отримайте консультацію та оцінку задачі за 1–2 дні. Наші інженери реалізували понад 50 успішних проєктів у геймдеві, і ми запропонуємо рішення під ключ.
Типові проблеми продуктивності ігор
Проект запускається на топових девайсах розробника без питань. На mid-range Android п'ятирічної давності — 20 fps і перегрів через 5 хвилин. На iPhone 11 — стабільні 60, але на iPhone XR — просідання у важких сценах. Ми стикаємося з цим кожен день. Оптимізація — не «потім», а архітектурне рішення з першого коміту. За 8 років роботи ми провели оптимізацію більш ніж для 50 ігор — від гіперказуалки до AAA на консолях. Гарантуємо: після нашого аудиту ви отримаєте не просто список проблем, а конкретний план з вимірними цілями та термінами. Замовте аудит продуктивності — оцінимо ваш проект за 3 дні і покажемо, як знизити draw calls на 40% без втрати якості.
Як профілювати продуктивність ігор?
Перш ніж оптимізувати — виміряти. Оптимізація без профілювання — вгадування.
| Інструмент |
Призначення |
| Unity Profiler |
CPU/GPU час по системах, GC allocations, audio |
| Frame Debugger |
Інспекція кожного draw call у кадрі |
| Memory Profiler |
Знімок пам'яті, граф залежностей асетів |
| RenderDoc |
Глибокий аналіз GPU-стану, актуальний для PC/Console |
| Android GPU Inspector |
Профілювання GPU на реальному Android-пристрої |
| Xcode Instruments |
GPU + memory на iOS (Metal Performance HUD) |
| Snapdragon Profiler |
Qualcomm GPU — детальна статистика шейдерів |
Профілюйте на цільовому залізі, а не в редакторі. Editor додає оверхед — цифри з Play Mode не репрезентативні. Розділяйте GPU та CPU bottleneck: на CPU багато draw calls або важка логіка, на GPU — складні шейдери або overdraw. Документація Unity: профілювання на пристрої — обов’язковий крок для мобільних ігор. Типовий час профілювання одного сценарію — 5–8 годин, включаючи збір метрик на 3–5 пристроях різних поколінь. Аналізуємо 10–15 ключових ігрових сценаріїв за 3 робочих дні.
Як зменшити кількість draw calls?
Draw call — команда CPU до GPU «намалюй це». Кожен виклик має overhead незалежно від складності геометрії. На мобільних 200–300 draw calls за кадр — межа. Мета: мінімізувати їх кількість, об’єднуючи геометрію з однаковим матеріалом.
Static Batching об’єднує нерухомі меші при збірці. Вимога: прапорець Static на об’єкті та однаковий матеріал. Ефективний для статичного оточення, але збільшує споживання пам’яті — об’єднаний меш зберігається окремо. Static Batching може знизити draw calls в 3 рази, але збільшує пам’ять на 20% порівняно з іншими методами. У сценах з тисячами статичних об’єктів слідкуйте за пам’яттю через Memory Profiler.
Dynamic Batching об’єднує меші в рантаймі з жорсткими обмеженнями: менше 900 вертексних атрибутів на меш, однаковий матеріал та масштаб. На практиці ефективний лише для дрібних об’єктів (партикли, UI). В URP за замовчуванням вимкнений — його витіснив SRP Batcher.
SRP Batcher — не класичний батчінг, а оптимізація CPU-overhead при підготовці draw calls. Замість того щоб кожен кадр заново завантажувати uniform-дані шейдера, SRP Batcher кешує їх у GPU-пам’яті та оновлює лише ті, що змінилися. Draw calls залишаються колишніми за кількістю, але кожен займає менше часу CPU — SRP Batcher дає приріст в 2–3 рази по CPU-часу рендеру. Вимога: шейдер має бути сумісний з SRP Batcher (декларувати per-object властивості в UnityPerDraw CBUFFER). Стандартні URP Lit/Unlit шейдери сумісні. Кастомні — перевіряємо в Inspector матеріалу: SRP Batcher compatible: Yes/No. Для ввімкнення переконайтеся, що в URP Asset опція SRP Batcher активна, а для кастомних шейдерів використовуйте макрос UNITY_INSTANCING_BUFFER і декларуйте per-object властивості в блоці CBUFFER_START(UnityPerDraw). Після ввімкнення в Profiler має знизитися час RenderLoop.Draw на CPU.
GPU Instancing — для множини копій одного меша з одним матеріалом (дерева, трава, NPC). Відправляє один draw call з масивом per-instance даних. Вмикається на матеріалі: Enable GPU Instancing. Обмеження: всі інстанси в одному batch повинні мати однаковий матеріал і меш. Graphics.DrawMeshInstanced / Graphics.DrawMeshInstancedIndirect — для процедурного рендерингу без GameObject overhead.
Вибір методу залежить від сценарію: Static Batching підходить для статичного оточення, SRP Batcher виграє в проектах з множиною унікальних матеріалів, GPU Instancing незамінний для масових об’єктів (ліс, натовп), Dynamic Batching — лише для дрібних та рідкісних випадків. На практиці ми комбінуємо все, починаючи з профілювання.
| Метод |
Тип об'єктів |
Вплив на CPU |
Вплив на GPU |
Споживання RAM |
| Static Batching |
Статичні |
Помірне зниження |
Без змін |
Збільшується |
| Dynamic Batching |
Дрібні (≤900 вертексів) |
Зниження |
Без змін |
Без змін |
| SRP Batcher |
Будь-які (сумісні шейдери) |
Значне зниження |
Без змін |
Без змін |
| GPU Instancing |
Копії одного меша |
Мінімальне |
Значне зниження |
Незначно |
Детальніше про технологію — на Wikipedia.
Чому оптимізація пам'яті критична для мобільних ігор?
Мобільні платформи — жорсткі обмеження по RAM. iOS вбиває додаток при перевищенні пам’яті без попередження (memory pressure kill). Android — аналогічно, але з onLowMemory callback. Цільові бюджети: iOS — <1 GB для сучасних пристроїв, <512 MB для підтримки iPhone 8/X; Android — <800 MB для широкої сумісності (ОС займає 400–600 MB). Типова економія після нашої оптимізації — 30–50% оперативної пам’яті при збереженні якості. Середня економія бюджету на етапі оптимізації — від $3,000 до $8,000 залежно від складності проекту, що підтверджується нашими кейсами.
Addressables та Asset Bundles: як не перевищити бюджет
Завантажувати все одразу при старті — неприйнятно для великих проектів. Addressables (надбудова над Asset Bundles) — система адресованого асинхронного завантаження асетів. Явне вивантаження: Addressables.ReleaseInstance / Addressables.Release. Addressables не вивантажують асети автоматично при знищенні об’єкта. Типова помилка: Addressables.InstantiateAsync у циклі без Release — пам’ять росте до крашу. Reference counting: асет вивантажується тільки коли всі його handles звільнені. Архітектурний патерн: сервіс/менеджер тримає handle завантаженого асета, звільняє при переході між сценами.
Groups та Bundle Strategy: групуємо асети за логікою завантаження. Наприклад, всі асети одного рівня — в один bundle, шарені (UI, шрифти) — в окрему групу з Prevent Updates. Така стратегія економить до 30% пам’яті.
Texture Memory: звідки береться 70% займаного об'єму
Текстури — основний споживач пам’яті. Аналіз через Memory Profiler: вкладка All Of Memory → Texture2D — одразу видно список найважчих текстур. На практиці ми знаходимо текстури з завищеним Max Size (4096 для мобільної іконки — типова помилка). Заходи: Mipmap для 3D-текстур (ввімкнути), для UI (вимкнути); Streaming Mipmaps для open world — завантажує mip-рівні по мірі наближення камери. Красовська проблема: текстури, на які посилаються невикористовувані Materials, залишаються в пам’яті — Memory Profiler покаже reference chain. Видаляйте зайві матеріали. Після заміни всіх текстур формату RGBA32 на ASTC 6×6 на Android економія досягає 60% без втрати якості.
GC Allocations: як усунути фризи в Hot Path
C# garbage collector в Unity — stop-the-world. Якщо за кадр алоційовано багато heap-пам’яті, GC-пауза викликає видимий фриз. Ціль: нульові алокації в hot path (Update, FixedUpdate, рендер). Типові джерела: string конкатенація в Update (замінюємо на StringBuilder), LINQ в hot path (ручні цикли з предалоційованими списками), GetComponent<T>() кожен кадр (кешуємо в Awake/Start), boxing value types при передачі в object параметри. Після профілювання за допомогою Unity Profiler ми знижуємо алокації в hot path на 95% — фризи зникають.
Як відсікти зайві 40% draw calls? LOD та Culling
LOD Group — перемикання на спрощену геометрію при віддаленні об’єкта від камери. Стандарт для 3D оточення: LOD0 (100%), LOD1 (30–50% трикутників), LOD2 (10–15%), Culled. Для мобільних поріг Culled ставимо агресивніше — менше малюємо за кадр. LOD знижує draw calls на 40–60% залежно від кількості об’єктів.
Occlusion Culling — Unity не рендерить об’єкти за стінами. Вимагає запечені occlusion дані. Для indoor сцен знижує draw calls на 20–40%.
Frustum Culling працює автоматично — об’єкти поза FOV камери не рендеряться. Але draw call на перевірку все одно відбувається. Для сцен з тисячами об’єктів — кастомний spatial partitioning (Quadtree, Octree). В одному з проектів впровадження оклюзійного калінгу та LOD знизило загальну кількість draw calls з 2800 до 450 на Android.
Оптимізація VR: як утримати 72 FPS на Quest 3
VR — окремий клас задач. Фреймрейт 72/90 Hz не можна порушувати, інакше motion sickness. Додатково до стандартних методів: Single Pass Instanced Rendering — рендер для обох очей за один прохід (знижує draw calls в 2 рази); Fixed Foveated Rendering (Quest) — зниження роздільної здатності на периферії; Late Latching (Quest 3) — оновлення позиції контролера максимально пізно перед рендером; Dynamic Resolution в URP/HDRP — автоматичне зниження роздільної здатності рендера при просадці fps. Для Quest профілюємо через OVR Metrics Tool — показує CPU/GPU time прямо в гарнітурі. Після застосування цих методів частота кадрів на Quest 2 стабілізується на 72 FPS навіть у сценах з 1.5 млн полігонів.
Які етапи оптимізації та терміни?
Ми пропонуємо комплексну послугу «під ключ»:
-
Профілювання на цільових пристроях — збір метрик (FPS, draw calls, пам’ять, GC). Аналізуємо 10–15 ключових сценаріїв за 3–5 робочих днів.
-
Звіт з пріоритетами — які проблеми критичні, які можна відкласти. Пріоритети виставляємо на основі впливу на ігровий досвід.
-
Впровадження оптимізацій — батчінг, LOD, Addressables, шейдери, occlusion culling. Середній цикл впровадження — 2–4 тижні.
-
Повторне профілювання — замір покращень. Типовий приріст FPS — 30–60% на мобільних пристроях.
-
Документація — рекомендації по підтримці та подальшій розробці.
-
Навчання команди — як не допустити регресу. Проводимо воркшопи з профілювання та оптимізації.
Завдяки запобіганню переробок на пізніх етапах замовник отримує значну економію. Отримайте консультацію — ми безкоштовно оцінимо ваш проект і покажемо потенціал оптимізації. Звертайтеся, щоб дізнатися точні терміни та вартість для вашого стеку.