Как выполнять сохранение и загрузку игры в Unity

Совсем недавно мы опубликовали серию уроков (1 часть, 2 часть, 3 часть, 4 часть) по созданию простой игры, используя очень распространенный игровой движок — Unity. В этой статье мы покажем, как организовать систему управления сохраненными играми в Unity. Мы будем писать меню как в Final Fantasy, где игроку предоставляется возможность создать новое уникальное сохранение или же продолжить уже существующее. Итак, к концу урока вы научитесь:

  • сохранять игру и загружать уже имеющуюся, используя сериализацию;
  • использовать статические переменные для сохранения данных при изменении сцены.

Примечание автора Описанный ниже подход работает на любой платформе, поддерживаемой Unity, кроме Web Player. Для получения информации о сохранении игровых данных в веб-плеере, взгляните на официальную документацию.

Подготовка к сериализации

Первое, что нам нужно сделать, это сериализовать наши данные, которые мы сохраним, а затем в нужное время восстановим. Для этого нам потребуется создать скрипт (в качестве языка программирования будем использовать C#). Давайте назовем его SaveLoad. Этот сценарий будет обрабатывать все, что связано с сохранением и восстановлением данных.

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

Полученный сценарий должен выглядеть вот так:

Мы хотим добавить немного функциональных возможностей нашему скрипту, а потому давайте пропишем несколько директив:

Первая строка позволит нам использовать динамические списки. Вторая — даст нужный функционал операционной системы по сериализации данных. А последняя директива позволяет нам работать с потоками ввода и вывода. Иными словами, она используется для создания и чтения файлов.

Отлично, теперь мы готовы к сериализации данных!

Создание класса, с возможностью сериализации

Игры, похожие на Final Fantasy (то есть многие RPG), предлагают игроку выбрать класс персонажа. Например, рыцарь, разбойник или маг. Создайте новый скрипт, назовите его Game и объявите в нем переменные:

Обратите внимание на строчку [System.Serializable], которая говорит движку, что этот скрипт может быть сериализован. Круто, не так ли? Как говорит официальная документация, Unity умеет сериализовать следующие типы данных:

  • Все базовые типы (int, string, float, bool и т.д.).
  • Некоторые встроенные типы (Vector2, Vector3, Vector4, Quaternion, Matrix4x4, Color, Rect и LayerMask).
  • Все классы, унаследованные от UnityEngine.Object (GameObject, Component, MonoBehavior, Texture2D и AnimationClip).
  • Перечисляемый тип (Enums).
  • Массивы и списки типов данных, перечисленных выше.

Первая переменная, объявленная в нашем классе, — current — является статической ссылкой на экземпляр игры. Когда мы будем сохранять или загружать какие-либо данные, нам потребуется обратиться к «текущей» игре. При использовании статических переменных сделать это особенно просто, без вызова лишних методов. Очень удобно!

Обратите внимание на класс Character. Его еще у нас нет — давайте создадим новый скрипт с таким названием:

Ничего странного не заметили? Да, мы действительно создали новый класс, внутри которого есть ровно одна строковая переменная. Мы, конечно же, могли использовать в скрипте Game переменные типа string вместо экземпляров класса Character. Но целью нашей статьи является не то, как сделать лучше, а рассказать, как можно решить нашу проблему.

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

Сохранение игры

Что происходит по нажатию на кнопку «Загрузить игру»? Правильно — показывается список уже сохраненных игры, которые мы можем восстановить. Так давайте создадим список игр и назовем его savedGames:

А теперь напишем статическую функцию сохранения игры:

Разберемся с тем, что же выполняет эта функция. Сначала мы добавляем в ранее созданный список текующую игру. Так как в скрипте Game мы использовали статическую переменную current, то мы можем достаточно просто обратиться к ней: Game.current. После этого нам следует сериализовать список, для чего и создается экземпляр класса BinaryFormatter.

Далее при помощи класса FileStream мы создаем новый файл под названием savedGames.gd в специальной директории, в которой и должны храниться все игровые данные. Для названия файла сохранений будем использовать savedGames, а расширением будет gd (от словосочетания game data).

Примечание автора В качестве расширения файла вы можете использовать все, что угодно. Так, например, в играх The Elder Scrolls используется .esm.

Далее вызывается метод Serialize экземпляра класса BinaryFormatter, который и сохраняет в файл сериализованные данные. И на этом все — файл закрывается. Наша игра сохранена!

Загрузка игры

А здесь все довольно просто. Так как при сохранении игры мы создали файл и записали в него сериализованный список, то сейчас нам придется открыть его и десериализовать имеющиеся в нем данные:

Алгоритм предельно прост. Для начала мы проверяем, существует ли файл сохранений. Если существует, то мы создаем новый экземпляр класса BinaryFormatter, открываем файл savedGames.gd и записываем в список savedGames десериализованные данные, считанные из файла.

Обратите внимание на 5 строчку. Метод Deserialize вернет нам битовую последовательность и, присвоив списку savedGames то, что возвращает функция Deserialize, мы ни к чему хорошему не придем. И поэтому нам следует полученную битовую последовательность преобразовать (привести) к типу List <Game>.

Примечание автора Тип данных, к которому вы приводите десериализованные данные, может быть совершенно разным. Например: Player.lives = (int)bf.Deserialize(file);.

Вывод

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

Таковы основы работы с игровыми данными в Unity. В файле проекта вы можете найти несколько других скриптов, которые демонстрируют, как я применяю написанные нами функции и как я отображаю данные при помощи Unity GUI.

Перевод статьи «How to Save and Load Your Players’ Progress in Unity»