Тёмная сторона Unity: о чём умалчивают официальные пресс-релизы

Прокомментировать Просмотры: 10

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

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

Behavior

Behavior — это инструмент для выстраивания логики неигровых персонажей на базе графов. Визуально он напоминает чертежи (Blueprints) в Unreal Engine и позиционируется как интуитивный способ реализации искусственного интеллекта. На первый взгляд перед нами классический поведенческий граф с узлами, где каждый блок отвечает за определенное действие в текущий момент (движение, поворот и т.д.). Ноды могут исполняться параллельно, а также допускается (и требуется!) написание собственных кастомных элементов для атаки, прыжков или применения заклинаний.

Неинтуитивная логика графов

Любой узел выдает определенный вердикт — успех либо неудачу. Тем не менее, соединить ноды разрешается лишь одной стрелкой независимо от исхода предыдущего шага. Из-за этого элементарные задачи вроде «если выстрелить не получается, перезаряди оружие» вынуждают городить громоздкие и избыточные управляющие конструкции.

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

Зависимость порядка выполнения от визуального расположения

Вследствие описанного выше ограничения приходится заменять последовательные блоки оператором Sequence. От него отходит несколько связей к нодам, которые должны активироваться по очереди. При этом приоритет отдается самому левому блоку, затем идет следующий и так далее вправо. Иными словами, порядок исполнения жестко привязан к геометрическому положению элементов в редакторе.

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

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

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

Увольнение профильных специалистов

Компания Unity Technologies распустила команду разработчиков, отвечавших за модуль Behavior, всего через полгода после его релиза. Хотя патчи выходят до сих пор, рассчитывать на серьезное развитие функционала уже не приходится.

NavMesh выступает стандартным и проверенным временем средством для расчетов навигации в играх на Unity. Тем не менее, несмотря на долгие годы эволюции движка, данная подсистема по-прежнему страдает от фундаментальных изъянов.

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

Отсутствие возможности редактирования меша

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

Невозможность локальной правки сетки

Генерация меша запускается глобально по единым для всей сцены параметрам (часть которых привязана не к самой сетке, а к характеристикам агентов). Вы не можете сделать так, чтобы один тридцатиградусный уклон считался проходимым, а другой — нет. Система трактует обе зоны одинаково, игнорируя тот факт, что в одном случае это может быть лестница, а в другом — ледяной скат.

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

Зачастую логика запекания остается загадкой. Допустим, вам требуется абсолютно плоская поверхность, и вы задаете предельную высоту области в 0.1 метра, но сгенерированный меш все равно выходит за установленные рамки. Или другой пример — составной мост из нескольких сегментов:

Сетка почему-то дробится на отдельные куски даже при сильном перекрытии элементов друг другом.

Проблема решается лишь набросом поверх невидимой плоскости (plane).

Невозможность слияния нескольких навигационных сеток

Если вы запекли два независимых меша — например, корпус корабля и причал, — объединить их в единое целое штатными средствами не получится ни в редакторе, ни во время исполнения игры. Даже если их границы соприкасаются или перекрывают друг друга, персонажи не смогут свободно перемещаться между ними. Существуют OffMeshLinks, которые служат скорее «порталами» для прыжков через препятствия или пропасти, но организовать с их помощью бесшовную ходьбу крайне проблематично.

Input System

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

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

Однако инициировать действие программно из кода игры напрямую нельзя. Допустим, завершение хода в вашей стратегии может происходить как по нажатию пробела, так и по клику на кнопку в интерфейсе. Написав обработчик для UI-кнопки, вы не сможете заставить Input System сгенерировать событие окончания хода — придется вручную вызывать все привязанные методы, дублировать подписки либо городить собственный слой-обертку.

Другой пример: вы разработали ПК-версию на базе Input System и решили портировать проект на мобильные устройства. Вместо привычных клавы и мыши на экране появляются виртуальные джойстики. Официальных компонентов для них нет, поэтому их приходится скачивать из Asset Store. Интегрировать их с Input System так, чтобы отклонение стика порождало стандартные события ввода, тривиальными методами не получится. Вновь требуется писать собственный прокси-класс и переписывать всю логику управления. В итоге мобильный порт либо обрастает костылями для совместимости с ПК-версией, либо превращается в самостоятельную ветку, требующую отдельной поддержки.

Различия в поведении редактора и итоговой сборки

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

Ошибки рендеринга теней

Параметры дальности прорисовки теней порой отрабатывают некорректно, из-за чего в сборочном пакете неожиданно всплывают графические артефакты.

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

А виновником проблемы выступает вот этот параметр:

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

Поскольку проект разрабатывался с видом сверху, сложная детализация теней по дальности казалась избыточной, и уменьшение значения Cascade Count показалось «оптимальным» решением, о побочных эффектах которого стало известно лишь во время тестового билда.

Звуковое сопровождение

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

Если в релизной сборке вызвать Play() для уже играющего источника, воспроизведение просто начнется заново, тогда как PlayOneShot() инициирует новый звуковой слой поверх старого. Однако в редакторе метод Play() по недоразумению работает ровно так же, как PlayOneShot(). В результате автоматная очередь, завязанная на метод Play(), звучит в редакторе безупречно, но полностью ломается в готовой игре.

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

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

Свежий пример из практики: необходимо реализовать управляемый танк с опорой на физику. Казалось бы, всё очевидно: по три коллайдера колеса (WheelCollider) с каждого борта плюс Rigidbody с основным коллайдером для корпуса. При повороте левые колеса крутятся вперед, правые — назад. Настраиваем, запускаем: гусеничная машина управляется как трамвай по рельсам и почти не поворачивает. Хорошо, у коллайдеров избыточное боковое трение — снижаем его значения. Танк начинает закладывать крутые заносы, ведя себя на дороге как корова на льду. Выставляем средние показатели — получаем гибрид трамвая и катка: он и скользит, и поворачивать отказывается. Отправляемся на поиски ответов:

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

  • Экзотические концепции, где вместо набора WheelCollider в центр шасси помещают одну массивную сферу, привязывая корпус через шарнирное соединение (joint). Избавляемся от старой проблемы и получаем десяток новых.

  • Гайды с классическими боковыми WheelCollider, но без единого слова о том, как побороть нежелательный дрифт.

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

Ладно, спросим искусственный интеллект. Claude рекомендует сохранить высокое сцепление, но в момент поворота принудительно докручивать корпус машины дополнительным вращательным моментом, преодолевая сопротивление колес. Сразу закрадывается подозрение, что метод крайне костыльный.

Что же требовалось сделать на самом деле? Установить низкое трение на крайних колесах и высокое — на центральных. Нашел ли я это решение в интернете? Нет.

Корутины (Coroutines)

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

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

Разумеется, можно написать собственную обертку. Но вдумайтесь: корутины появились в Unity версии 1.1 еще в далеком 2005 году. Они прекрасно осведомлены о состоянии внутреннего перечислителя, и запрограммировать элементарный геттер не составляет никакого труда. Тем не менее за два десятилетия разработчики двигателя так и не сочли нужным внедрить эту базовую возможность.

Общая философия и отношение к экосистеме

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

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

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

 

Источник

Поделиться:

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов