MapLevels
Namespace: Vortex.Sdk.MapLevels
Assembly: ru.vortex.sdk.maplevels
Активация
Пакет активируется через SdkSettings (меню Tools → Vortex → Configs → SDK Settings), тоггл mapLevelsSdk. Тоггл управляет define-символом USING_VORTEX_MAP_LEVELS, который указан в defineConstraints asmdef'а — при выключении пакет не компилируется и его типы недоступны.
Канон описания активации SDK-пакетов: Vortex/Sdk/SdkSettingsSystem/README.ru.md.
Файл-расширение тогглера: DefineSettings/SdkSettings.MapLevels.cs — partial-кусок к Vortex.Sdk.SdkSettingsSystem.SdkSettings.
Назначение
Управление графовой картой уровней с ленивой инстанциацией префабов и стримингом соседей. Активный уровень переключается через Enter(guid), прямые соседи (hop=1) подгружаются в фоне, дальние (на дистанции >= unloadDistance) выгружаются.
Возможности:
- Каталог уровней строится из
Database.GetRecords<MapLevelModel>()приInit - Ленивая синхронная инстанциация префаба активного уровня
- Фоновый стриминг соседей через
UniTask.Yield(распределение по кадрам) - BFS по графу
NeighborGuidsс ограничением глубины удержания - Подменяемая реализация контроллера через
MapLevelsSettings.controllerTypeName(рефлексия) - Сохранение текущего уровня в
MapLevelsGameData(частьGameModelчерезIGameData) - Регистрация контейнера-родителя сцены через
MapsView/RegisterMapsParent - Автоматическая переброска инстансов между сценовым контейнером и hidden-pool (
MapVoid)
Вне ответственности:
- Содержимое уровня (NPC, объекты, логика) — задаётся префабом
- Точки спауна между уровнями — описание в
MapGate, обработка перехода — задача проекта - Адресная загрузка ассетов — префаб уровня хранится прямой ссылкой в
MapLevelPreset
Зависимости
Core
Vortex.Core.AppSystem—App,AppStatesVortex.Core.DatabaseSystem—Database,Record,RecordPresetVortex.Core.SettingsSystem—Settings,SettingsModel(partial-расширение)Vortex.Core.System.Abstractions—Singleton<T>Vortex.Core.Extensions.LogicExtensions—IsNullOrWhitespace,InitValve,[POCO],SerializePropertiesVortex.Core.Extensions.ReactiveValues—EnumData,StringData,IReactiveData
Unity
Vortex.Unity.SettingsSystem—SettingsPresetVortex.Unity.DatabaseSystem—RecordPreset<T>,[DbRecord]Vortex.Unity.EditorTools—[ValueSelector]Vortex.Unity.UI.StateSwitcher—UIStateSwitcher,[StateSwitcher]Vortex.Unity.Extensions.ReactiveValues—Vector2Data
Sdk
Vortex.Sdk.Core.GameCore—GameController,GameModel.IGameDataVortex.Sdk.SdkSettingsSystem—[DefineSymbol]
Внешние
- UniTask — асинхронный стриминг
- Sirenix Odin Inspector — инспектор-атрибуты
MapGate
Архитектура
ru.vortex.sdk.maplevels
├── Abstractions/
│ └── IMapObject.cs ← базовый контракт объекта карты
├── Bus/
│ └── MapLevelsBus.cs ← статическая шина: Controller, Config, MapsParent
├── Config/
│ ├── MapLevelsSettings.cs ← SettingsPreset (unloadDistance, controllerTypeName)
│ └── SettingsModelExt/
│ ├── MapLevelsConfig.cs ← runtime-конфиг (POCO)
│ └── SettingsModelExtMapLevels.cs ← partial SettingsModel.MapLevels
├── Controllers/
│ ├── MapLevelsController.cs ← Singleton-каркас, IsInitialized, Model
│ ├── MapLevelsController.Lifecycle.cs ← Init / Cleanup, BuildCatalog, привязка GameController
│ ├── MapLevelsController.Active.cs ← Enter, переключение активного уровня
│ └── MapLevelsController.Streaming.cs ← BFS, EnsureLoaded, Unload, ApplyStreamingPlan
├── DefineSettings/
│ └── SdkSettings.MapLevels.cs ← partial SdkSettings, тогглер mapLevelsSdk
├── Interfaces/
│ ├── IMapLevelsController.cs ← публичный контракт контроллера
│ └── IMapLevelView.cs ← Editor-only поставщик MapGate[]
├── Model/
│ ├── MapContainer.cs ← per-level: GameObject Instance + EnumData<State>
│ ├── MapContainerState.cs ← Empty / Loading / Loaded / Unloading
│ ├── MapGate.cs ← struct: id, gatePoint, target (mapID||gateID)
│ ├── MapLevelModel.cs ← Record: Prefab, NeighborGuids
│ ├── MapLevelViewModel.cs ← POCO под сериализуемое состояние представления
│ ├── MapLevelsGameData.cs ← GameModel.IGameData: CurrentLevelGuid
│ └── MapLevelsModel.cs ← runtime-агрегатор: Catalog, Containers, ActiveLevelGuid
├── Presets/
│ └── MapLevelPreset.cs ← RecordPreset<MapLevelModel>: Prefab, NeighborGuids (Singleton)
└── View/
├── MapLevelView.cs ← MonoBehaviour на префабе уровня, переключает UIStateSwitcher
└── MapsView.cs ← регистратор сценового контейнера-родителя
Компоненты
| Класс | Тип | Назначение |
|---|---|---|
MapLevelsBus |
static | Шина: Controller, Config, MapsParent, OnReady, OnRelease |
MapLevelsController |
Singleton<T>, IMapLevelsController, partial |
Реализация по умолчанию: каталог, активный уровень, стриминг |
IMapLevelsController |
interface | Контракт контроллера: Init, Enter, Cleanup + события |
MapLevelsModel |
IReactiveData |
Runtime: Catalog, Containers, ActiveLevelGuid |
MapLevelsGameData |
GameModel.IGameData, [POCO] |
Сохраняемое состояние: CurrentLevelGuid |
MapLevelModel |
Record |
Рабочая копия уровня: Prefab, NeighborGuids |
MapLevelPreset |
RecordPreset<MapLevelModel> |
Пресет уровня (фиксирован как Singleton) |
MapContainer |
sealed class | Per-level: Instance, EnumData<MapContainerState> |
MapContainerState |
enum | Empty, Loading, Loaded, Unloading |
MapGate |
struct | Точка спауна: id, gatePoint, target (mapID‖gateID) |
MapLevelView |
MonoBehaviour, IMapLevelView |
На префабе уровня — переключает UIStateSwitcher (On/Off) |
MapsView |
MonoBehaviour |
Регистрирует/снимает сценовой MapsParent в шине |
MapLevelsSettings |
SettingsPreset |
unloadDistance (1–16), controllerTypeName |
MapLevelsConfig |
POCO | Runtime-конфиг (генерируется из MapLevelsSettings) |
IMapObject |
[POCO] interface |
Базовый контракт объекта карты (Vector2Data Position) |
Контракт
Вход
MapLevelsBus.Controller.Enter(guid, gateId = null)— переключение активного уровняMapLevelsBus.RegisterMapsParent(Transform)/UnregisterMapsParent(Transform)— привязка контейнера сценыMapLevelsSettings—unloadDistance,controllerTypeName(резолв черезIMapLevelsController)
Выход
MapLevelsBus.OnReady(InitValve) — контроллер инициализированMapLevelsBus.OnRelease— контроллер очищенMapLevelsBus.OnMapsContainerRegistered/OnMapsContainerReleased— контейнер сцены привязан (RegisterMapsParent) / отвязан (UnregisterMapsParent)IMapLevelsController.OnActiveLevelChanged(guid, gateId)— смена активного уровняIMapLevelsController.OnLevelLoaded(guid)/OnLevelUnloaded(guid)— события стримингаMapLevelsModel.ActiveLevelGuid—StringDataдля подписки
Гарантии
Initидемпотентен: повторный вызов на инициализированном контроллере — no-opEnterсинхронно гарантируетLoadedдля целевого уровня перед сменойActiveLevelGuid- Стриминг соседей — async-fire-and-forget, проверяет
IsInitializedи совпадениеActiveLevelGuid OnActiveLevelChangedне диспатчится повторно для того жеguid- При загрузке (
OnLoadGame) контроллер сбрасывает все инстансы и заходит вCurrentLevelGuid MapLevelPreset.OnValidateфорситRecordTypes.Singleton
Ограничения
MapLevelPresetфиксирован какSingleton— на каждый пресет одна runtime-копия- Префаб хранится прямой ссылкой (не Addressable) — все префабы попадают в билд
- При отсутствии сценового
MapsParentинстансы складируются в hiddenMapVoid(DontDestroyOnLoad) - Резолв
controllerTypeNameидёт через рефлексию по всем сборкам — тип должен иметь публичный конструктор без параметров
Поток жизненного цикла
[RuntimeInitializeOnLoadMethod]MapLevelsBus.Bootstrapподписывает создание контроллера наSettings.OnInitSettings.OnInit→CreateController→ читаетSettings.Data().MapLevels, резолвит тип, создаёт черезActivator.CreateInstance, вызываетInitInitстроитCatalogиз БД, подписывается наGameController.OnNewGame/OnLoadGame, выставляетIsInitialized = true, вызываетEnterCurrentFromGameDataEnter(guid)→EnsureLoaded(синхронная инстанциация) →ActiveLevelGuid.Set→OnActiveLevelChanged→ApplyStreamingPlan(async)ApplyStreamingPlan— BFS, инстанциацияhop=1, выгрузка>= unloadDistanceApp.OnExit→Dispose(отписка)
Пример: создание уровня
// 1. Создать MapLevelPreset через меню Database/MapLevel Preset
// Назначить prefab и neighborGuids (через DbRecord-селектор)
// 2. На корне префаба — MapLevelView с UIStateSwitcher (SwitcherState.On/Off)
// и опциональным массивом MapGate
// 3. На сцене — GameObject с MapsView (контейнер для инстансов уровней)
// 4. Переключение из логики проекта:
MapLevelsBus.OnReady.Subscribe(() =>
{
MapLevelsBus.Controller.Enter(targetLevelGuid, gateId: "spawn_north");
});
Пример: подмена контроллера
// Реализация IMapLevelsController в проекте
public sealed class MyMapLevelsController : Singleton<MyMapLevelsController>, IMapLevelsController { ... }
// В MapLevelsSettings → controllerTypeName выбрать через ValueSelector
// (поставщик типов сканирует домен и фильтрует по IMapLevelsController + ctor())
Граничные случаи
controllerTypeNameпуст → используетсяMapLevelsControllerпо умолчаниюcontrollerTypeNameне резолвится →LogError, контроллер не создаётся,IsReady = falseEnterдоInit→LogError, no-opEnterс неизвестнымguid→LogError, no-opMapsParentуже зарегистрирован при повторной попытке →LogError,RegisterMapsParentвозвращаетfalseApp.GetState() == AppStates.Stoppingпри переброске инстансов → инстансы уничтожаются вместо переносаunloadDistance <= 1→ удержание только активного уровня; соседи hop=1 загружаются, но тут же выгружаются на следующемEnter