Секретные базы и залы игрока
MCE поставляется с одиночным срезом застраиваемых интерьеров: игрок может войти в инстансированную комнату, которой он владеет, украсить её из каталога, пройти бесконечную Боевую башню, провести переигровки с побеждёнными лидерами залов и даже создать собственный пользовательский зал с командой лидера и пользовательским значком. Всё здесь локально — оно едет в обычном сохранении без миграции схемы.
Посещение баз других игроков, синхронизация мебели по сети и серверно-авторитетное жильё — это задача онлайн-слоя. Эта страница охватывает только одиночные системы. Для сетевой, посещаемой версии см. руководство Жильё игрока.
Все четыре функции доступны из игрового меню паузы (GameMenuScreen, случаи 8–11) — см. руководство Система меню о том, как записи подключены и ограничены.
Обзор
| Функция | Запись в меню паузы | Пост оянный дом (в сохранении) |
|---|---|---|
| Моя База (украшение) | Mi Base (случай 10) | GlobalGameData.SecretBase |
| Мой Зал (пользовательский зал) | Mi Gimnasio (случай 11) | GlobalGameData.PlayerGym |
| Боевая башня (бесконечная) | Torre de Batalla (случай 9) | эфемерная — не сохраняется |
| Бой в зале / переигровка | Gimnasio (случай 8) | ассет GymDefinition (авторский) |
Моя База (Секретная база)
Поток со стороны игрока
Выбор Mi Base ветвится в зависимости от того, где стоит игрок (GameMenuScreen.EnterOrDecorateSecretBase):
- В надземном мире → текущий тайл сохраняется как локация возврата, и игрок телепортируется в инстансированную сцену интерьера через те же варпы
PlayerTeleporter, что используются обычно. - Уже внутри интерьера → открывается внутренняя палитра украшения (
SecretBaseDecorateController.Open).
Интерьер — это одна инстансированная комната. Сохранённый декор игрока отрисовывается при загрузке и повторно синхронизируется при каждом изменении.
Украшение
SecretBaseDecorateController — это управляемый через меню поток размещения/удаления (он повторно использует DialogManager.ShowChoiceMenu — никакого нового UI-фреймворка). Декор размещается на тайле, к которому обращён игрок (та же математика тайла-перед-игроком, что использует предмет «Base Kit»):
- Разместить — выберите декор из палитры каталога, затем он размещается на тайле, к которому обращён игрок.
- Удалить — удаляется любой декор, покрывающий тайл перед игроком (зарезервированный мировой якорь базы защищён и никогда не может быть удалён таким образом).
Вся легальность (границы + перекрытие) живёт в чистом ядре SecretBasePlacement — контроллер лишь собирает выбор игрока и вызывает менеджер.
Модель сохранения
Секретная база — это простой сериализуемый через JsonUtility POCO, который едет в GlobalGameData.sav без миграции формата сохранения (тот же прецедент, что и FollowerData):
[Serializable]
public class PlacedDecor
{
public string DecorId; // opaque id, resolved against a DecorCatalog
public Vector3Int Tile; // footprint origin, in real grid coords
public int FootprintWidth = 1;
public int FootprintHeight = 1;
public int Rotation;
public string CustomData; // opaque per-placement data (variant/colour)
}
[Serializable]
public class SecretBaseData
{
public List<PlacedDecor> PlacedDecor = new();
}
В рамках модели одной базы ячейка-якорь базы в надземном мире сохраняется как PlacedDecor с зарезервированным id (SecretBaseManager.BaseAnchorDecorId) внутри того же списка, так что якорь едет в существующем сохранении без единого нового поля.
Шов времени выполнения
SecretBaseManager — это простой POCO-мост между взаимодействием мира/меню и проверенным ядром SecretBasePlacement плюс сохранением. Он не владеет ни одним из правил размещения — он делегирует каждое решение о легальности чистому ядру и добавляет лишь две обязанности, которые ядро намеренно не берёт на себя: предикат проходимости и сохранение в GlobalGameData.SecretBase.
| Член | Назначение |
|---|---|
TryPlaceBase(Vector3Int cell, SecretBaseData data) | Закрепить базу в ячейке надземного мира (проверяет + сохраняет) |
RemoveBase(Vector3Int cell) | Очистить поле сохранения одной базы |
AddDecor(string decorId, Vector3Int tile, int rotation = 0) | Разрешить по Catalog, проверить против InteriorBounds, добавить в сохранение |
RemoveDecorAt(Vector3Int tile) | Удалить декор, покрывающий тайл (отказывает якорю) |
MoveDecor(Vector3Int from, Vector3Int to) | Удалить-затем-разместить-заново; откатывается при недопустимой цели |
InteriorBounds (RectInt) | Единственное правило размещаемости, которое использует интерьер |
Catalog (DecorCatalog) | Разрешитель id → префаб/спрайт/след |
OnBasePlaced / OnBaseRemoved / OnDecorChanged | События, которые слушает визуал мира/интерьера |
Настройка как разработчик
Системы кодово завершены; подключение сцен и ассетов — задача разработчика. Здесь ничего не выдумано — каждый тип ниже является настоящим классом в Assets/OpenMon/Core/Runtime/World/.
1. Создайте каталог декора
Создайте ассет DecorCatalog (меню MCE ▸ World ▸ Decor Catalog). Каждый DecorEntry — это простой сериализуемый держатель:
[Serializable]
public class DecorEntry
{
public string DecorId; // stable, opaque, unique in the catalog
public string DisplayNameLocKey; // localization key shown in the palette
public GameObject Prefab; // spawned when the interior is populated
public Sprite Icon; // palette/menu icon
public int FootprintWidth = 1;
public int FootprintHeight = 1;
}
Каталог — это песочный разрешитель: каждая запись всегда доступна — в этом срезе нет флагов разблокировки, экономики или ограничений. Поиск O(1) через TryGet(decorId, out entry).
2. Постройте сцену интерьера
Добавьте SecretBaseInteriorLoader в сцену интерьера. Это единственный MonoBehaviour, владеющий комнатой: он конструирует SecretBaseManager времени выполнения, привязанный к живому GlobalGameData, передаёт ему швы интерьера (DecorCatalog и прямоугольник InteriorBounds), публикует его в SecretBaseRegistry и SecretBaseInteriorSession, затем инстанцирует один префаб на каждый PlacedDecor и повторно синхронизируется при OnDecorChanged.
Сериализуемые поля для назначения:
| Поле | Значение |
|---|---|
catalog | DecorCatalog, переданный менеджеру |
interiorBounds | Прямоугольник комнаты, в тайловых координатах этой сцены (по умолчанию 8×8) |
grid | Сцена Grid для отображения тайл→мир (откатывается к тождественным координатам) |
decorContainer | Родитель для инстанцированного декора (по умолчанию — transform загрузчика) |
arrivalMarker | Авторский маркер того, где прибывает игрок |
Загрузчик реализует IPlayerDataReceiver, потому что PlayerDataInstaller привязывает GlobalGameData с .WhenInjectedInto<IPlayerDataReceiver>(). Без этого маркера [Inject] молча пропускается, и интерьер отрисовывается пустым во время выполнения, хотя все офлайн-тесты проходят.
3. Подключите запись меню паузы
На префабе GameMenu (GameMenuScreen) назначьте:
secretBaseInteriorScene—SceneInfoAssetшаблона интерьера.secretBaseInteriorSpawn— тайл, на который прибывает игрок (должен находиться внутриinteriorBounds).
Пока secretBaseInteriorScene не назначен, шаг входа Mi Base является безопасной пустой операцией. Тайл возврата захватывается в GlobalGameData.SecretBaseReturn и проходит туда-обратно через сохранение, так что динамический выход из интерьера (ExitSecretBaseInterior) возвращает игрока точно туда, откуда он ушёл.
4. (Опционально) Показать базу в надземном мире
SecretBaseSpawner превращает OnBasePlaced / OnBaseRemoved в видимый префаб. Привяжите менеджер времени выполнения через SetManager(...) (что также регистрирует его в SecretBaseRegistry, чтобы предмет «Base Kit» мог его разрешить) и создайте baseVisualPrefab. В рамках модели одной базы новое размещение сначала деспаунит любой предыдущий визуал базы. Спаун/деспаун/отображение сетки — это внедряемые швы (SetSpawnFactory, SetDespawner, SetCellToWorld), так что поток тестируется в edit-mode без живой сцены.
Мой Зал (зал, принадлежащий игроку)
Mi Gimnasio (случай 11) — это вариант зала потока базы. Он повторно использует ровно тот же движок декора — SecretBaseData Room, проверяемая тем же чистым ядром SecretBasePlacement — и добавляет специфичные для зала данные, о которых база не имеет понятия.