Когда Unity Developer немножко виноват.

Да, я говорил, что 90% оптимизации - забота технического артиста. Но оставшиеся 10%... они умеют больно кусать за FPS. Причём, когда артист всё уже выжал, а игра всё равно тормозит.

Недавно получил кейс: игра лоу-поли, графика - картошка, но в эдиторе выше 100 FPS не поднимается. Провёл первичную магию (запёк свет, настроил батчинг) — получил 140 вызовов на проце, 260 статик-батчей, на видео просто отличные 58 вызово… и тормоза. Не принтер, но 150 фпс в билде для такого ПК проекта...

Открыл профайлер. И там… космос. Около 30% времени в главном потоке движок просто ЖДАЛ. Ничего не делал. Стоял и ждал.

Не люблю лезть в чужой код, но пришлось. И тут началось веселье.

Шаг первый. Асинхронность — не просто модное слово Проект был идеально SOLID, MVP и прочим, но… монопоток. Всё в линейку.

Асинхронность и многопоточность — это ключ. Их не было. Добавил async/UniTask/Job для тяжёлых расчётов, вынес физику отдельно и главный поток выдохнул.

Monobehaviour — вынужденное зло. Даже если у вас нет Update/FixedUpdate/LateUpdate, он всё равно встаёт в очередь на обработку каждым кадром. В сцене 400 объектов, на каждом по 3 монобеха. 1200 сущностей трижды в кадр ждали своей очереди. Просто отключил ненужные после инициализации - +20 FPS. Запомните: отработал - отключай.

Шаг второй. Смертельные грехи Update Прогнал по коду — нашёл классику.

FindGameObject или GetComponent в Update? Можно смело бить в лицо. Это функции перебора. Заменил на кэширование в Start - убрал микрофризы.

Сравнение дистанций. Если не нужно показывать расстояние на экране - используйте квадраты. Vector3.Distance внутри - теорема Пифагора с корнем. А корень - это функция подбора, самая затратная. Заменил на sqrMagnitude - и сотни вызовов перестали жрать кадр.

Циклы и вычитание. Тут просто рекомендация,т.к. прирост просто микроскопический, воспринимать как занятный факт. Самая быстрая операция - вычитание. Поэтому for (int i = 100; i > 0; i--) быстрее, чем с инкрементом. А временные отчёты лучше делать вычитанием i -= Time.deltaTime, а не сложением.

Dictionary вместо списков. Да, он жрёт на старте, но поиск - по хешу, а не перебором. Была система состояний с FirstOrDefault - заменил на Dictionary<Transform, State>. При хит-коллайдере - прямое обращение. На порядок быстрее.

Шаг третий. Тяжёлая артиллерия

ComputeShader - для гиперкрутых. Это неграфические расчёты на GPU. GPU делает простейшие операции в тысячи раз быстрее CPU. Но это уровень «договориться с техартистом», самому можно сварганить, но ты не знаешь как техартист будет кушать GPU, решения могут устроить бойню в GPU.

Самое важное — границы ответственности Были кейсы, когда я выставлял определённые настройки производительности (пакетное окклюдирование, LOD-группы, билд-сетап), а разработчик их менял, потому что «его код не вписывался». Результат плачевный. Договаривайтесь на старте. Оптимизация - командная игра. Техартист не враг, он хочет тебе помочь.

Итоги 10% кода решают 50% тормозов. Асинхронность, кэширование, квадраты, словари и выключенные монобехи — вот что спасает, когда артист уже выжал всё из текстур и света.

Всем 60 FPS и никакой магии - только системный подход. И да, FindGameObject /GetComponent в Update/FixedUpdate/LateUpdate - это уголовщина.

Когда Unity Developer немножко виноват. | Сетка — социальная сеть от hh.ru