Одна из самых важных вещей в игре – сохранение пользовательских данных: настроек и игровых результатов. Когда-то игры были короткими, и сохранять там было особенно нечего. В лучшем случае игра записывала самый высокий балл для составления рейтинга. Но технологии стремительно развивались, и геймдев не оставался в стороне.
Сейчас нам нужно хранить тонны данных, включая прогресс и статистику игрока. Эта потребность актуальна как для игр с огромным открытым миром, так и для простых линейных приключений. Зачастую требуется несколько сессий, чтобы завершить игру, значит, нужен способ сохраниться и вернуться позже к тому же моменту. Даже для простых коротких игр может быть полезно записывать какую-то информацию.
Подготовка
Unity предлагает сразу два способа сохранять игровые данные – попроще и посложнее:
- Попроще – встроенная система PlayerPrefs. Устанавливаете значение для ключа, нажимаете Save – и все готово.
- Посложнее – сериализация данных и запись в файл для дальнейшего использования.
У обоих методов есть преимущества и недостатки, поэтому для конкретного случая важно выбрать правильный вариант. Для демонстрации нам потребуется некоторая минимальная конфигурация. Создадим новый проект в Unity, за основу для простоты возьмем 2D-шаблон.
Добавим два скрипта – SavePrefs
и SaveSerial
– для реализации двух методов.
Чтобы создать скрипт, кликните правой кнопкой мыши в окне Assets
и выберите пункты Create
-> C# Script
.
Начнем с более простого способа – SavePrefs
.
Кликните два раза по скрипту, чтобы открыть его в редакторе Visual Studio.
Простой способ: PlayerPrefs
Для начала можно закомментировать или удалить методы Start
и Update
, так как они не потребуются для демонстрации сохранения данных. Затем нам понадобятся несколько переменных.
С помощью метода OnGui создадим пользовательский интерфейсдля визуального управления этими переменными.
- Две кнопки – для увеличения значений
intToSave
иfloatToSave
. - Текстовое поле – для переменной
stringToSave
. - Несколько лейблов для отображения текущих значений переменных.
- Три кнопки действий, чтобы сохранить, загрузить и сбросить данные.
Сохранение
Создадим метод SaveGame
, который будет отвечать за сохранение данных:
Как видим, для сохранения данных с PlayerPrefs
нужно лишь несколько строчек кода. Здесь мы устанавливаем ключи настройки ("SavedInteger"
или "SavedFloat"
) и их значения передаем в соответствующие методы объекта PlayerPrefs
. После того, как все нужные данные записаны, сохраняем их, вызвав метод PlayerPrefs.Save
. Выводим сообщение в отладочную консоль, о том, что операция успешно выполнена.
Должно быть, вам интересно, где сейчас физически находятся эти данные. Они записываются в файл в папке проекта. В Windows его можно найти по адресу HKEY_CURRENT_USER\Software\Unity\UnityEditor\[company name]\[project name]
. Именно отсюда запускается игра из редактора. В exe-файле их можно найти по адресу HKEY_CURRENT_USER\Software\[company name]\[project name]
. На Mac OS согласно документации файлы PlayerPrefs находятся в папке ~/Library/Preferences
, в файле с названием unity.[company name].[product name].plist
.
Загрузка
Загрузка сохраненных данных – это, по сути, сохранение наоборот. Необходимо взять значения, хранящиеся в PlayerPrefs
и записать их в переменные.
Хорошей практикой является предварительная проверка наличия нужных ключей. Для этого предназначен метод HasKey
. Достаточно проверить хотя бы один ключ из установленных в методе SaveGame
, чтобы понимать, есть ли у пользователя сохраненные данные.
Если данных нет, выведем в консоль сообщение об ошибке.
Сброс
Для удаления всех данных, хранящихся в PlayerPrefs
, нужно использовать метод PlayerPrefs.DeleteAll
.
В методе ResetData
мы очищаем хранилище, а также обнуляем все переменные.
Теперь проверим весь этот код в деле. Сохраните файл и вернитесь в редактор Unity. Прикрепите скрипт SavePrefs
к какому-нибудь объекту, например, к Main Camera
.
Теперь запустите игру и начните взаимодействовать с GUI-элементами. Изменяйте переменные, нажимая на кнопки и заполняя текстовое поле. Когда будете готовы, сохраните данные кнопкой Save Your Game
. После этого остановите и перезапустите игру и нажмите на кнопку Load Your Game
. Если вы всё сделали правильно, значения переменных немедленно изменятся на те, что вы сохранили в предыдущем запуске.
Чтобы очистить PlayerPrefs
, кликните Reset Save Data
.
Недостатки
Этот способ кажется простым и эффективным. Почему бы всегда не использовать PlayerPrefs
для сохранения пользовательских данных?
Из-за его невысокой безопасности. Вы точно не захотите сохранять таким способом данные, в которые не должен вмешиваться игрок: например, количество доступной игроку игровой валюты, или статистика.
Из названия понятно, что PlayerPrefs
предназначен для хранения пользовательских предпочтений и других неигровых данных. Например, этот метод идеально подходит для записи настроек интерфейса: цветовой темы, размера элементов и т. п.
Другая проблема – в недостаточной гибкости. В PlayerPrefs
вы можете сохранять только числа и строки, поэтому он не подходит для данных, имеющих сложную структуру.
К счастью, у нас есть еще один способ, более гибкий и безопасный.
Сложный способ: Сериализация
Для демонстрации сложного способа сохранения данных в Unity откроем скрипт SaveSerial
.
Снова определим переменные и создадим интерфейс для управления ими. Метод OnGUI
похож на тот, что мы только что писали:
Для сериализации данных потребуется добавить несколько директив using
:
Сохранение
Создадим новый сериализуемый класс SaveData
, который будет содержать сохраняемые данные.
Обратите внимание, три переменные в классе SaveData
соответствуют переменным из класса SaveSerial
. Для сохранения мы будем передавать значения из SaveSerial
в SaveData
, а затем сериализовать последний.
Добавим в класс SaveSerial
метод SaveGame
:
Объект BinaryFormatter
предназначен для сериализации и десериализации. При сериализации он отвечает за преобразование информации в поток бинарных данных (нулей и единиц).
FileStream
и File
нужны для создания файла с расширением .dat
. Константа Application.persistentDataPath
содержит путь к файлам проекта: C:\Users\[user]\AppData\LocalLow\[company name]
.
В методе SaveGame
создается новый экземпляр класса SaveData
. В него записываются текущие данные из SaveSerial
, которые нужно сохранить. BinaryFormatter
сериализует эти данные и записывает их в файл, созданный FileStream
. Затем файл закрывается, в консоль выводится сообщение об успешном сохранении.
Загрузка
Метод LoadGame
– это, как и раньше, SaveGame
наоборот:
- Сначала ищем файл с сохраненными данными, который мы создали в методе SaveGame.
- Если он существует, открываем его и десериализуем с помощью
BinaryFormatter
. - Передаем записанные в нем значения в переменные класса
SaveSerial
. - Выводим в отладочную консоль сообщение об успешной загрузке.
Если файла с данными не окажется в папке проекта, выведем в консоль сообщение об ошибке.
Сброс
Наконец, реализуем метод для сброса сохранения. Он похож на тот ResetData
, который мы написали для очистки PlayerPrefs
, но включает в себя пару дополнительных шагов.
Сначала нужно убедиться, что файл, который мы хотим удалить, существует. После его удаления обнуляем значения переменных класса SaveSerial до дефолтных и выводим сообщение в консоль.
Если файла нет, выводим сообщение об ошибке.
Скрипт метода сериализации готов, теперь его можно проверить в деле. Сохраните код, вернитесь в Unity и запустите игру. Привяжите скрипт SaveSerial
к объекту Main Camera
(не забудьте деактивировать предыдущий).
После запуска вы увидите тот же самый интерфейс, что и раньше. Попробуйте изменить переменные и сохранить игру.
В этот раз файл будет сохранен по "постоянному пути данных" игры. В Windows это C:\Users\username\AppData\LocalLow\project name
, в Mac – ~/Library/Application Support/companyname/productname
согласно документации.
Перезапустите игру и загрузите данные, нажав на кнопку Load Your Game
. Значения переменных должны измениться на те, что вы сохранили ранее.
Также вы можете удалить все сохраненные данные кнопкой Reset Save Data
.
Заключение
Сохранение данных – важная часть большинства игр, за исключением лишь некоторых специфических исключений (например, игры с очень короткими игровыми сессиями или механикой перманентной смерти, в которой после каждого проигрыша вся игра сбрасывается к началу).
Помните о том, что PlayerPrefs
– это простой способ сохранить настройки и предпочтения пользователя, но его не следует использовать для записи важных игровых данных.
Сериализация и запись в файл – более сложный путь, но он дает вам бóльшую гибкость и безопасность.
Какие именно данные сохранять и каким способом – зависит от особенностей проекта.
Комментарии