Unity: сквозь тернии к importing assets

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

Бэкграунд

В геймдеве более 10 лет. Начинал с порта iOS игр на Android, потом пилил небольшие проекты на LibGDX, писал туториалы, которые, в своё время, многим помогли. В какой-то момент переехал в Москву, где работал Android-разработчиком, а параллельно со знакомыми организовал свой первый стартап, в рамках которого работали над MOBA Unyworld, под которую мы даже привлекли инвестиции. Проект, правда, пришлось закрыть.

Игра была на Unity. С тех пор (с ~2014 года) в той или иной степени приходится взаимодействовать с движком. Я не специалист графики и вряд ли сейчас с ходу смогу свой рендер написать или нормальный шейдер. На том проекте я занимался всем бекендом/инфраструктурой (инстансы, аркестраторы самопальные, матчмейкер, работа с БД и вот это вот всё), а в Юньке отвечал по большей части лишь за какие-то архитектурные вещи или аспекты, которые не касаются непосредственно самого движка:

  • Квестовая система
  • Система ачивок
  • AI
  • Клиент-серверное взаимодействие
  • Лиги, рейтинговые бои
  • Чат (использовали ejabberd)

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

Небольшое отступление

Это не пост с целью показать какой Unity плохой движок. Если бы я столько времени проработал с тем же UE, то, думаю, моментиков было бы не меньше. Просто делюсь опытом и интересными (надеюсь) историями из личной практики. А если я сейчас буду опять делать небольшую инди-игру, то, скорей всего, всё ещё выберу Unity.

Проблемные обновления

Познакомился с движком в районе Unity 4. И было множество изменений тех или иных аспектов Unity, которые влекли за собой переписывание нашего кода. Нормально, когда движок меняется, особенно в рамках мажорных обновлений, вот только…новые вещи привносят новые проблемы и баги, которые компания может месяцами (а то и годами) не фиксить.

У них несколько раз менялась сетка. В бородатые годы там были RPC. Примерно в то же время была альтернатива в виде uLink. Но это стороннее решение было не самым стабильным. Были вопросики к мастер-слейв архитектуре и крашам. В какой-то момент пришлось переехать на HLAPI/LLAPI. Теперь уже и HLAPI/LLAPI deprecated.

На момент написания заметок про сетку уже появилось MLAPI, но тогда оно было в статусе экспериментального. Что забавно, в старых доках куча рекомендаций использовать LLAPI, который уже не поддерживается. А в LLAPI ссылки на новый экспериментальный MLAPI.

В былые годы для отправки http запросов был не самый лучший класс WWW. Позже на замену пришёл UnityWebRequest. Я в наших игровых инстансах серверных (да, они были на Юнити) переписал код для отправки Http-запросов на него. В какой-то момент запросы переставали отправляться. Не было ясно, что не так. Даже если явно всё закрывать, вызывать Dispose и прочие хитрости.

netstat показывал сотни незакрытых соединений. UnityWebRequest почему-то их вообще не закрывал. В итоге пришлось остаться на самописном Http-клиенте с использованием дефолтного TcpClient из C#.

Был момент, когда начали приходить тревоги, что на серваке место на диске заканчивается. Но на этих серваках кроме логов ничего и не пишется. В итоге выяснилось, что Юнитишное LLAPI спамило каждый кадр ошибку откуда-то из нативного кода, логфайл забил весь хард из-за этого. Отловить ошибку нельзя. Затрай-кетчить тоже. Что вызывало ошибку? Не ясно. В итоге я это зарепортил напрямую сотруднику, который сетку пилил. Через 1-2 апдейта починили.

Тот самый WWW класс вообще не поддерживал keep-alive соединения. UnityWebRequest изначально тоже не поддерживал…И, судя по всему, поддерживать начал только в Unity 2019.

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

Очередной релиз принёс ошибку «Increase kMaxCallback». А всё потому что выкатили обновление, при котором при каждом заходе в Play Mode регистрировался ивент, а отписки не было. В итоге, если редактор долго запущен, то после 64 запусков он крашился.

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

Что-то не фиксится годами.

Странности

Была у нас однажды проблема с тем, что в какой-то момент просто пропадало всё GUI в игре. В логах спавн сообщений из нативного движка про вертексы. Так и не было ясно, что не так. Репортили Unity, те ответили, что баг очень редкий, но они «готовы выделиться сотрудника, если мы будем оплачивать ему часы по ставке $10к в месяц».

В итоге разбили корневой канвас на более мелкие — проблема исчезла. Баг с UI забавен ещё тем, что, если зайти в иерархию объектов в редакторе Unity и просто кликнуть по объекту, то гуятина снова появлялась.

Чтобы разгрузить основной билд и скорость запуска увеличить, часть ассетов вынесли в ассет бандлы и грузили по требованию. Иногда бывала ошибка «Asset Bundle: CRC Mismatch». Так и не поняли, что с этим делать. При пересборке иногда лечилось. Так и всплывало от раза к разу.

Тормоза UI это норма. Окна можно инстанциировать на старте игры и отключать (память жрут) либо инстанциировать по надобности (большой фриз при открытии). Но даже без этого всё равно будут при обновлениях тормоза:

  • Работа с текстом.
  • Перерисовки канвасов.
  • И т. д.

Я: оптимизирую логику AI и рейксаты, а теперь думаю, куда бы направить сэкономленный бюджет кадра.

Тем временем GUI:

Про Windows все шутят, мол «если что-то не работает на Винде, то ребутни/переустанови». С Unity есть похожая тема. Порой, когда начинаются непонятки, ошибки с пакетами и т. п, то советуют удалить папку Library. Что забавно, это очень часто действительно решало проблему. Хотя сейчас, к счастью, такое реже происходит.

Бывает, что в редкторе всё ок, а в билде с шейдерами проблема. Почему-то периодически могут слетать в настройках графики. Приходилось ресетать.

Если включить автосохранение, то при малейшем изменении префабов и их свойств редактор будет тупить несколько секунд.

Багует периодически сам редактор. При установке нескольких версий Юнити, судя по всему, ломаются теймплейты для создания новых проектов. В итоге при открытии проекта два юнити запускаются и нормально не поработать.

Лечится тем, что нужно сбросить расположение слоёв в редакторе Windows -> Layouts -> там переключиться на что-нить. Потом на default.

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

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

Профайлер в Unity живёт своей жизнью. Я его сам не включал, и он ничего не трекал. Выходит, что там за ночь что-то натекло в служебных процессах 🤔

С ничего ошибка возникла в Unity 2021. Чистый проект, никаких зависимостей не ставил. Burst даже не ставил. Фиксили много месяцев. В итоге, так и не уверены, что пофиксили.

Прождал чисто, чтоб показать, как процессы в Unity порой могут зависать. Без понятия, что оно там за код пытается завершить более 3-х часов 🙄

UnityWebRequest.SendWebRequest может намертво зависнуть. Случается редко, явная установка таймаута не помогает. С HttpClient либой такой проблемы нет.

Рекомендуемый (первый способ) может навечно повиснуть, даже несмотря на таймаут. Второй вариант всегда норм отрабатывает.

Раньше приходилось писать в MonoDevelop. Редактор сам по себе ужасный, так там ещё и куча проблем (особенно на Маках). К примеру, у тиммейтов на Маках был странный оффест по вертикали, когда при клике нажатие срабатывало на несколько сот пикселей ниже фактического клика.

Множество платформ классных и страшных

Были проблемы на конкретных платформах.

Facebook gameroom

В Gameroom от Facebook после какого-то обновления ланчера появилась проблема со скейлом, когда игра реагировала на тачи не в том месте, где они были совершены.

Android

Помните времена, когда в Unity не было Gradle при сборке под Android? А я помню 🌚

Всеми любимая (нет) ошибка при сборке: «Too many field references: 70613; max is 65536».

Да, был лимит на число ссылок во всех классах. Как можно было обойти? Выкинуть часть плагинов. Если у вас использовались тяжёлые плагины вроде FB, Firebase, Крашлитика и т. п, то только они почти весь лимит сжирали. Ох и намучился я с этим в своё время, чтобы уместить в лимит и билд собрать. Это сейчас подобные штуки поставляются в виде разных пакетов, можно выбрать какие компоненты хочешь использовать, а тогда не всегда такая роскошь была.

На момент 4/5 с Android Resolver было практически невозможно работать. При обновлении плагина FB мог отвалиться плагин Firebase. Хорошо, если в момент билда будет ругаться, что классов нужных нет, но иногда такое только при запуске обнаруживалось. Причём, отваливались то не все компоненты. Скажем, Firebase вроде без проблем инициализировался, а потом в какой-то момент падало по факту что-то, что не на старте срабатывает.

На iOS был Application.RequestUserAuthorization для запроса пермишенов, а вот на Android Permission.RequestUserPermission появился лишь в районе 2018. До этого нужно было пилить свой нативный плагин на Java, а в C# коде использовать неудобные обёртки для доступа к плагину.

Под Android не было возможности авторизоваться, нужно было использовать плагин. На iOS был Social.localUser.Authenticate, но не позволял получить токен для серверной валидации. Сейчас вижу, в 2019 оказывается Unity запилили плагин для этого. Но…

Cудя по всему, они и это больше не поддерживают. Страницы плагина выдаёт:

This package has been deprecated from the Asset Store In most cases, package deprecation happens because the publisher is unable or unwilling to support

В очередной раз обновил Юньку, поставил SDK и…Android билд не собирается. Ругается на версию build tools.

  1. Из редактора не обновить
  2. Нужно запускать консоль и там sdkmanager’у скармливать команды

После установки всё равно не заведётся, Unity продолжает старую версию использовать. Нужно ручками удалить папки старых версий build tools. 2023 год, а всё ещё такие базовые проблемы со сборкой под Android всплывают.

WebGL

Сложно понять, что пашет в WebGL билде, а что нет.

  1. System.Threading вроде как не должно работать, но…Часть System.Threading.Tasks работает.
  2. Task.Delay не работает. При этом не падает, а просто стопорится на await’е.

Выкрутился за счёт while’а с await Task.Yield(), но костыль костыльный. Самая корка в том, что они то что-то делают, то убирают, то добавляют. Т. е. потенциально с обновлением Unity может отвалиться что-то, что работало (haha_classic.jpg).

Либы и плагины

Для локализации на проектах порой используем I2 Localization. Обычно проблем не было, но в какой-то момент ко всем строкам добавили префиксы и через некоторое время на проде сломалась у всех локализации.

По дефолту плагин долбится к Гугловским таблицам. Если есть изменения, то качает их. Раньше проблем не замечали, т. к. не было массовых правок. В общем, не забудьте там в настройках автоапдейт отключить и зашивайте всё в билд.

В своё время для авторизации с помощью геймцентра Android нужно было тащить отдельный плагин, тогда как для iOS был Юнитишный класс GameCenterPlatform. Из того, что вижу, ничего в этом плане не поменялось.

С GooglePlayGames вообще много мороки было, начиная с того, что в некоторых версиях некоторые методы не работали, тогда как в других версиях работали эти, но не работали другие. Больше всего намучился с GetServerAuthCode.

Многие вещи наружу вынесены. К примеру, для некоторых нужно было использовать и оборачивать вызов методов в GooglePlayGames.OurUtils.PlayGamesHelperObject.RunOnGameThread.

На одном из проектов использовался Spine для анимаций. Грузили мы их на старте, чтобы потом фризов не было. Профайлер показывал большие спайки при загрузке. Что-то связанное с Json’ами. 100 мс…

Я на тот момент этого не знал, но анимации можно выгружать как в Json’ах, так и бинарниками. В итоге переделали на бинарники, ускорилось в 5 раз.

Я бы сказал, что на этом всё закончилось, но нет. 2 из 25 анимаций не работали нормально, если их экспортировать как бинарники. Аниматоры так и не смогли понять причину. В итоге 2 анимации экспортировали в виде Json’ов.

Использовали мы Infinario SDK для аналитики. Ииии при обновлении до 5.4 оно перестало работать. Но если добавить пустой скрипт, то снова заработает. Я даже пулреквест оставлял. Правда, его за 5 лет так и не приняли 😅

Представьте, что для того, чтобы Firebase заработал на Android, нужно устанавливать iOS модуль. Да, на Винде !🤦‍♂️

И это не шутка, без этого часть редакторских скриптов (по работе с google-services.json, например) не работают в Unity 2021.

Эксперимент экспериментом погоняет

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

Решать пытаются тем, что скупают готовые решения (разраба, который им помог с UI, разрабов TMP, Bolt и т.д). Но в итоге это частенько прилеплено сбоку. Самое обидное, для меня, это ситуация с Bolt, на который они положили болт (хе-хе).

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

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

Втаскивают необязательные зависимости. В частности, много проблем у нас было из-за Newtonsoft.Json, который ранее внешне ставился, а теперь в packages лежит. После обновления Unity опять начались проблемы с ним. Советы от Unity лучше не делают.

Умудряются выпускать сборки, в которых в принципе невозможно собрать билд под одну из популярных платформ.

Время занимательных историй

Просто ребутни

Касательно серверных инстансов…Мы с определённой периодичностью их обновляли и, соответственно, перезапускали. Поэтому некоторых проблем не замечали. В какой-то момент не было обновлений на серваке более 2 недель, и, внезапно, мы обнаружили, что на машине закончилась RAM.

Да, серверные инстансы текли. Раньше этого не замечали, т. к. при рестарте память очищалась. В инстансах у нас поднимались комнаты с аренами. Но в одном из Монобехов оставалась висячая статическая ссылка, из-за чего GC не мог подчистить ресурсы. Нашли проблемную ссылку, жить стало лучше.

Но до конца утечки памяти мы так и не починили. А разбираться на тот момент совсем не было времени. В итоге сделали так, чтобы инстансы позволяли создавать не более 50 комнат. После чего инстанс сообщал сервису над ним, чтобы тот не инициировал создание новых комнат.

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

Периодически игроки жаловались на то, что их выкидывает из игры. С инстансами вроде было всё в порядке. Немного последив за этим делом выяснилось, что это обычно происходило в момент создания новых комнат. В этот момент часть игроков с текущих комнат отключало.

А Unity у нас однопоточный…Если что-то грузит основной поток, начинаются проблемы. Всё дело было в парсинге json’ов профилей игроков при создании новой комнаты. На несколько кадров всё фризило. А если одновременно несколько комнат создавалось, то речь уже шла о секундах…

В итоге весь парсинг я просто вынес в отдельный поток, тем самым разгрузив основной. Это ещё один урок на тему того, что обязательно нужно проводить нагрузочное тестирование.

Опять синглтонишь?

На том проекте никакого DI не было, куча синглтонов. И если на клиенте ещё более-менее терпимо (нет) было писать что-то типа gamemanager.Player, то на бекенде, уф. А большую часть кода мы старались переиспользовать между клиентом и сервером (напоминаю, на беке крутились юнитишные инстансы). Вылилось в то, что на бекенде на всём протяжении разработки периодически всплывали моменты, когда мы обращались совсем не к тому игроку. Это уже не говоря про лапшу из дефайнов GAME_CLIENT и GAME_SERVER.

ip6 всему голова

После авторизации наша API’шка клиенту отдавала IP сервера, где игроку комната поднялась. И всё работало, пока в Apple не решили протестировать игру в локальной сети, где что-то намудрили с ipv6.

В общем, запросы до нас не доходили. Возможно это было связано с тем, что они там что-то поменяли по части секьюрности. В итоге для всех серваков завёл поддомены и API их отдавала, а не голый IP.

Кешируй меня полность

С Enum’ами были нюансы. Часто нужно было для них вызывать ToString(), особенно на серваке. Довольно медленно. В итоге написали скриптик, который для всех Enum’ов генерил автоматически массивы и считывание уже было по индексу.

Использование Enum’ов как ключей для Dictionary ведёт к постоянному анбоксингу и лишним аллокациям. Решается двумя путями на выбор:

  1. Своей реализацией IEqualityComparer.
  2. Использовать int’овый ключ.

Скорость примерно одинаковая. Мы выбрали второе. В словарях как ключ использовали int, а при получении значений просто приводили enum’ы к int’у.

Why are you dying?

Серверный инстанс с аренами представлял собой одну сцену, на который спавнились комнаты с каким-то глобальным оффсетом (скажем, по X сдвиг на 10000). Как-то задиплоили билд с багом, из-за которого снаряды не уничтожались. В итоге они с одной арены долетали до другой 😅

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

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

Заранее подумайте, как это всё можно тестировать в редакторе, где есть удобный дебаг и прочее. У нас не для всего так получилось, в итоге приходилось частенько исследовать проблему по логам. А если это серверная проблема, где сотни игроков, то, ну, сами понимаете…

Wait, it was in play mode?

Случалось, что вы что-то правите в объектах, а потом оказывается, что это всё было в плеймоде, и после нажатия на Stop все изменения терялись? При редактировании уровней это могло убить много часов работы.

По мелочи

  • На заре Addressable было множество проблем. Самая большая, учитывая, что я тогда под мобилки писал, проблема была с освобождением памяти. Даже когда счётчик доходил до 0, текстуры некоторые всё равно оставались в памяти. Один и тот же ассет можно было грузить и как текстуру, и как спрайт. Поведение и очистка в зависимости от этого отличалась. Приходилось много кода писать вокруг дефолтного API. Очень много лапши для кеширования и т. п
  • С префабами много мороки было из-за отсутствия наследования и вложенности нормальной. Только в 2018 появились nested prefabs и prefab variants.
  • Если включить автосохранение, то при малейшем изменении префабов и их свойств редактор будет тупить несколько секунд.
  • Самая большая проблема в Unity для меня, что сам редактор работает, как я понимаю, в одном потоке. При изменениях/операциях его вечно фризит. Поменял скрипт? Ждёшь. Поменял настройки спрайтов? Ждёшь. И т. д. А ведь можно было заморочиться и распараллелить эти вещи. Шаги в эту сторону делаются, но очень уж медленно. Только совсем недавно в Unity 2021.2a19 добавили поддержку параллельного импорта моделей и текстур.
  • Редактор до сих пор не умеет сериализовывать Dictionary. Даже с примитивом в виде ключа. Приходится городить огороды. Но, к счастью, есть сторонние пакеты вроде Одина, которые ооооочень упрощают работу с редактором.
  • Добивали ворнинги «warning CS0649: Field is never assigned to, and will always have its default value null». И это при том, что Unity знает, на каких префабах скрипт висит и в курсе, что оно не null. Приходится оборачивать в \#pragma warning disable 0649″.
  • Менеджер пакетов в Unity весьма костыльный. Есть как внутренние пакеты, так и возможность из репо подтянуть. Но нету каких-то колбеков по факту установки/обновления пакета. К примеру, если у вас поменялось часть скриптов, удалили, например, то вам как-то ручками нужно чистить файлы. И никаких ивентов до сих пор не завезли. Сами Юнитишники советуют вот такую дичь. Но всё равно даже при использовании такого подхода приходится всё ручками чистить на уровне файловой системы.
  • В нативке Юнити есть методы для вызова изменений, которые не затрагивают иерархию (специальный флаг есть). Но в C# враперы это не вынесено. Узнал чисто случайно, когда с GDC смотрел доклад от разработчиков INSIDE.
  • При обновлении Юньки может отвалиться сборка под какую-то платформу. Помню, что в районе 2017-2018 у меня была проблема с GPS, тогда фиксом было добавление к id префикса \u003.
  • Через минут 10 после того, как вывел комп из спящего режима вылезла вот эта ошибка, при этом зашумели кулеры на видяхе на максималку. Редактор Unity был открыт, но не был запущен, и крашнулся в итоге.
  • Невозможно было нормально дебажить в 2021 версии. При каждому запуске и выключении плеймода вылетает эксепшн.

У тиммейта проблема на Маке.

  1. Работаешь с гуятиной.
  2. Клонируешь какой-то тяжёлый объект.
  3. Ctrl+Z.
  4. Краш редактора.

Не знаю, это именно с UI-компонентами связано или нет, но повторяется постоянно. Жутко бесит его 😅

Что же дальше

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

Позже расторжение соглашение с Ветой. Ну и несколько волн увольнений — в рамках последней было уволено почти 25% всего штата.

Многие ключевые команды порядели. Не ясно, какое будущее у того же DOTS’а. Хотя не так давно и вышла относительно стабильная версия, но боязно.

При этом очевидно, что компания не закроется, проекты всё ещё можно пилить на движке, но на фоне UE нынче Unity выглядит как отстающий игрок.


Если хотите поддержать выход дайджеста и других материалов, сделать это можно одним из способов.

Также вы можете подписаться на рассылку дайджеста.