Борьба с Unity в игре Simantik

Поговорим о страшных вещах в Unity, а именно с оптимизацией больших миров и созданием карт со включённым HDRP в Unity.

Скрин из игры<br />
Скрин из игры

Небольшое предисловие

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

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

А что за игра?

Идея игры появилась у меня уже давно, даже писал статьи о ней пару лет назад, да никак времени на игру не находил, но вот недавно я послал всё куда подальше, накопил денег, уволился с не любимой работы и кинулся с головой в геймдев. Уже скоро готовлю релиз, даже есть группа в ВК и страница в Steam

Вначале была карта...

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

Идея такая:

  • Делаем много заготовок с домиками, деревьями и тд (тайлов грубо говоря)
  • Пишем свою кисточку
  • Рисуем карту, сменяя типы тайлов подобно цветам палитры
  • Бинго!

Значит заготовил я около 150 штук тайлов (кстати не советую хранить их в одной папке, Unity вылетает), но их же ещё раскидать надо.

Вот небольшая кучка самих тайлов<br />
Вот небольшая кучка самих тайлов

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

Борьба с Unity в игре Simantik

В нём то уже есть палитры с разными типами тайлов, полуавтоматическая расстановка (свой новояз проясню ниже) и сохранение в файлик.

И конечно же зелёный квадратик в роли кисти

Борьба с Unity в игре Simantik

Но возникает резонный вопрос: "А что интересного то?"
Тут прописан минимальный анализ высот для каждого из тайлов (домиков, деревьев, травы и тд)
Анализирую высоту при помощи Raycast'ов в 5 точках расположенных в форме квадрата (пятая в центре), а затем смотрим на разницу высоты между точками, в которые попали лучи, просто но эффективно.
На счёт травы, тоже самое только от каждого меша (ищу её по тегу в самом тайле) уходит свой луч, который касается земли и туда уже устанавливает объект травы.

Поделюсь кодом, он простой, но мало ли пригодится:

void Instantiate(RaycastHit hit, GameObject gameObject,float angle) //Метод установки тайла { if (gameObject == null || yMax < hit.point.y || yMin > hit.point.y || !CheckDeltaY(hit)) return; try { GameObject temp = Instantiate(gameObject, hit.point, Quaternion.Euler(0, angle, 0), enviropment.transform) as GameObject; foreach (var i in temp.GetComponentsInChildren<Transform>()) //вот тут начинаем устанавливаем траву и другие детали { if (i.tag != "Detail") continue; if (Physics.Raycast(new Ray(i.position + Vector3.up * 2, Vector3.down), out RaycastHit addHit, 999f, LayerMask.GetMask(terrainLayer))) { i.position = addHit.point; } } } catch { Debug.Log("Instantare Erorr"); } } bool CheckDeltaY(RaycastHit hit) // Метод проверки высот { foreach (var i in new Vector3[] { Vector3.forward, Vector3.right, Vector3.back, Vector3.left }) { if (Physics.Raycast(new Ray(hit.point + Vector3.up * 2 + i * subUnitScale, Vector3.down), out RaycastHit addHit, 999f, LayerMask.GetMask(terrainLayer))) { if (Mathf.Abs(addHit.point.y - hit.point.y) > maxDeltaYPerUnit) return false; } else return false; } return true; } //enviropment - родитель всех наших тайлов //subUnitScale - переменная которая задаёт размер квадрата //maxDeltaYPerUnit - максимально допустимая разность высот

А уже полученные расстановленные объекты, мы просто копируем и ставим в уже в эдиторе (ctrl+c в плей моде, ctrl+v в эдит моде), unity поругается на то что ей не хватает размера кэша, но это не страшно.
Минусы такого метода:

  • Установленные объекты не являются префабами
  • Достаточно случайная карта
  • Неудобно, потому что нужен плей мод (можно в целом и без него, если выполнять скрипты в эдиторе)

Объектов много, а fps мало

Конечный размер игровой зоны 5-6 квадратных километров, с кучей разных объектов, а моя видеокарта начала разогревать воздух до температуры сауны и счёт кадров завис на 15. Не круто....

LOD и Occolusion Culling - использованы, модели - оптимизированы, материалы - собраны в паки, а кадры ниже плинтуса.
Изобретаем следующее решение:

  • Делим всю карту на сетку(матрицу), в каждой ячейке которой храним объекты
  • Включаем объекты только в ближайших к игроку ячейках, а остальные отключаем
  • Проверяем кадры

Крайне простая идея, но дошёл до неё не сразу. Попробую визуализировать выше названный список:

  • Зелёные клеточки - включённые объекты, представим что в центре поля игрок
  • Если игрок ушёл по оси X, то отключаем красные клетки (сторона противоположная движению игрока)
  • А если по оси Y, то синие отключаем (тоже противоположные направлению)
Борьба с Unity в игре Simantik

Конечно изначально все объекты должны быть отключены, иначе когда игрок запустит игру ему придётся ждать покуда система отключит все ненужные объекты, и установит позицию игрока

В Unity выглядит как то так:

Самих объектов только не видно, их уже LOD убрал<br />
Самих объектов только не видно, их уже LOD убрал

Грубо говоря отключаем все объекты, которые не находятся в зелёных клетках (использую нативный метод SetActive).

Самое главное что такая система спокойно масштабируется.

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

Минусы

  • Приходиться заморачиваться и делать лишние действия при билде в виде отключения всех объектов
  • Телепортировать персонажа - неудобно

А с кадрами что?

У меня FPS стабилизировался и просадок не наблюдал, попробовал скинуть билд знакомым с разными конфигами ПК итого:

  • На среднем Ryzen с GTX 1050ti - стабильные 60
  • На встроенном Intel iris G7 - такая же картина
  • На стареньком проце от AMD и не менее старенькой GTX 950, 20-30 кадров, но потому что у неё не хватило видео памяти, а нормальных настроек в момент теста - я не сделал

Без такой системы моя RTX 3060 выдавала 15-20 кадров, поэтому смею утверждать что всё работает хорошо

На небольшие локации, такое решение будет рудементарно, поэтому рекомендую только для больших карт

Ещё скрин из игры<br />
Ещё скрин из игры

Спасибо за удивлённое время!

Если вас заинтересовала игрушка, то вот Группа ВК и Steam, всем буду рад!
P.S. Если понравились мои костыли, то в будущем составлю статью про другие виды извращений (велосипедов) в играх

5858
24 комментария

Комментарий недоступен

12
Ответить

Эм, боюсь спросить, что же такое геймплей для какого нибудь walking simulator, с повествованием через открытый мир.
Ну окей, ты тратишь 10 минут на написание интеракции с объектами, дальше что?
То что такие игры не для всех, и лично я их тоже не очень понимаю, это уже другой разговор.
Но даже если мы говорим о survival играх, то тестирование систем один черт идет параллельно с задачей заставить работать большой мир.

2
Ответить

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

Ответить

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

8
Ответить

Так и не понял причем тут Unity, и зачем тут HDRP?

7
Ответить

Так и не понял зачем hdrp. Он в чистой сцене ест 4мс просто так. При том что качество картинки буквально тоже самое что закинуть ассеты от amplify.
И совершенно не понятно как это исправлять.

3
Ответить

Согласен, HDRP для опенворлда очень не оптимизирован. Я бы посоветовал автору для Built-in хороших бесплатных шейдеров найти и ассеты растительности( на крайняк можно самому пару-тройку деревьев создать и лоды для них, на скечфабе куча бесплатных деревьев), зато гладкие 100+ кадров на древних системах при плотной растительности. На крайняк URP попробовать, но никак не тормозящий HDRP.

2
Ответить