Перейти к основному содержимому

Система сохранений

MCE использует систему сохранений на основе JSON с именованными сохранениями, атомарной записью, ротацией автосохранений, версионированием и расширяемостью. Система сохранений хранит всё состояние игры игрока: команду, инвентарь, позицию, значки, прогресс квестов и игровые переменные.

Настройки, связанные с сохранением

Три переключателя на этом экране относятся к данной системе: Partida guardada выбирает формат сериализации, Autoguard. historia решает, записываются ли сюжетные моменты автоматически, а Subir a la nube — тот, что спрашивает разрешения, прежде чем данные покинут машину.

Архитектура​

Система сохранений состоит из нескольких компонентов:

КомпонентНазначение
SavegameManagerУправляет операциями сохранения/загрузки (ScriptableObject, привязанный через SavegameInstaller)
SavableObjectБазовый класс (ScriptableObject) для любого объекта, который хранит данные: SaveToText, LoadFromText и ResetSave
MCESavesSerializerJSON-сериализатор, используемый для каждого сохранённого текста
GameVariablesХранилище именованных переменных: булевых, целых, вещественных и строк
GameVariableHolderSavableObject, владеющий хранилищем GameVariables (GlobalGameData и каждый SceneInfoAsset — держатели)
GameVariableReferenceСсылка в Inspector на одну переменную, используется узлами CommandGraph
Migrations/ISaveMigration, SaveMigrationPipeline и SaveFormatVersions: версионирование сохранений

Что сохраняется​

SavegameManager.SavegameObjects — список сохраняемых объектов, входящих в каждое сохранение. Собственные объекты движка:

ОбъектСодержит
GlobalGameData (сохраняется как SavableGlobalGameData)Позиция игрока, деньги, полученные значки, игровые переменные, метка версии формата, секретная база и зал игрока
RosterМонстры команды с их полным состоянием (вид, уровень, IV, EV, характер, приёмы, PP, HP, статус, дружба...)
MonsterStorageМонстры в боксах
BagПредметы и количества
DexУчёт увиденных/пойманных по видам
QuestManagerАктивные квесты, выполненные цели, полученные награды
CharacterDataПерсонаж-игрок
TimeManagerВремя суток
NurseryПитомник для разведения
SceneInfoAssetСобственные игровые переменные каждой карты

Файлы сохранений​

Сохранение — это папка, а не один файл. Сохранения находятся в Saves/ внутри Application.persistentDataPath Unity:

ПлатформаpersistentDataPath
Windows%USERPROFILE%/AppData/LocalLow/<CompanyName>/<ProductName>/
macOS~/Library/Application Support/<CompanyName>/<ProductName>/
Linux~/.config/unity3d/<CompanyName>/<ProductName>/
AndroidВнутреннее хранилище (приватное для приложения)
iOSПесочница приложения
<persistentDataPath>/Saves/
2026-10-10-18-30-02/ a manual save, named with the current date
GlobalGameData.sav one .sav per savable object
Roster.sav
...
Manifest.txt the list of files a COMPLETE save must contain
Autosave/
OlderAutosave/

Сохранения записываются так, чтобы быть устойчивыми к сбоям:

  • Каждый файл записывается атомарно (во временный файл, который затем заменяет исходный).
  • Всё сохранение сначала записывается в папку <name>.staging и подставляется на место только после записи всех файлов и манифеста. Прерванное сохранение никогда не оставляет наполовину записанную смесь старого и нового состояния.
  • При загрузке отсутствие файла, указанного в манифесте, означает, что сохранение было прервано, поэтому оно отклоняется, а не загружается частично.

Сохранение и загрузка​

Игрок сохраняется из меню паузы (пункт Exit предлагает сохранить, а клавиша сохранения в меню запрашивает подтверждение и записывает сохранение с именем по текущей дате) и загружается из главного меню (Continue загружает последнее сохранение, Load перечисляет все). Когда игра работает онлайн (MCE Online), прогресс хранит сервер, а локальное сохранение не записывается.

Из кода — через SavegameManager (редакции Source и Studio):

// A new save named with the current date
yield return savegameManager.SaveGameWithCurrentDate(playerCharacter);

// Rotate the autosave
yield return savegameManager.Autosave(playerCharacter);

// Load by name, or the most recent one
yield return savegameManager.LoadGame(saveName);
yield return savegameManager.LoadLastSavegame();

SavegameManager.GetAllSavegames(out list) и AreThereSavegames() сообщают, что есть на диске. Тестовые состояния сохранений (SaveStates) лежат рядом с сохранениями игрока под собственным префиксом и скрыты из всех списков, которые видит игрок.

Фасад сохранений SDK

engine.SaveSystem (ISaveSystem) предоставляет Save(slot), Load(slot), HasSaveData(slot), DeleteSave(slot), OnSaved и OnLoaded, но в текущем движке он не подключён к SavegameManager: ни один обработчик не обрабатывает его запросы сохранения и загрузки, а HasSaveData ищет в persistentDataPath файл slot{N}.sav, который движок не записывает. Для настоящих сохранений используйте SavegameManager (редакции Source и Studio) или игровые меню. См. ISaveSystem.

Автосохранение​

Две опции на экране Options (хранятся в GameplayConfigurationFile) управляют автосохранением:

ОпцияКогда сохраняет
AutoSaveOnTeleportingКаждый раз, когда игрок телепортируется на другую карту (не во время загрузки игры)
AutoSaveOnStoryВ сюжетных точках, где в графе есть узел AutoSaveIfEnabled

Каждое автосохранение идёт в Autosave/; предыдущее перемещается в OlderAutosave/ только после успешной записи нового, поэтому прерванное автосохранение никогда не уничтожает последнее хорошее.

Рекомендации по автосохранению
  • Размещайте AutoSaveIfEnabled после сюжетного момента, а не посреди сражения или кат-сцены.
  • Показывайте индикатор автосохранения во время сохранения.

Версионирование сохранений​

Сохранения несут версию формата, которая записывается в SavableGlobalGameData при сохранении и сравнивается при загрузке через SaveMigrationPipeline, запускающий миграции, нужные более старому сохранению:

ВерсияНазваниеЗначение
0LegacyHashesСохранения до появления метки версии: виды, формы, способности, приёмы и предметы определяются по хешу времени выполнения от имён их ассетов
1StableNamesОпределяет их по именам ассетов (стабильным строкам). Это текущая версия

Когда формат меняется, в конвейер добавляется новая ISaveMigration, а SaveFormatVersions.Current увеличивается.

Имена сериализованных полей и имена ассетов

Никогда не переименовывайте сериализованные поля в классах, связанных с сохранениями, без миграции, и считайте имена ассетов видов, приёмов, способностей и предметов ключами сохранений: переименование ассета ломает сохранения, которые его упоминают. Если вы переименуете поле, существующие файлы сохранений потеряют эти данные при загрузке; для переименования полей используйте [FormerlySerializedAs("oldName")].

GameVariables​

Система GameVariables — хранилище постоянных именованных значений MCE. Узлы CommandGraph и скрипты читают и записывают её, и она сохраняется вместе с объектом-владельцем.

Типы переменных​

ТипОписаниеПример
BooleanФлаги «истина/ложь»defeatedBrock = true
IntegerЧисловые счётчикиfishCaught = 42
FloatДесятичные числаrivalTrust = 0.5
StringТекстовые значенияrivalName = "Gary"

Каждая переменная живёт в держателе: глобальном (GlobalGameData) или в SceneInfoAsset карты. Переменная, определённая на карте, локальна для этой карты; глобальная общая для всей игры.

Использование в CommandGraph​

  • SetBoolVariable, SetIntVariable: задают значение переменной.
  • IfBool, IfIntEquals, IfIntCompare, SwitchIntVariable: ветвление по переменной.

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

Использование в коде​

// globalGameData is the injected GlobalGameData (a GameVariableHolder);
// the class that receives it implements IPlayerDataReceiver
bool beatBrock = globalGameData.GetVariable<bool>("defeatedBrock");
int fishCount = globalGameData.GetVariable<int>("fishCaught");

globalGameData.SetVariable("defeatedBrock", true);
globalGameData.SetVariable("fishCaught", fishCount + 1);

Пользовательские данные сохранения​

Используйте GameVariables (все редакции)​

Самый простой способ сохранять собственные флаги и счётчики вместе с игрой — хранить их как игровые переменные в GlobalGameData: они сохраняются, загружаются, мигрируют и сбрасываются вместе со всем остальным.

Расширение SavableObject (редакции Source и Studio)​

Для целой структуры данных напишите новый SavableObject и добавьте ассет этого типа в список Savegame Objects ассета SavegameManager:

[CreateAssetMenu(menuName = "MyGame/Custom Save Data")]
public class CustomSaveData : SavableObject
{
[Serializable] private class Data { public int counter; public List<string> unlocked; }
private Data data = new() { unlocked = new List<string>() };

public override string SaveToText(ISerializer<string> serializer, PlayerCharacter playerCharacter) =>
serializer.To(data);

public override IEnumerator LoadFromText(ISerializer<string> serializer, string data,
MCESettings mceSettings, MonsterDatabaseInstance monsterDatabase)
{
this.data = serializer.From<Data>(data);
yield break;
}

public override void ResetSave() => data = new Data { unlocked = new List<string>() };
}

Он записывается как отдельный файл .sav в каждом сохранении и указывается в манифесте. ResetSave задаёт ему чистое состояние при начале новой игры.

Формат файла сохранения​

Каждый файл .sav — это обычный JSON, записанный MCESavesSerializer, так что его можно просматривать при отладке. Точная форма зависит от объекта, которому он принадлежит (у глобальных данных, команды и остального — свои файлы).

Не редактируйте файлы сохранений

Хотя файлы сохранений — читаемый JSON, ручное редактирование может вызвать проблемы, если вы введёте недопустимые данные (например, приём, которого нет в базе данных). Вместо этого используйте внутриигровые инструменты или команды отладки.

Рекомендации​

  1. Тестируйте циклы сохранения/загрузки. Сохранитесь, выйдите, загрузитесь заново и убедитесь, что всё цело.
  2. Используйте GameVariables для флагов событий. Они сохраняются автоматически и интегрируются с CommandGraph.
  3. Планируйте миграцию сохранений. Если вы меняете формат в обновлении, добавьте шаг миграции.
  4. Не переименовывайте имена ассетов видов, приёмов, способностей и предметов после того, как у игроков появились сохранения.
  5. Корректно обрабатывайте сбои загрузки. Повреждённое или прерванное сохранение отклоняется с диалогом ошибки, а не загружается частично.