Гайд Стриминг уровней в UE4

Забаненный
Статус
Оффлайн
Регистрация
3 Авг 2020
Сообщения
284
Реакции[?]
170
Поинты[?]
3K
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
Стриминг уровней в UE4
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.




Стриминг уровней — это загрузка уровней и их контента "на лету", что дает возможность создавать бесшовный (seamless) геймплей и левел-дизайн, не прерывая его на экраны загрузки. Позволяет заранее загружать уровни в память, чтобы быстро их отображать и прятать, создавая иллюзию непрерывности повествования. Как, например, в God of War.
Persistent уровень — основной уровень, который загружается на старте и в структуре которого будут содержаться все подуровни.
Подуровни — все уровни, которые будут загружаться и отображаться по необходимости.



Always loaded — тип загрузки подуровня, название говорит само за себя — уровень загружается вместе с persistent и не выгружается, пока существует Persistent.
Blueprint — тип загрузки подуровня, название также говорящее — уровень загружается и выгружается из логики в blueprints.



Основных инструментов стриминга уровней в UE4 четыре:
  • Streaming Volumes
  • Load Streaming Level
  • Create Level Instance
  • World Composition
Их мы и рассмотрим отдельно и подробно. К данной статье в качестве доп.материалов идет
Пожалуйста, авторизуйтесь для просмотра ссылки.
c PDF презентацией. Все скриншоты сделаны из него.
Инструмент Streaming Volumes
Позволяет загружать подуровни, настраивая триггеры типа (и названия) Level Streaming Volumes. Сразу сделаю ремарку — данный способ не загружает и выгружает подуровни, а меняет для них свойство Hidden, что отключает видимость и коллизии, но при этом все подуровни — загружаются с загрузки persistent уровня. По этому я буду применять термин Выключает вместо Выгружает, и для Загрузки соответственно — Включение. Этот способ — самый простой в подготовке и реализации.



Подготовка
  1. Создать Persistent уровень.
  2. Создать подуровни с необходимым контентом, учитывая переходы между подуровнями в фиксированных местах.
  3. Добавить их в persistent (либо Drag'n'Drop из Content Browser, либо Add Existing в меню Levels окна Levels).
  4. Подуровни должны быть Always Loaded (тип загрузки).
  5. Расставить и настроить Level Streaming Volumes именно в persistent уровне.


Чтобы открыть окно Levels, нужно найти его в меню Window

Настройка
Теперь нужно добавить в Persistent уровень Level Streaming Volume акторы и настроить их. Их можно добавлять в уровень из окна Modes, вкладки Volumes.



Чтобы видеть в World Outliner в каком уровне или подуровне сейчас находится актор, можно поставить отображение Level во всплывающем меню аутлайнера.



Теперь в каждом Streaming Volume необходимо настроить, какой уровень он включает и выключает. В моём примере первый триггер, пока игрок стоит в нем, должен включать подуровни Level_A и Level_B.


Второй — Level_B и Level_C.


И третий — Level_C и Level_D.



На этапе подготовки я покрасил комнаты в разный цвет, чтобы удобнее ориентироваться — какая из них должна быть включена, а какая — выключена.
Как только Player Character войдет в Streaming Volume — тот в свою очередь отобразит уровни, указанные в его настройках. Если с Level Streaming Volume-ом нет пересечений у Player Character (в том числе, его Camera Component) — он выключает указанные в нем уровни. В результате, когда я прихожу Player Character-ом красную комнату (Streaming Trigger 3), выключаются Level_A и Level_B с зеленой и фиолетовой комнатами.



Выводы
Плюсы:
  • Без блюпринтов вообще, только настройка.
  • Можно запекать свет (весь контент может быть Static).
  • Корректно работают по сети out of box.
Минусы:
  • Триггеры срабатывают на Camera Component.
  • Контент в подуровнях необходимо заранее расставлять с учетом структуры подуровней.
  • Уровни загружены изначально и у них меняется только свойство hidden (уровень невидим, коллизии выключены, но он — загружен в память системы при загрузке persistent уровня).
Функция Load Streaming Level
Данный метод позволяет загружать подуровни по имени (подуровня) при вызове функции Load Streaming Level. И в данном случае именно загружать. Уровни не загружены при старте persistent, и на их загрузку по триггеру и блюпринт-логике необходимы ресурсы вашей системы, следовательно, если уровень большой или в нем много контента — могут быть просадки FPS, будьте внимательны и правильно распределяйте контент по подуровням.



Подготовка
  1. Создать Persistent уровень.
  2. Создать подуровни с необходимым контентом, учитывая переходы между подуровнями, в фиксированных местах.
  3. Добавить их в persistent.
  4. Подуровни должны быть типа загрузки Blueprint.
  5. Создать свой blueprint-триггер с логикой, завязанной на Load Streaming Level.
  6. Расставить и настроить свои кастомные blueprint-триггеры. Первый blueprint-триггер должен быть в persistent, чтобы начать цепочку загрузок уровней. Остальные могут располагаться в подуровнях или в persistent, зависит от удобства.
Настройка
В подготовке я уже сказал, что нужно расставить blueprint-триггеры, но не рассказал, что же должно быть у них внутри, какая логика. Срочно исправляю это упущение!
Я создал актора с именем BP_CustomStreamingTrigger, он унаследован от обычного actor класса. Перечислю, что я в него добавил.



BP Custom Streaming Trigger — переменные
Я добавил три Instance Editable переменные, чтобы их можно было изменять непосредственно в сцене для выбранного актера:
Extent — 3D-Vector, который я использую в Construction Script, чтобы управлять размером триггера (компонент Box) в уровне при настройке.
LevelsToLoadNames / LevelsToUnloadNames — Array (список) переменных типа Name, этот список заполняется точными именами уровней, которые необходимо загрузить / выгрузить, когда Player Character попадает в триггер.
А также четыре переменных для работы логики:
CurrentLevelToLoad / CurrentLevelToUnload — переменные типа Name, хранят имя текущего уровня на загрузку / выгрузку.
CurrentLoadlevelID / CurrentUnloadlevelID — переменные типа Int, хранят индекс уровня на загрузку/выгрузку.
BP Custom Streaming Trigger — логика
В Event Graph-е происходит не очень много.



Сразу замечу, что логика не завернута в функции по двум причинам.
Первая: это быстрое решение, чтобы показать результат, ну и — всегда есть куда рефакторить.
Вторая: базовая функция движка Load Stream Level (как и Unload Stream Level) — является Скрытой (или Отложенной), это означает, что после того как функция была вызвана и до того как она завершит свою работу — повторные вызовы execution flow через неё не пройдут, и всё, что стоит на выполнении после неё — не выполнится. А когда она выполнится — исполнение пойдет от первого вызова (все последующие — не запомнятся).



В данной реализации по соприкосновении с blueprint-триггером (Begin Overlap) любого актора срабатывает сперва загрузка первого из списка на загрузку уровня, а следом — выгрузка первого из списка на выгрузку, соответственно. Если делать рефакторинг, то точно стоит определить, кто может взаимодействовать с этим триггером (либо через коллижн пресет, либо через проверку актора на выходе события Begin Overlap), имейте это ввиду.
После первых запусков загрузки / выгрузки уровней пойдет выполнение следующих, если в списках таковые есть.
В результате, когда я нахожусь Player Character-ом в зеленой комнате (BP_CustomStreamingTrigger_AB), загружены Level_A и Level_B с зеленой и фиолетовой комнатами, и выгружены Level_C и Level_D с желтой и красной.



Выводы
Плюсы:
  • Свой кастомный код, достаточно простой, и своя настройка оверапов.
  • Можно запекать свет (весь контент может быть Static).
  • Корректно работают по сети out of box.
Минусы:
  • Контент в подуровнях необходимо заранее расставлять с учетом структуры подуровней.
  • Требуется простейшее знание Blueprints.
Функция Create Level Instance
Данный способ позволяет загружать множество инстансов (копий) уровней из заготовок (подуровней), которые также можно вращать и подставлять в любые необходимые вам координаты. Проще говоря — можно делать рандомно-генерируемые подземелья по созданным паттернам.



Подготовка
  1. Создать Persistent уровень.
  2. Создать подуровни-заготовки с необходимым контентом, выставлять точки выходов из подуровней, а точка входа — либо в нулях, либо — сложная система определения смещения уровня (в примере — точки входа в нулях).
  3. Подуровни должны быть Blueprint (тип загрузки).
  4. Создать свой Blueprint-триггер с логикой, завязанной на Create Instance.
  5. Создать свой Blueprint, который будет служить точкой выхода из уровня.
  6. Создать логику глобального менеджера стриминга уровней (можно реализовать её в кастомном GameMode).
  7. Расставить и настроить все свои кастомные Blueprint акторы по всем подуровням-заготовкам.
Настройка
Давайте разберем те кастомные Blueprint-акторы, что я упомянул в подготовке. В первую очередь, в данном методе необходимы два актора:
BP_Connection — будет указывать координаты и направление для загрузки инстанса (копии) подуровня-заготовки.
BP_CreateLevelInstance — аналогичный актору BP_CustomStreamingTrigger из предыдущего примера, но со своей логикой.
И немножко логики появилось в Level Blueprint нашего persistent и кастомном Game Mode.
BP Connection
Начнем с самого простого — актор BP_Connection.



В нём нет никакой собственной логики, только два компонента: StaticMesh и Arrow. Он просто является более удобной (на мой взгляд) альтернативой такому актеру как Target Point. Единственное, в нём есть Bool переменная Available, которая нужна, чтобы на занятом какой-то комнатой BP_Connection не появилось новой комнаты. По умолчанию она True.
BP Create Level Instance — переменные
Дальше смотрим на BP_CreateLevelInstance.



BP Create Level Instance — логика
У него в Construction script (как и у BP_CustomStreamingTrigger) — логика для настройки размера триггера в сцене.
В Event Graph есть проверка на Character и после — вызов кастомной функции ManageLevelInstance.
Ещё у этого актера есть функция CustomLevelReset, но о ней чуть позже.
В ManageLevelInstance же происходит следующее:
В начале кастомный Do Once (вы можете использовать обычный макрос Do Once, если вам так удобнее) и сохранение референса Game Mode в переменную.


Дальше — цикл, который проходит по всем указанным в уровне с blueprint-триггером коннекторам (BP_Connection).


Тело цикла делает следующее:
Сперва проверяет — доступен ли для выставления комнаты коннектор и если доступен, то для начала — выключить эту доступность.


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


И вызывает функцию Create Instance, на вход которой нужен объект уровня (берем его по имени), и уникальное (это важно) имя инстанса. И чтобы создать уникальное имя инстанса, и чтобы оно было всегда новым, я реализовал увеличение индекса в Game Mode (следующий скриншот из Game Mode), что сделало этот счетчик для уникальных имен инстансов — глобальным (для игровой сессии).



И в финале тела цикла — я добавляю ссылку на созданный инстанс в список (Array) всех загруженных уровней (чтобы была возможность их всех разом выгрузить), в качестве координат (Transform) передаю местоположение и направление текущего Коннектора, для которого выполняется данное тело цикла и после — загружаю и делаю видимым инстанс уровня.



Что осталось сделать? Правильно выставить комнаты в подуровнях — вход в комнату должен быть в нулевых координатах мира. На скриншоте фиолетовая комната с одним выходом — в persistent уровне и она стартовая. Красная же комната — это подуровень Level_D, ассеты которой так выставлены, чтобы вход в комнату был в нулевых координатах мира.



Чтобы стало понятнее — если включить отображение всех уровней будет такое:



И настройка blueprint-триггера BP_CreateLevelInstance с указанием в нем всех выходных BP_Connection выглядит примерно так:



И последний штрих — я добавил возможность "Сбросить состояние подземелья".
В Event Graph блюпринта persistent уровня я на клавишу «1» вызываю функцию CustomReset, которая принадлежит GameMode.



В этой функции в цикле я прохожусь по всем BP_CreateLevelInstance, у них беру список всех ими созданных инстансов подуровней, все их выгружаю и чищу этот список. И в завершении — беру BP_CreateLevelInstance из persistent уровня которому добавил тег FirstOne, и для него вызываю ManageLevelInstatnce функцию (по сути — запускаю загрузку первой комнаты после стартовой).



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



Выводы
Плюсы:
  • Свой кастомный код, но более сложный, своя настройка оверапов.
  • Множество копий заготовленных подуровней.
  • Рандомная генерация на лету — "огонь ваще".
Минусы:
  • Запечь свет не получится, если только это не глухие помещения (например, подземелья), т.к. вы будете крутить уровень. Или — не использовать rotation, только location для выставления подуровня.
  • Либо все подуровни выставлять в нулях, либо продумывать сложную систему смещений уровня.
  • Гораздо больше всего нужно предусматривать в своем коде, в т.ч. проверки, чтобы подуровни не появлялись там, где уже есть загруженные подуровни.
  • По сети — всё очень плохо и сломано out of box (чинить репликацию надо в “плюсах”).
Бесшовный открытый мир с World Composition
И последний из представленных мной способов — совокупность лендскейпов (террейнов), каждый из которых является подуровнем и подгружаются они за счет настроек расстояния.
Подготовка
  1. Создать Persistent уровень.
  2. Включить для него в World Settings опцию Enable World Composition.
  3. Создать первый подуровень (в окне LevelsNew Level).
  4. В первом подуровне создать первый ландшафт (в окне ModesLandscape) .
  5. На его основе создавать все последующие ландшафты, которые редактируются совместно (ПКМ на ландшафте — подменю LandscapeAdd Adjacent Landscape Level — выбрать направление).
  6. Создать свой слой и установить дистанцию от камеры для стриминга уровня.
  7. Назначить всем подуровням созданный слой.
Настройка
Подробнее разберем настройки, необходимые, чтобы весь этот World Composition настроить. Сперва — Enable World Composition, включить его можно тут:



Создать первый террейн можно в ModesLandscape:



Чтобы увидеть композицию ваших террейнов, нужно открыть окно World Composition по кнопке Summon world composition окна Levels:



Чтобы добавить элемент террейна, нужно в World Composition сперва сделать Load для террейна, который вы хотите взять для исходный, а дальше — Add Adjacent Landscape Level и выбор направления, куда добавить относительно этого террейна новый участок.



Редактор предложит назвать и сохранить новый уровень с террейном. И вот добавленный террейн, который можно редактировать как один с остальными (бесшовно).



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



А тут — в дистанции загрузки:



Выводы
Плюсы:
  • Без блюпринтов вообще, только настройка.
  • Можно запекать свет (весь контент может быть Static) или спокойно пользоваться динамическим освещением, это ж “опенворлд”!
  • Система сделана для удобства работы с ландшафтами, и именно с ними — идеальный вариант.
  • Корректно работают по сети out of box.
Минусы:
  • Триггеры срабатывают на Camera Component.
  • Контент в подуровнях необходимо заранее расставлять с учетом структуры подуровней.
Фишечки
Фишечка с террейнами
Можно импортировать в проект заготовленный в стороннем ПО и разделенный на сектора террейн, главное — именование секторов должно соответствовать правилу <name>X<n>_Y<n>.



Фишечка с C++
В плюсовом коде есть возможность сделать обертку функции Create Level Instance, которой на вход можно передавать путь к нужному подуровню, что дает возможность не добавлять в persistent уровень необходимые подуровни. Следовательно, можно пользоваться одним entry подуровнем с player spawner, а всё остальное подгружать на лету.
Но тут нужно будет ещё чинить репликацию в C++, так же как с Create Instance функцией.
Фишечка с освещением
Уровень может быть использован как Light Scenario (сценарий освещения), что позволяет запечь несколько видов освещения для одного и того же пространства. Но есть нюансы со sky light и sky sphere.



Заключение
У каждого способа свои плюсы и минусы и, конечно, нужно смотреть в первую очередь на ваши потребности (геймдизайн), но фича, которая позволяет геймдизайнеру дополнительно поддерживать flow и иммерсивность игрового процесса за счет того, что игрок не прерывается на экраны загрузки — стоит многих часов разработки, а все вышеперечисленные методы различаются больше именно сложностью разработки и поддержки.
Текст: Антон Токарев
Источник:
Пожалуйста, авторизуйтесь для просмотра ссылки.
 
Начинающий
Статус
Оффлайн
Регистрация
28 Авг 2020
Сообщения
6
Реакции[?]
0
Поинты[?]
0
Очень подробный урок/статья по стримингу в ue4 - мне пригодилось! Автору респект)
 
Начинающий
Статус
Оффлайн
Регистрация
7 Сен 2020
Сообщения
2
Реакции[?]
0
Поинты[?]
0
В способе с World Composition не могу победить проблему с загрузкой подуровня. Т.е. персонаж загрузился а уровень не успел и как следствие перс летит в неизвестность((. Если кто может подскажите где глянуть решение проблемы.
 
Начинающий
Статус
Оффлайн
Регистрация
28 Окт 2020
Сообщения
11
Реакции[?]
1
Поинты[?]
0
Если бы мог, влепил бы жирный лайк. Полезная статья.
 
Сверху Снизу