Игровые движки не сравнивал только ленивый, но за анализом фич и красивых пресс-релизов редко упоминаются баги, костыли и прочие особенности, превращающие разработку в кошмар. А ведь именно они могут повлиять на игру сильнее, чем люмены, наниты и прочие URP.

Эта статья - сборник проблем Unity, с которыми столкнулся лично я. Он далеко не исчерпывающий и несколько субъективен, но знай я о них заранее - возможно выбрал бы другую технологию.

Behavior

Behavior - это система для создания поведения NPC на основе графов. Внешне выглядит очень похоже на блупринты в UE и позиционируется как простое решение для реализации ИИ в игре. С виду - обычный граф поведения с нодами. Каждая нода описывает действие в конкретный момент времени (идти, повернуться и т. п.). Ноды могут выполняться параллельно. Можно (и нужно!) писать свои кастомные ноды (атака, заклинание, прыжок и т. п.)

Граф не понятен интуитивно

У каждой ноды графа есть результат исполнения - успех или провал. Но в самом графе от ноды можно провести только одну стрелку - независимо от успеха предыдущей ноды. Из-за этого инструкции вроде “если стрелять не получается - перезарядись” требуют дополнительных управляющих конструкций (весьма монструозных).

Тут написано “если враг слишком далеко - подбеги к нему, затем, если кончились патроны - перезарядись”. Там потом сама стрельба, но она не влезла на скриншот.
Тут написано “если враг слишком далеко - подбеги к нему, затем, если кончились патроны - перезарядись”. Там потом сама стрельба, но она не влезла на скриншот.

Порядок исполнения зависит от внешнего вида графа

По причине выше приходится вместо последовательных нод использовать конструкцию Sequence. От неё отходит несколько стрелок к нодам, которые надо выполнить по очереди. Так вот, первым выполнится самая левая нода. Потом следующая и так далее слева направо. То есть порядок исполнения зависит от расположения нод в эдиторе.

Красные стрелки - порядок исполнения
Красные стрелки - порядок исполнения

Это открывает непаханное поле для багов: передвинули часть графа и что-то не зацепили рамкой. Не создали новой связи, ни строчки нового кода - и всё равно получили баг.

Тут ещё был рассказ про сломанный принцип модульности, когда попытка вызвать один граф из другого вешала эдитор, но это, к счастью, исправили.

Разработчики уволены

Unity Technologies уволила команду, занимавшуюся системой Behavior спустя всего полгода после её релиза. Сейчас фиксы ещё выходят, но ждать заметных изменений не стоит.

NavMesh

NavMesh - это официальное решение для ИИ-навигации в Unity, очень старое и общепринятое. Но несмотря на годы развития движка, в нём всё равно есть огромные проблемы.

Наэтом уровне 2 навмеша: для людей и для техники
Наэтом уровне 2 навмеша: для людей и для техники

Меш не редактируется

Меш навигации можно запечь в эдиторе, но нельзя никак редактировать (даже программно!) - только перезапечь снова. Да, там есть компоненты NavMeshObstacle, которые добавляют динамические препятствия и с помощью которых можно делать двери, разрушаемые ящики и технику. Но если у вас на локации появляется куча песка, и надо ходить по ней, а не сквозь неё - это большая проблема. Потому что куча - это не препятствие, это изменение формы меша.

Меш нельзя редактировать по частям

Меш запекается только одной кнопкой по предустановленным для всего меша настройкам (часть которых прописываются не для самого меша, а для агентов). Вы не можете сделать так, чтобы один наклон в 30° на одном и том же меше был проходим, а другой - нет. Система оба места сделает проходимыми и ей плевать, что в одном месте у вас лестница, а в другом - скользкая крыша.

Это практически все настройки
Это практически все настройки

Зачастую непонятно как вообще запекается меш. Например, вам нужно чтобы уровень был полностью плоским, вы ставите высоту области для меша в 0.1 м. Но сгенерированный меш кое-где явно выходит за эти рамки. Или вот ещё пример: мост, набранный из нескольких секций:

Меш запекается кусками, почему - непонятно. Даже если ставить секции с сильным перекрытием - результат будет тот же.

Кидаем поверх невидимый plane - и всё работает.

Два меша нельзя склеить

Если вы запекли 2 меша, например, корабль и пристань, то их никак нельзя объединить в один. Ни в эдиторе, ни в рантайме. Даже если их зоны будут стоять в упор друг к другу или пересекаться - NPC не смогут переходить с одного на другой. Да, там есть OffMeshLinks, которые как раз позвляют переходить с одного меша на другой или между разрывами в одном меше, но это именно что “порталы”, позволяющие реализовать, например, прыжок с одной крыши на другую. Бесшовное передвижение ими сделать проблематично.

Input System

Система ввода - довольно старое и повсеместно использующееся решение для обработки ввода игрока. Она позволяет довольно удобно описывать действия, их клавиши и подписываться на совершение действия, а не на нажатие кнопки.

Сама система довольно проста и удобна в использовании.
Сама система довольно проста и удобна в использовании.

Но действие невозможно “триггернуть” из кода игры вручную. Например, если у вас в игре конец хода происходит и по нажатию на пробел, и по кнопке в UI - в обработчике кнопки вы не сможете заставить Input System отправить событие конца хода. Вам придётся пройтись по всем его обработчикам и вызвать их вручную. Либо подписываться и на Input System, и на UI. Либо писать свой враппер над Input System.

Или вот сделали вы игру под ПК на Input System и решили портировать её под мобилки. У вас вместо WASD и мыши - экранные джойстики. Но официального решения для таких джойстиков нет, вы качаете их из стора. И их невозможно интегрировать с Input System, чтобы отклонение джойстика порождало событие, на которое уже подписана вся ваша остальная игра. Вам снова нужен враппер + переписывание всей обработки ввода под этот враппер.  В итоге мобильный порт либо вносит изменения в ПК версию, либо становится форком, который надо поддерживать отдельно.

Разное поведение в эдиторе и на билде

Это частая проблема любых движков: в эдиторе включены какие-то дебажные обработчики, хуже производительность и .т. п. Но в Unity игра в эдиторе зачастую выглядит лучше, чем на билде.

Тени

Настройки дальности теней работают по-разному, отчего тени в эдиторе выглядят правильно, а на билде появляются баги.

Слева: билд, тени не ложатся на траву, появляется какая-то полоса в центре карты. Справа: эдитор, тени отображаются корректно.
Слева: билд, тени не ложатся на траву, появляется какая-то полоса в центре карты. Справа: эдитор, тени отображаются корректно.

А вот причина проблемы:

Cascade Count был выставлен в 1
Cascade Count был выставлен в 1

Нам в виде сверху не требовались настройки теней по дальности, поэтому обрезать cascade count показалось "хорошей" идеей, о последствиях которой мы узнали только на этапе билда.

Звуки

В Unity есть 2 способа проиграть звук через компонент AudioSource: Play() и PlayOneShot(). Первый предназначен для долгих звуков (музыки, например). Когда звук надо просто запустить. Второй предназначен, например, для выстрелов, когда надо проиграть короткие звуки несколько раз подряд, накладывая их друг на друга.

Если на билде вызвать Play() пока звук ещё играется - он просто стартанёт сначала, тогда как PlayOneShot() стартанёт новый звук и смикширует его с предыдущим. А вот в эдиторе Play() работает ровно как PlayOneShot(). В итоге автоматная очередь, проигранная через Play(), в эдиторе будет звучать нормально, но сломается на билде.

Разрозненная и противоречивая база знаний

В разработке мы сталкиваемся со множеством задач, и часто эти задачи типичны: управление персонажем, камерой, анимацией и прочим. В идеале для такого рода задач должно быть одно стандартное, общеизвестное решение или 2-3 варианта таких решений по ситуации. Даже если решение не описано в официальной документации, оно должно легко находиться в гугле или хотя бы с ИИ. Но в Unity зачастую даже решение стандартной задачи становится проблемой: вместо одного решения находится сразу несколько, но ни одно из них не работает нормально.

Вот недавний пример: надо сделать в игре управляемый танк, чтобы ездил он более-менее по физике. Ну казалось бы: по 3 WheelCollider слева и справа, RigidBody с коллайдером для корпуса. При повороте левые колёса едут вперёд, правые - назад. Настраиваем, запускаем: танк ездит как трамвай, почти не поворачивает. Ок, у коллайдеров слишком большое боковое трение, надо понизить. Танк начинает дрифтить как заправский стритрейсер и вообще ведёт себя как корова на льду. Ставим значение посередине - получаем трамвай на льду: и дрифтит, и не поворачивает. Идём искать решение:

  • Куча туториалов как сделать танк и таскать его, напрямую меняя координаты, без физики. Не наш уровень, ищем дальше.

  • Экзотические варианты, где вместо WheelCollider в центр танка ставится один большой шар и к нему joint’ом прибивается корпус. Решается одна проблема, появляется 10 новых.

  • Туториалы с WheelCollider по бокам, но без объяснения как убрать дрифт.

  • Одна длинная статья как сделать реалистичные гусеницы, которые будут повторять рельеф.

Хорошо, спросим ИИ. Клод советует оставить высокое трение, но при повороте к корпусу танка применять дополнительное вращение, чтобы пересилить трение на колёсах. Закрадывается мысль, что идея так себе.

Что надо было сделать на самом деле: поставить низкое трение на колёсах с краю и высокое - на центральных. Нашёл ли я это решение где-то в интернете? Нет.

Coroutines

Корутины - это асинхронные методы, на которых держится львиная доля логики Unity. По крайней мере, в небольших проектах и всевозможных туториалах. Грубо говоря, любой объект на сцене может вызвать метод StartCoroutine(). Он вернёт объект Coroutine, который будет каждый кадр дёргать переданный ему IEnumerator. Очень простая, старая и крайне распространённая штука.

Казалось бы, исполняемый объект должен знать своё состояние. Вот только у объекта Coroutine никак нельзя спросить - он ещё выполняется или уже закончился. Когда вы просто двигаете кубик, вам это может и не надо, но когда у вас персонаж выполняет несколько параллельных действий, или у вас на вложенные корутины завязан игровой ИИ, вам просто необходимо знать выполняется ли действие или уже закончилось.

Да, можно опять написать враппер. Но вдумайтесь: корутины появились в Unity 1.1, в 2005-м. Они знают состояние своего IEnumerator, сделать геттер совсем несложно. Но за 20 лет никто так и не удосужился написать эту функциональность.

Общее отношение к продукту

Есть в Юнити небольшой баг, который выдаёт некорректное предупреждение, если текстовое поле (TextMeshPro) используется внутри Canvas (а они почти всегда так используются). Вот тема с техническими деталями. Обратите внимание на даты: репорт - 2019 год, одно из обещаний починить в 2021, а баг до сих пор с нами. За это время вышла куча фичей, но один вводящий в ступор ворнинг, с которым сталкиваются все разработчики, не то что не починили, а даже не переписали.

И вот это как раз та проблема, которая порождает все остальные. Unity - движок неплохой, но делают его не разработчики игр и не для разработчиков игр. Его делают для инвесторов. Параллельно получаются какие-то фичи, которые как-то работают. Иногда даже хорошо. Пользователи же как будто существуют отдельно: пилят свои системы навигации, визуального скриптинга и как могут чинят то, что должно работать из коробки.

Буду признателен, если в комментариях поделитесь сталкивались ли Вы с похожими проблемами в Unity или других движках.

Комментарии (3)


  1. v645
    04.08.2026 18:35

    Разработчики юнити сделали 1001 фичу, которые, на их взгляд решают любую проблему. Но беда в том, что ни одна из них не работает нормально с любой другой.
    Есть 3 разных физических движка.
    Есть 5 разных UI систем (каждая из которых умеет полтора землекопа).
    Есть ECS/Jobs/DOTS, но в 90% проектов они не используются тк требуют умнейших костылей от разработчиков чтоб реально получить какой-то толк.
    Есть асинхронность и корутины, но всё равно приходится использовать UniTask или нечто подобное.
    Шейдеры вроде как-то умно компилируются. А в итоге получается uber.shader на 20мб когда внутри используется только стандартный spriteDefault и какой-то additive на частицы.

    Не говоря уже о том, как сильно разросся размер билда и сколько там мусора везде лежит в памяти (в тч ОЗУ)
    Да и постоянные эти загрузки после каждого изменения кода. Domain reloading. Да, можно всё отключить, поставив теги что мол обновляй вручную поля статические, но раньше почему-то и без этого работало и не надо было ждать по 10с.

    Крч что хочу сказать (исключительно субъективно): движок ужасный, но лучший из всех. Тут можно сделать что угодно, но есть нюансы на каждом шагу


  1. fire64
    04.08.2026 18:35

    О чем говорить, если полноценное динамическое освещение они обещают добавить только в 7 версии. Я имею ввиду Surface Cache Gi

    Да и UI они в последней версии тоже сделали сразу 2 вариантов..


  1. o1o1o1o1o2
    04.08.2026 18:35

    нуу честно сказать некоторые штуки как будто имеют место быть, но в основном претензии выглядят как недостаточное понимание особенностей движка и некоторой теории. Unity это не ноу код конструктор а довольно сложное апи с кучей нюансов которых хотелось бы конечно поменьше и поменьше багов, но в данной статье не все претензии оправданы я считаю. Например деревья поведения зависят от расположения нод бай дизайн, это не только для Unity решения верно, сверху вниз слева направо поэтому это не баг а фича. Для встроенного навмеша (если вы хотите именно его использовать, потому что есть и сторонние пакеты довольно неплохие) например есть динамический навмеш, и если у вас структура уровня меняется в процессе игры вам скорее всего придется перестраивать навмеш динамически. С инпут системой тоже не до конца понятно. Зачем вам эмулировать события инпут системы? у вас нажатие кнопки вызывает какую то логику, ну так и вызывайте ее. Ну то есть существует кто-то ответсвенный за выполнение логики, а триггеры могут быть разные (инпут с клавиатуры или кнопка интерфейса), это неправильное разделение ответственности. Зачем вам корутины когда есть unitask, который в принципе может работать как корутина в главном потоке а может и переключаться на другие треды тогда когда это возможно. С текстом тоже не понял, там 2 разных компонента для ui и для мира, вы точно используете правильный (что то типа TextMeshProUGUI вместо TextMeshPro)? На скрине с мостом если зеленое это коллаидеры то не совсем понятно зачем там на стойка они например, ну и плейн который вы накинули, на нем как будто меш колаидер полностью повторяет геометрию плейна (могу ошибаться) как будто было бы достаточно одного квада. Ну и по поводу обилия разных решений, тут я согласен что хотелось бы каких то стандартных однотипных решений для одинаковых случаев но unity движок с историей, что-то постоянно новое появляется, улучшается, меняется, поэтому и подходы меняются, то как решали проблему 10 лет назад и сейчас конечно может отличаться, но ведь у вас телефон сейчас тоже не кнопочный например, прогресс такая штука, если гуглите смотрите просто на даты публикаций и версии для которых написаны те или иные решения. Я без негатива и не защищаю unity, у них полно проблем, но не все что тут описано на мой взгляд справедливо.