Большинство проектов созданных в Unity часто имеют систему хранения игровых данных. Эта система включает в себя инструменты для сохранения и загрузки данных. Как и где хранить эти данные часто зависит от того что это за игра, кто в нее играет и какое кол-во данных необходимо сохранить. Обычно различают два вида хранения данных: локальную, облачную (удаленную) и комбинированную.
Локальную систему хранения данных часто используют в одиночных играх, где необходимо хранить несущественные данные, вроде прогресса прохождения или характеристик персонажей и тд.
Облачную систему чаще используют для многопользовательских проектов. В таких проектах игре необходимо иметь доступ к данным всех игроков, поэтому они используют сервера, где хранятся эти данные.
Комбинированную систему обычно используют в проектах, нацеленных как на одиночную игру, так и на многопользовательскую. В таких проектах необходимо хранить данные локально и удаленно.
В этой статье рассмотрим локальный тип хранения данных, и для этого в Unity есть очень простой инструмент PlayerPrefs.
PlayerPrefs – это небольшой набор методов для сохранения и загрузки данных из реестра системы. Сам реестр используется для иерархического хранения данных и настроек системы. В отличие от файловой системы, где хранятся файлы с любыми данными и которые доступны всем пользователям компьютера, в реестре хранятся только настройки программы с самыми необходимыми данными которые доступны только определенным пользователям, а PlayerPrefs, в свою очередь, позволяет записывать и считывать эти самые данные из реестра.
Методы работы
Для начала рассмотрим способы записи данных в реестр с помощью PlayerPrefs.
Система имеет несколько методов и все они работают по одному и тому же принципу: сначала указываем ключ под которым хотим записать данные, после чего указываем сами данные которые необходимо записать.
- SetInt. Метод используется для записи целого числа(integer) в реестр.
- SetFloat. Метод для записи числа с “плавающей” запятой или дробного числа(float).
- SetString. Метод для записи текстовых данных.
Для загрузки есть аналогичные методы, которые возвращают сохраненные ранее данные под определенным ключом.
- GetInt. Метод используется для считывания целого числа(integer) из реестра.
- GetFloat. Метод для считывания дробного числа(float).
- GetString. Метод для считывания текстовых данных.
И так, мы разобрали основные методы для работы с PlayerPrefs, теперь попробуем сохранить с помощью этой системы некоторые данные в игре.
Игра представляет собой небольшую аркаду в которой необходимо отстреливать инопланетные корабли до того как они захватят главную базу.
Начнем с простого сохранения кол-ва уничтоженных кораблей.
Создадим небольшой скрипт Control унаследованный от MonoBehaviour.
- public class Control : MonoBehaviour {
- public int kills = 0;
- }
В числовой переменной kills будем хранить кол-во уничтоженных кораблей.
Теперь добавим метод сохранения Save.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public void Save() {}
- }
В игре этот метод вызывается через UI кнопку.
После нажатия этой кнопки переменная kills запишется в реестр под указанным ключом.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public void Save() {
- string key = “MyGame”;
- PlayerPrefs.SetInt(key, this.kills);
- PlayerPrefs.Save();
- }
- }
И так первым действие указываем в переменной key ключ под которым необходимо будет записать данные, пусть, к примеру название ключа будет MyGame, далее вызываем метод SetInt в который передаем ключ и переменную kills, в конце завершаем запись данных в реестре с помощью метода Save.
Пробуем протестировать.
Проверить записи данных можно в реестре. Для быстрого входа в реестр необходимо нажать комбинацию кнопок Win + R, после чего в окошке “Выполнить” ввести regedit и нажать “Ok”.
Далее необходимо найти раздел с игрой. Все данные unity проектов хранятся в разделе HKEY_CURRENT_USER/Software/Unity/UnityEditor/DefaultCompany в этом разделе находим проектом по названию, там и будут храниться все записи программы.
В разделе “Параметр” можно увидеть название ключа под которым записаны данные, а в разделе “Значение” число равное кол-ву уничтоженных кораблей в игре.
Именно в этом разделе мы будем хранить все остальные данные из игры.
Загрузка данных
Теперь необходимо произвести чтение данных из реестра.
Загрузку будет проводить при старте игры, для этого заведем новый метод Start в скрипте Control.
- public class Control : MonoBehaviour {
- public int kills = 0;
- private void Start() {
- Load();
- }
- private void Load() {
- string key = “MyGame”;
- }
- /*…метод Save…*/
- }
В методе Load, в переменную key укажем ключ под которым записаны наши данные.
Теперь с помощью условия проверим: существуют ли наш ключ в реестре, для этого используем метод HasKey.
- public class Control : MonoBehaviour {
- public int kills = 0;
- private void Start() {
- Load();
- }
- private void Load() {
- string key = “MyGame”;
- if (PlayerPrefs.HasKey(key)) {
- }
- }
- /*…метод Save…*/
- }
Если ключ существует значит можно загрузить данные из реестра.
- public class Control : MonoBehaviour {
- public int kills = 0;
- private void Start() {
- Load();
- }
- private void Load() {
- string key = “MyGame”;
- if (PlayerPrefs.HasKey(key)) {
- this.kills = PlayerPrefs.GetInt(key);
- }
- }
- /*…метод Save…*/
- }
Проверяем.
Отлично, данные загрузились.
Комплексные данные
И так, теперь мы научились сохранять и загружать самые элементарные данные из реестра. Теперь попробуем проделать все тоже самое с кол-во очков в игре, для этого объявим новую дробную переменную scores.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- }
Теперь немного расширим метод Save, чтобы сохранить эту новую переменную в реестр.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- public void Save() {
- string key = “MyGame”;
- PlayerPrefs.SetInt(key, this.kills);
- PlayerPrefs.SetFloat(key, scores);
- PlayerPrefs.Save();
- }
- }
В методе Load проведем аналогичные действия только по загрузке переменной scores.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- private void Start() {
- Load();
- }
- private void Load() {
- string key = “MyGame”;
- if (PlayerPrefs.HasKey(key)) {
- this.kills = PlayerPrefs.GetInt(key);
- this.scores = PlayerPrefs.GetFloat(key);
- }
- }
- /*…метод Save…*/
- }
Запускаем игру, чтобы проверить работоспособность системы.
Текстовые данные
У методов записи и загрузки данных есть один недостаток, заключается он в том, что под одним ключом может храниться только одна переменная определенного типа. Мы уже использовали ячейки для записи целого числа int и дробного float, больше данный ключ вместить данных не может, но в игре еще остались данные которые необходимо записать в реестр – это кол-во жизней главной базы.
Кол-во жизней базы это тоже дробное число float, а так как ячейка дробного числа уже занята переменной scores, то получается, что мы не сможем поместить еще одну. В этом случае на помощь приходят текстовые данные. Мы просто преобразуем все данные для сохранения в текст и запишем его в реестр в текстовую ячейку, которая все еще пустая. Для удобного преобразования множества данных в текст и обратно используем JSONUtility.
JSON – это удобный текстовый формат хранения данных. Он преобразует любой объект в читаемый текст и обратно. С помощью него можно хранить практически любое кол-во данных в виде текста.
И так объявим новую переменную health в скрипте Control где будем хранить кол-во жизней базы.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- public float health = 100;
- }
Теперь нам нужен объект который будет хранить все эти три переменные. Для этого подойдет простой класс SaveData. Создадим новый скрипт SaveData и уберем у него наследование от MonoBehaviour.
- public class SaveData {
- public int kills;
- public float scores;
- public float health;
- }
Переходим в метод Save, откуда сотрем последние два действия SetInt и SetFloat.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- public float health = 100;
- public void Save() {
- string key = “MyGame”;
- SaveData data = new SaveData();
- PlayerPrefs.Save();
- }
- }
Сначала создаем новый экземпляр класса SaveData, после чего наполняем его данными.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- public float health = 100;
- public void Save() {
- string key = “MyGame”;
- SaveData data = new SaveData();
- data.kills = this.kills;
- data.scores = this.scores;
- data.health = this.health;
- PlayerPrefs.Save();
- }
- }
Теперь необходимо преобразовать объект data в текст, для чего воспользуемся методом ToJson класса JsonUtility.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- public float health = 100;
- public void Save() {
- string key = “MyGame”;
- SaveData data = new SaveData();
- data.kills = this.kills;
- data.scores = this.scores;
- data.health = this.health;
- string value = JsonUtility.ToJson(data);
- PlayerPrefs.Save();
- }
- }
После чего сохраняем полученный текст в реестр с помощью метода SetString.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- public float health = 100;
- public void Save() {
- string key = “MyGame”;
- SaveData data = new SaveData();
- data.kills = this.kills;
- data.scores = this.scores;
- data.health = this.health;
- string value = JsonUtility.ToJson(data);
- PlayerPrefs.SetString(key, value);
- PlayerPrefs.Save();
- }
- }
Теперь необходимо проделать действия по загрузке данных в методе Load и перевести текст обратно в объект SaveData с помощью того же JSONUtility.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- public float health = 100;
- private void Start() {
- Load();
- }
- private void Load() {
- string key = “MyGame”;
- if (PlayerPrefs.HasKey(key)) {
- string value = PlayerPrefs.GetString(key);
- }
- }
- /*…метод Save…*/
- }
Как и раньше проверяем существование ключа, после чего загружаем текст из реестра. Далее преобразуем полученный текст в объект SaveData с помощью метода FromJson класса JsonUtility.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- public float health = 100;
- private void Start() {
- Load();
- }
- private void Load() {
- string key = “MyGame”;
- if (PlayerPrefs.HasKey(key)) {
- string value = PlayerPrefs.GetString(key);
- SaveData data = JsonUtility.FromJson<SaveData>(value);
- }
- }
- /*…метод Save…*/
- }
В методе FromJson, в фигурных скобках указываем тип объекта который мы хотим получить из текста, а в сам метод передаем текстовую переменную value в которой находится загруженный текст из реестра. Получив целый объект из текста применяем сохраненные значения переменных обратно.
- public class Control : MonoBehaviour {
- public int kills = 0;
- public float scores = 0f;
- public float health = 100;
- private void Start() {
- Load();
- }
- private void Load() {
- string key = “MyGame”;
- if (PlayerPrefs.HasKey(key)) {
- string value = PlayerPrefs.GetString(key);
- SaveData data = JsonUtility.FromJson<SaveData>(value);
- this.kills = data.kills;
- this.scores = data.scores;
- this.health = data.health;
- }
- }
- /*…метод Save…*/
- }
Запускаем для проверки.
Сохранение и загрузка работают исправно. Переходим в реестр и проверяем данные.
Теперь в разделе “Значение” мы видим текст со всеми переменными и их значениями.
Заключение
Сохранение и загрузка данных через PlayerPrefs имеет свои преимущества перед другими видами локального хранения данных: во первых простотой работы, вам не нужно работать с файлами и лезть в файловую систему вообще, во вторых при работе с файловой системе, к примеру, на некоторых платформах вам нужно иметь разрешение на чтение и запись данных, для PlayerPrefs в этом нет необходимости он работает на всех устройствах одинаково. Поэтому PlayerPrefs отлично подходит для хранение небольшого кол-ва несложных данных на устройстве.
Ознакомится с проектом из статьи можно по ссылке.
Хотел сделать сохранение громкости в игре, но увы ничего не получилось 🙁
Проконсультируйтесь с нами через почту сайта
Статья годная, но
сделайте шрифт по-больше, больно читать. ;(
Спасибо за отзыв
Молодцы! Все подробно и понятно! Жду с нетерпением статью про облачное сохранение данных!