Как выполнять сохранение и загрузку игры в Unity
Подготовка к сериализации
Первое, что нам нужно сделать, это сериализовать наши данные, которые мы сохраним, а затем в нужное время восстановим. Для этого нам потребуется создать скрипт (в качестве языка программирования будем использовать C#). Давайте назовем его SaveLoad. Этот сценарий будет обрабатывать все, что связано с сохранением и восстановлением данных.
Мы будем ссылаться на этот сценарий из других скриптов, а потому сделаем класс статичным, добавив ключевое слово static между public и class. Также не забудем удалить два автоматически созданных метода, поскольку нам не потребуется крепить скрипт ни к какому игровому объекту.
Полученный сценарий должен выглядеть вот так:
using UnityEngine; using System.Collections; public static class SaveLoad { }
Мы хотим добавить немного функциональных возможностей нашему скрипту, а потому давайте пропишем несколько директив:
using System.Collections.Generic; using System.Runtime.Serialization.Formatters.Binary; using System.IO;
Первая строка позволит нам использовать динамические списки. Вторая — даст нужный функционал операционной системы по сериализации данных. А последняя директива позволяет нам работать с потоками ввода и вывода. Иными словами, она используется для создания и чтения файлов.
Отлично, теперь мы готовы к сериализации данных!
Создание класса, с возможностью сериализации
Игры, похожие на Final Fantasy (то есть многие RPG), предлагают игроку выбрать класс персонажа. Например, рыцарь, разбойник или маг. Создайте новый скрипт, назовите его Game и объявите в нем переменные:
using UnityEngine; using System.Collections; [System.Serializable] public class Game { public static Game current; public Character knight; public Character rogue; public Character wizard; public Game () { knight = new Character(); rogue = new Character(); wizard = new Character(); } }
Обратите внимание на строчку [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. Его еще у нас нет — давайте создадим новый скрипт с таким названием:
using UnityEngine; using System.Collections; [System.Serializable] public class Character { public string name; public Character () { this.name = ""; } }
Ничего странного не заметили? Да, мы действительно создали новый класс, внутри которого есть ровно одна строковая переменная. Мы, конечно же, могли использовать в скрипте Game переменные типа string вместо экземпляров класса Character. Но целью нашей статьи является не то, как сделать лучше, а рассказать, как можно решить нашу проблему.
Теперь, когда наши классы настроены, перейдем обратно к скрипту SaveLoad и добавим возможность сохранения игры.
Сохранение игры
Что происходит по нажатию на кнопку «Загрузить игру»? Правильно — показывается список уже сохраненных игры, которые мы можем восстановить. Так давайте создадим список игр и назовем его savedGames:
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Runtime.Serialization.Formatters.Binary; using System.IO; public static class SaveLoad { public static List<Game> savedGames = new List<Game>(); }
А теперь напишем статическую функцию сохранения игры:
public static void Save() { savedGames.Add(Game.current); BinaryFormatter bf = new BinaryFormatter(); FileStream file = File.Create (Application.persistentDataPath + "/savedGames.gd"); bf.Serialize(file, SaveLoad.savedGames); file.Close(); }
Разберемся с тем, что же выполняет эта функция. Сначала мы добавляем в ранее созданный список текующую игру. Так как в скрипте Game мы использовали статическую переменную current, то мы можем достаточно просто обратиться к ней: Game.current. После этого нам следует сериализовать список, для чего и создается экземпляр класса BinaryFormatter.
Далее при помощи класса FileStream мы создаем новый файл под названием savedGames.gd в специальной директории, в которой и должны храниться все игровые данные. Для названия файла сохранений будем использовать savedGames, а расширением будет gd (от словосочетания game data).
Примечание автора В качестве расширения файла вы можете использовать все, что угодно. Так, например, в играх The Elder Scrolls используется .esm.
Далее вызывается метод Serialize экземпляра класса BinaryFormatter, который и сохраняет в файл сериализованные данные. И на этом все — файл закрывается. Наша игра сохранена!
Загрузка игры
А здесь все довольно просто. Так как при сохранении игры мы создали файл и записали в него сериализованный список, то сейчас нам придется открыть его и десериализовать имеющиеся в нем данные:
public static void Load() { if(File.Exists(Application.persistentDataPath + "/savedGames.gd")) { BinaryFormatter bf = new BinaryFormatter(); FileStream file = File.Open(Application.persistentDataPath + "/savedGames.gd", FileMode.Open); SaveLoad.savedGames = (List<Game>)bf.Deserialize(file); file.Close(); } }
Алгоритм предельно прост. Для начала мы проверяем, существует ли файл сохранений. Если существует, то мы создаем новый экземпляр класса BinaryFormatter, открываем файл savedGames.gd и записываем в список savedGames десериализованные данные, считанные из файла.
Обратите внимание на 5 строчку. Метод Deserialize вернет нам битовую последовательность и, присвоив списку savedGames то, что возвращает функция Deserialize, мы ни к чему хорошему не придем. И поэтому нам следует полученную битовую последовательность преобразовать (привести) к типу List <Game>.
Примечание автора Тип данных, к которому вы приводите десериализованные данные, может быть совершенно разным. Например: Player.lives = (int)bf.Deserialize(file);
.
Вывод
Итак, теперь вы знаете, как реализовать систему сохранения и загрузки игровых данных. Наш скрипт готов, и в окончательном виде он выглядит вот так:
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Runtime.Serialization.Formatters.Binary; using System.IO; public static class SaveLoad { public static List<Game> savedGames = new List<Game>(); //методы загрузки и сохранения статические, поэтому их можно вызвать откуда угодно public static void Save() { SaveLoad.savedGames.Add(Game.current); BinaryFormatter bf = new BinaryFormatter(); //Application.persistentDataPath это строка; выведите ее в логах и вы увидите расположение файла сохранений FileStream file = File.Create (Application.persistentDataPath + "/savedGames.gd"); bf.Serialize(file, SaveLoad.savedGames); file.Close(); } public static void Load() { if(File.Exists(Application.persistentDataPath + "/savedGames.gd")) { BinaryFormatter bf = new BinaryFormatter(); FileStream file = File.Open(Application.persistentDataPath + "/savedGames.gd", FileMode.Open); SaveLoad.savedGames = (List<Game>)bf.Deserialize(file); file.Close(); } } }
Таковы основы работы с игровыми данными в Unity. В файле проекта вы можете найти несколько других скриптов, которые демонстрируют, как я применяю написанные нами функции и как я отображаю данные при помощи Unity GUI.
Комментарии