Реалізація системи збереження та завантаження даних ігор

Наша компанія з розробки відеоігор веде незалежні проекти, спільно з клієнтом створює ігри та надає додаткові операційні послуги. Досвід нашої команди дозволяє нам охопити всі ігрові платформи та розробити приголомшливий продукт, що відповідає баченню клієнта та перевагам гравців.

Від імерсивних застосунків до ігрових світів і 3D-сцен

Наша виділена команда для VR/AR/MR-розробки, Unity-продакшну і 3D-моделювання та анімації — з власними кейсами і презентаціями.

Відвідати персоналізований сайт
Показано 1 з 1 послугУсі 242 послуг
Реалізація системи збереження та завантаження даних ігор
Середня
від 2 робочих днів до 1 тижня
Часті питання

Наші компетенції

Які етапи розробки гри?

Останні роботи

  • image_games_mortal_motors_495_0.webp
    Розробка гри для компанії Mortal Motors
    683
  • image_games_a_turnbased_strategy_game_set_in_a_fantasy_setting_with_fire_and_sword_603_0.webp
    Покрокова стратегія у фентезі сеттингу With Fire And Sword
    862
  • image_games_second_team_604_0.webp
    Розробка ігри для компанії Second term
    491
  • image_games_phoenix_ii_606_0.webp
    3D-анімація – тизер для гри phoenix 2.
    533

Реалізація системи збереження та завантаження даних ігор

PlayerPrefs.SetFloat("health", 100) працює для прототипу. Для продакшену — це тупик. Коли обсяг збереження даних виростає до десятків змінних, PlayerPrefs перетворюється на неструктуровану свалку без версіонування, без можливості кількох слотів та без захисту від корупції. Перехід з PlayerPrefs на нормальну систему в середині розробки — болісна задача.

Що повинна вміти система збереження

Мінімальний production-ready набір:

  • Кілька слотів збереження з метаданими (дата, ім'я персонажа, рівень, скріншот)
  • Атомарна запис: файл або записаний повністю, або не записаний взагалі (проміжний краш не корруптує дані)
  • Версіонування: при оновленні гри старі збереження повинні мігрувати, а не ламатися
  • Асинхронна запис: збереження не повинне фризити гру на 200ms

Архітектура: ISaveable та SaveManager

Паттерн: кожен компонент, який хоче зберігатися, реалізує інтерфейс ISaveable:

public interface ISaveable
{
    string SaveId { get; }
    object CaptureState();
    void RestoreState(object state);
}

SaveManager при збереженні знаходить усі ISaveable на сцені (через FindObjectsOfType або регістрацію), викликає CaptureState() у кожного, збирає результат у Dictionary<string, object>, серіалізує та пише на диск. При завантаженні — зворотний процес.

SaveId — унікальна рядок для кожного компонента. Використовується GUID, генерований в інспекторі через [SerializeField] private string _saveId. Важливо не використовувати ім'я об'єкту сцени як ID: це не унікально і може змінитися.

Серіалізація: JSON vs Binary

JSON (Newtonsoft.Json) — читаємий, легко дебажиться, сумісний із різними платформами. Мінуси: більший обсяг файлу, трохи повільніше, потрібні кастомні конвертери для типів Unity (Vector3, Quaternion, Color). JsonConvert.SerializeObject(data, Formatting.None) з кастомним UnityTypeConverter — робочий підхід.

BinaryFormatter — Unity-вбудований, швидкий, компактний. Але: deprecated в .NET 5+, має вразливості безпеки (не критично для offline ігор). Для нових проектів не рекомендується.

MessagePack-CSharp — бінарний формат з продуктивністю кращою за JSON та без проблем BinaryFormatter. Хороший вибір для мобільних ігор з великими обсягами даних.

Шлях до файлу збереження: Application.persistentDataPath + "/saves/slot_{index}.sav". persistentDataPath гарантовано доступний для запису на всіх платформах (iOS, Android, PC, Console).

Атомарна запис та захист від корупції

Пряма перезапис файлу File.WriteAllText(path, json) може залишити файл у невалідному стані при крашуванні під час запису. Атомарна запис:

  1. Записати дані у тимчасовий файл slot_0.sav.tmp
  2. Якщо запис успішний — переіменувати File.Move(tmpPath, finalPath) (атомарна операція на більшості ОС)
  3. Старий файл попередньо переіменувати в slot_0.sav.bak — резервна копія

При завантаженні: якщо основний файл не знайдений або невалідний — спробувати .bak. Це елементарний захист, який економить тисячі годин поддержки після релізу.

Асинхронне збереження

Серіалізація 5 МБ JSON синхронно — це 50–200ms затримки на середньому PC, на мобільних ще гірше. Рішення: async/await з File.WriteAllTextAsync():

public async Task SaveAsync(int slot)
{
    var data = CollectSaveData();
    string json = JsonConvert.SerializeObject(data);
    await File.WriteAllTextAsync(GetSavePath(slot), json);
}

У Unity async Task методи працюють коректно з ConfigureAwait(false) для фонових потоків. UI індикатор збереження показується до виклику, приховується у finally блоці.

Версіонування та міграція

Кожен файл збереження містить "saveVersion": 3. При завантаженні версія порівнюється з currentSaveVersion. Якщо версії не збігаються — запускається ланцюжок мігреторів:

ISaveMigrator[] migrators = {
    new SaveMigratorV1ToV2(),
    new SaveMigratorV2ToV3()
};

Кожен мігратор знає як оновити JObject від своєї версії до наступної. Це дозволяє оновлювати формат збережень без втрати даних гравців. Без версіонування перше ж оновлення гри зі зміною структури даних інвалідує всі існуючі збереження.

Автозбереження та checkpoint система

Автозбереження через InvokeRepeating("AutoSave", 300f, 300f) — кожні 5 хвилин у спеціальний autosave слот. Checkpoint-збереження: при вході в trigger-зону публікується подія OnCheckpointReached, SaveManager зберігає у checkpoint-слот без UI.

Критично: не зберігати у момент бою або завантаженої сцени — вибирати момент збереження так, щоб фоновий потік запису не конкурував з піком CPU геймплею. Прапор isSafeToSave знімається на час інтенсивних сцен.

Орієнтовні строки

Масштаб Склад Строк
Простий JSON, один слот, без версіонування 2–4 дня
Базовий ISaveable паттерн, кілька слотів, атомарна запис 1–2 тижні
Повний Async, версіонування, міграція, cloud sync 3–5 тижнів
З хмарними збереженнями + Unity Cloud Save / Steam Cloud інтеграція +1–2 тижні

Процес

Спочатку пишемо SaveManager з тестом в ізоляції (Unity Test Runner): зберегти дані, завантажити, перевірити ідентичність. Потім інтегруємо ISaveable у існуючі компоненти по одному. Імітуємо краш запису під час тестування — спеціально переривємо процес збереження і перевіряємо, що дані не корруптувалися. Cloud sync реалізується останнім — тільки після стабільної роботи локального збереження.