PlayerPrefs
class in UnityEngine
Успех!
Благодарим вас за то, что вы помогаете нам улучшить качество документации по Unity. Однако, мы не можем принять любой перевод. Мы проверяем каждый предложенный вами вариант перевода и принимаем его только если он соответствует оригиналу.
Ошибка внесения изменений
По определённым причинам предложенный вами перевод не может быть принят. Пожалуйста попробуйте снова через пару минут. И выражаем вам свою благодарность за то, что вы уделяете время, чтобы улучшить документацию по Unity.
Описание
Stores and accesses player preferences between game sessions.
Editor/Standalone
В Mac OS X параметры игрока (player prefs) хранятся в папке
/Library/Preferences , HKCU
Software
[company name]
[product name] key, где «company name» и «product name» являются The same .plist file is used for both Projects run in the Editor and standalone players.
В Mac OS X параметры игрока (player prefs) хранятся в папке
/Library/Preferences , HKCU\Software\[company name]\[product name] key, where company and product names are именами указанными в настройках проекта.
On Linux, PlayerPrefs can be found in
/.config/unity3d/[CompanyName]/[ProductName] again using the company and product names specified in the Project Settings.
On Windows Store Apps, Player Prefs can be found in %userprofile%\AppData\Local\Packages\[ProductPackageId]>\LocalState\playerprefs.dat
On Windows Phone 8, Player Prefs can be found in application’s local folder, See Also: Windows.Directory.localFolder
On Android data is stored (persisted) on the device. The data is saved in SharerPreferences. C#/JavaScript, Android Java and Native code can all access the PlayerPrefs data. The PlayerPrefs data is physically stored in /data/data/pkg-name/shared_prefs/pkg-name.xml.
WebPlayer
On Web players, PlayerPrefs are stored in binary files in the following locations:
Mac OS X:
Windows: %APPDATA%\Unity\WebPlayerPrefs
В Mac OS X параметры игрока (player prefs) хранятся в папке
There is one preference file per Web player URL and the file size is limited to 1 megabyte. If this limit is exceeded, SetInt, SetFloat and SetString will not store the value and throw a PlayerPrefsException.
Источник
Unity Android — гайды для начинающих
Поиск по этому блогу
- Получить ссылку
- Электронная почта
- Другие приложения
Unity Android — как хранить данные на устройстве
- Получить ссылку
- Электронная почта
- Другие приложения
Для сохранения пользовательских данных необходимо использовать встроенные методы движка Unity. В этой статье мы рассмотрим самый простой способ их хранения.
Начнем с того, что в Unity есть синглтон PlayerPrefs, который реализует методы сохранения трех типов данных — строки, числа типа float и числа типа int.
Все данные сохраняются под ключами, как в словаре или ассоциативном массиве. Давайте рассмотрим это на примере кода.
Данная команда записала в словарь пользовательских данных число с типом int под ключом «name». Но просто прописать данную команду недостаточно: нужно подствердить сохранение. Для этого просто вызываем:
Вот пример сохранения других типов:
Чтобы получить значение, воспользуемся get методами:
string data = PlayerPrefs.GetString(«nickname»);
int number = PlayerPrefs.GetInt(«name»);
float money = PlayerPrefs.GetFloat(«money»);
Если до этой игровой сессии данные были занесены, методы вернут сохраненные значения.
Источник
Сохранение игры в Unity3D
Если вы пишете не казуалку под веб и не беспощадный суровый рогалик, без сохранения данных на диск не обойтись.
Как это делается в Unity? Вариантов тут достаточно — есть класс PlayerPrefs в библиотеке, можно сериализовать объекты в XML или бинарники, сохранить в *SQL*, можно, в конце-концов, разработать собственный парсер и формат сохранения.
Рассмотрим поподробнее с первые два варианта, и заодно попробуем сделать меню загрузки-сохранения со скриншотами.
Будем считать, что читающий дальше базовыми навыками обращения с этим движком владеет. Но при этом можно не подозревать о сущестовании в его библиотеке PlayerPrefs, GUI, и ещё в принципе не знать о сериализации. С этим всем и разберёмся.
А чтобы эта заметка не стала слишком уж увлекательной и полезной, ориентирована она самый неактуальный в мобильно/планшетно/онлайновый век вариант — сборку под винду (хотя, конечно, более общих моментов достаточно).
- Кстати, пару недель назад на Хабре была статья, где автор упомянул, что Unity3D проходят в курсе компьютерной графики на кафедре информатики питерского матмеха. Занятный факт, немало говорящий о популярности движка.
Хотя насколько это в целом хорошая идея — на мой взгляд, тема для дискуссии. Может быть, обсудить это было бы даже интереснее вопросов сериализации =)
Ещё небольшой дисклеймер — я не профи ни в одной из раскрываемых тем, так что если какие-то вещи можно сделать правильнее-проще-удобнее — исправления и дополнения очень приветствуются.
1. PlayerPrefs
Удобный встроенный класс. Работает с int, float и string. Довольно прозрачный, но мне всё равно встречались на форумах обороты в духе «не могу понять PlayerPrefs» или «надо бы как-нибудь разобраться с PlayerPrefs», так что посмотрим на него на простом примере.
1.1 Примитивное использование в рамках одной сцены: QuickSave & QuickLoad по хоткеям.
Быстрый пример использования. Допустим, у нас одна сцена и персонаж на ней. Скрипт SaveLoad.cs прикреплен к персонажу. Будем сохранять самое простейшее — его положение.
Конечно, тут применение PlayerPrefs довольно надумано — фактически против обычных переменных оно добавляет нам только возможность загрузки игру с места сохранения после выхода.
Зато весь основной интерфейс класса виден: для каждого из трех типов Get / Set по ключу, проверка вхождения по ключу, очистка. Нет смысла даже разбирать ScriptReference, всё очевидно по названиям функций: PlayerPrefs
Однако на одной всё же стоит остановиться подробнее, PlayerPrefs.Save. В описании говорится, что вообще дефолтно юнити пишет PlayerPrefs на диск только при закрытии приложения — в общем-то логично, учитывая, что класс ориентирован не на внутренний обмен данными, и на их сохранение между сеансами. Соответственно, Save() предполагается использовать только для периодических сохранений на случай крэша.
Возможно, в некоторых случаях это так и работает. Под Win PlayerPrefs пишутся в реестр, и, как можно легко убедиться, считываются и пишутся сразу.
Как-то так выглядит наш класс в реестре:
Ко всем ключам в конце добавлен их DJBX33X-хеш (Bernshtein hash with XOR).
UnityGraphicsQuality сохраняется всегда автоматически, и действительно при закрытии приложения. Это Quality level из Edit -> Project Settings Quality, оно же QualitySettings.SetQualityLevel .
Можно при запущенном приложении модифицировать сохранённое значение в реестре, потом затребовать его из программы — и мы увидим, что вернулся модифицированный вариант. Т.е. не стоит думать что во время работы программы PlayerPrefs — что-то вроде аналога глобальных переменных, а работа с диском не происходит.
2. Сериализация в XML
Говорим сериализация, подразумеваем бинарный код. Такое встречается, но на самом деле сериализовать можно в любой формат. По сути это перевод структуры данных или состояния объекта в хранимый/передаваемый формат. А десериализация, соответственно — восстановление объекта по сохраненным/полученным данным.
Вообще Mono умеет и бинарную сериализацию, и XML (System.Xml.Serialization), но есть один момент: большинство классов Unity не сериализуются напрямую. Невозможно просто взять и сериализовать GameObject, или класс, наследующий MonoBehavoir: придётся завести дополнительно внутренний сериализуемый класс, содержащий нужные данные, и работаеть, используя его. Но XmlSerializer хотя бы кушает автоматически Vector3, а BinarySerializer, afaik, даже этого не умеет.
2.1 Суть примера
Представьте, что вы пишете свой Portal, где герой проходит череду однотипных локаций — но на любую из них может впоследствии вернуться. Причём на каждую он мог оказать воздействие: какие-то ресурсы использовать, что-то сломать, что-то расшвырять. Хочется, эти изменения сохранять, но возвращение на локацию маловероятно и непрогнозируемо, и тащить за собой параметры всех комнат в оперативке нет особого смысла. Будем сериализовать локацию, покидая её — например, по триггеру на двери. А при загрузке локации генерировать либо дефолтную ситуацию, либо, если есть сохраненные данные, восстанавливать по ним.
2.2 Сериализуемые классы для данных
XmlSerializer умеет работать с классами, данные в которых состоят из других сериализуемых классов, простых типов, большинства элементов Collections[.Generic]. Обязательно наличие у класса пустого конструктора и public-доступ ко всем сериализуемым полям.
Некторые типы из библиотеки Юнити (вроде Vector3, содержащего всего три интовых поля) успешно проходят этот фейсконтроль, но большинство, особенно более сложных, его фейлят.
Допустим, в каждой комнате нам надо сохранять состояния некоторого произвольного набора GameObject’ов. Напрямую сделать этого мы не можем. Значит, нам потребуются дублирующие типы.
Создадим новый скрипт в Standard Assets:
В квадратных скобках идут атрибуты для управления XML-сериализацией. Тут они фактически влияют только на имена тегов в генерируемом *.xml, и строго говоря, необходимости в них нет. Но пусть будут, для наглядности 🙂 Если вам почему-то вдруг важно, как будет выглядеть xml-код, то возможности атрибутов, конечно шире.
Дальше там же добавим базовый класс для предметов из списка и сколько угодно наcледуемых от него. Хотя… для примера хватит и одного:
Итак, сериализуемые классы готовы. Сделаем теперь ещё класс для дополнительного упрощения сериализации созданного типа RoomState.
2.3 Непосредственно сериализация
Тоже в Standard Assets сделаем класс с парой статических методов, которыми будем в дальнейшем пользоваться:
Здесь XmlSerializer мы создаём через конструктор Constructor (Type, Type[])
FileStream открываем по адресу сохранения, передаваемого конкретной локацией.
Использование
Итак, все вспомогательные инструменты готовы, можно приступать к самой комнате. На объект комнаты вешаем:
Напоследок, сделаем вызов RoomGen.Dump(). Пусть, например, по триггерам на дверях, которые являются дочерними объектами относительно комнаты (объекта с компонентом RoomGen):
Вот и всё. Здесь опущено собственно взаимодействие с предметами и процесс изменения их состояния, но это несложно добавить. Для первоначального теста можно просто добавить в скрипт пару устанавливащих состояния функций по хоткеям, или ставить на паузу и двигать руками.
При первом запуску генерируется дефолтный вариант, при выходе изменения дампятся в файл, при возвращении последние состояние восстанавливается из файла, в том числе если приложение закрывалось. Works like a charm.
Один из существенных недостатков XML — игрок может легко изменить данные. И если в данном случае мало кого заинтересует расположение раскиданных стульев, то при сохранении более существенных для игрока данных сериализации в XML лучше избегать. Да и в реестре поменять значения не сложно. В таких случаях уже лучше использовать бинарную сериализацию или свой формат.
3. Save/Load через меню
Наверное, актуальнее было бы реализовать вариант с выбором/созданием пользователя и внутренними автоматическими сохранениями. Если вашей игре требуется серьёзное меню Save/Load, то вряд ли вы сейчас читаете эту статейку для профанов.
Но я жду не дождусь новогодних праздников, когда можно будет наконец увидеться с сестрой и за пару вечеров добить классическую American McGee’s Alice, так что сделаем Save/Load почти как там. Со скриншотами. Заодно будет повод покопаться в GUI, текстурах и других увлекательных вещах.
3.1 Главное меню
Чтобы сделать загрузку и сохранение через меню, нам, как ни странно, понадобится меню. Конечно, можно сделать его самостоятельно через объекты на сцене, можно заюзать готовые решения ироде NGUI , но мы пока воспользуемся GUI из штатной библиотеки.
- Scripting Reference
Для начала пригодятся:
OnGUI() — функция MonoBehaviour для отрисовки GUI и обработки связанных с ним событий. Нечто вроде Update(), но специально для GUI и вызываться может чаще, чем каждый фрейм.
функция кнопки. Рисует её в рамках заданного прямоугольника, реагирует на нажатие, возвращая true. Конструкторов больше, но нам хватит этих.
Группировка элементов гуи, полезна в основном переопределением границ относительно которых вычисляется положение вложенных элементов (дефолтно это границы экрана, в данном случает — прямоугольник position).
Сделаем отдельную стартовую сцену, а на ней пустой объект, к которому и прикрепим скрипт для нашего меню. Т.о. меню будет путешествовать сквозь сцены (при загрузке очередной сцены не пересоздаётся, а просто переносится в неё), хэндлить нужные события (вроде кнопки вызова меню), при вызове рисоваться поверх экрана игры.
Главное меню до и после начала игры
3.2 Рисуем меню загрузки / сохранения
Функция drawSaveLoadMenu() у нас уже вызывается при menutype>0, но пока не написана. Исправим это упущение. Пока просто научимся рисовать наши меню и вызывать собственно функции загрузки/сохранения.
- Scripting Reference
GUI.SelectionGrid — рисует сетку кнопок, но по сути это одновариантый селект. Всегда выбран один вариант, возвращает номер выбранного.
Количество — исходя из размеров передаваемого массива. Вообще предназначен для использования как-то так:
Кажется, это не совсем то, что нам требуется — нам-то нужно выбрать один раз и сразу отреагировать. Но SelectionGrid спокойно ест грязный хак — индекс вне пределов реального массива. Т.е. мы всегда будем передавать, допустим, -1 и тогда сможем отслеживать собственно событие клика.
Меню Load на SelectionGrid — внешне ничем не отличается от соответствующего Save
Основное, что мне в этом решении не нравится, это что в меню загрузки не содержащие сохранений слоты остаются относительно активными — внешне отличаются только отсутствием текстуры, реагируют на наведение. Поэтому бонусом — сетка ручками, вместо неактивных слотов рисуем Box, для активных Button.
Заодно добавим резиновости: количество слотов в строке задаётся, размер слотов подстраивается под экран. Правда, тут они уже квадратные, но встроить произвольное соотношение сторон будет несложно 🙂 Ну и заодно min/max width/height из GUILayout и прочая обработка напильником.
Меню Load на Button и Box — теперь пустые слоты неактивны
3.3 Текстуры, скриншоты
Итак, с момента создания нашего объекта меню мы будем держать массив текстур. Памяти он занимает немного и нам гарантирован в ним мгновенный доступ. На самом деле, тут и альтернативы особой нет — не пихать же работу с диском в onGUI().
Как мы уже видели, при создании нашего меню создаём и массив:
Сохранять мы будем не только информацию сейвов, но и информацию о них, а точнее — какие именно слоты содержат сохранения. Как хранить — выбор каждого, можно по параметру 0/1 на каждый слот, можно строку из 0/1, но мы сделаем некрасиво 🙂 и возьмём битовый вектор в int. В какой момент и как он сохраняется, увидим позже, пока просто читаем.
Добавим в Start():
Ну и собственно главное в данном вопросе — как скрины сохранять? Напрашивается вариант Application.CaptureScreenshot , но тут сразу два подвоха. Во-первых, они сохраняются в полном размере, а поскольку в кончном итоге понадобятся нам только thumbnails, логичнее сразу сделать ресайз. Во-вторых, мы же держим массив текстур, придётся в него снова считывать с диска? Не очень-то здорово.
Функцию взятия и записи скриншота вызывать будем позже, а пока заранее выделим в Coroutine:
Неприятный нерешенный момент — текстура с текущей сессии и текстура, загруженная с диска, сильно различаются по качеству.
Ниже слева две текущих, справа — две с диска, от предыдущих сессий:
3.4 Собственно реализация сохранения загрузки
Итак, вроде бы с шелухой разобрались. Научились минимально работе с GUI, сделали простое главное меню, меню Save/Load, научились работать со скриншотами.
Как реализовать взаимодействие между объектами сцены, параметры которых мы будем сохранять и нашим меню?
1. Если мы будем записывать только состояние такого же создаваемого с первой сцены и неразрушаемого далее объекта (например, игрок, его параметры и инвентарь) — можно сразу держать прямую ссылку.
2. GameObject.Find и GameObject.FindWithTag тут использовать практически не стыдно — загрузка/сохранение — разовое событие. Можно искать напрямую, а поскольку сцены могут содержать разную информацию — то, как вариант, добавлять на каждую специальный объект с определенным тегом, к которому и будет прикручен скрипт сохранения/загрузки собственно данной сцены, тут уже можно держать прямые ссылки на требуемые объекты.
А пока рассмотрим такой простой вариант. Сохранять будем только сцену и положение игрока. Игрок в каждой сцене пересоздаётся, но всегда вид от первого лица, и соответственно к игроку прикреплена камера.
Через неё и будем получать доступ. В ниже представленной функции вся эта специфика — в двух строках помеченных //!, и её не сложно локально заменить, остальное привязано к уже написанному нами выше коду.
Если делать скриншот заранее, то он во-первых, может не пригодится, а во-вторых, нужно ещё успеть. А так, с учётом заблокированности камеры в режиме меню, результат примерно тот же.
С загрузкой ещё проще. Всё специфику мы снова полностью делегируем, причём даже не будем её напрямую вызывать. Просто загрузим нужный уровень, а дальше они как-нибудь сами 🙂
Сделаем теперь поведение, который будем вешать на камеры:
Надо заметить „дальше как-нибудь сами“ было определенной степенью лукавства: loadgame() меню и load() объекта определенно обменялись информацией, только вот через известное место — реестр. Сохранять туда откровенно временную переменную — ход не слишком красивый. Можно изменить на прямой вызов load(), а без изменения текущей общей структуры — держать переменную в меню, и в Start() загружаемого объекта добавить поиск объекта меню и получение нужной информации.
Дальше. От созданного базового поведения мы можем унаследовать разные варианты для разных сцен и объектов. Например, вариант с сохранением поворота:
Конечно, здесь данным уже пригодилась бы защита. Поскольку поскольку вся фактическая работа с PlayerPrefs тут выделена в отдельные функции save() / load(), заменить их содержательную часть будет не сложно. На что? Можно аналогично примеру из части 2 держать класс-рефлектор, и сериализовать его через BinarySerializer.
Другой неплохой вариант — прикрутить, например, SQLite. Правда, по слухам, на js с ней работать удобнее, чем на шарпе, но и на последнем всё в конечном итоге заводится. Кто хочет попробовать, начать можно отсюда.
Этот текст никогда бы не получился без:
и хабра. Спасибо им.
Надеюсь, всё это принесёт кому-нибудь пользу, и никому — вреда 🙂
Источник