0

Где хранятся сохранения unity

Смотрите также:

Новая часть Assassin’s Creed под названием Unity не оправдала многие ожидания, но нам ли судить. Пока игра приносит деньги ее будут клепать каждый год с минимальными изменениями, зато постоянно завышая необходимые характеристики ПК для нормальной игры. Но речь сейчас не об этом, а о том, где AC Unity хранит сохранения.

Итак, в зависимости от типа игры, пиратка или лицензия, сохранения находятся в:

3DM:
Папка с игрой/save3dmgames

ALI:
Папка с игрой/Profile

Лицензия:
Облачное хранилище если включена синхронизация в Uplay или
C:Program Files (x86)UbisoftUbisoft Game Launchersavegames[Uplay ID Number]720
Папка 720 и есть каталог с сохранениями игры. Скопируйте его для бэкапа или удалите, чтобы начать игру сначала.

Вот и все. Сохранения с лицензии также можно передать другим, но что касается пиратки то, скорее всего, сохранения от 3DM будут работать только на 3DM. Попробуйте и скажите, так ли это, ну а пока удачи!

Большинство проектов созданных в Unity часто имеют систему хранения игровых данных. Эта система включает в себя инструменты для сохранения и загрузки данных. Как и где хранить эти данные часто зависит от того что это за игра, кто в нее играет и какое кол-во данных необходимо сохранить. Обычно различают два вида хранения данных: локальную, облачную (удаленную) и комбинированную.

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

Облачную систему чаще используют для многопользовательских проектов. В таких проектах игре необходимо иметь доступ к данным всех игроков, поэтому они используют сервера, где хранятся эти данные.

Комбинированную систему обычно используют в проектах, нацеленных как на одиночную игру, так и на многопользовательскую. В таких проектах необходимо хранить данные локально и удаленно.

В этой статье рассмотрим локальный тип хранения данных, и для этого в Unity есть очень простой инструмент PlayerPrefs.

PlayerPrefs – это небольшой набор методов для сохранения и загрузки данных из реестра системы. Сам реестр используется для иерархического хранения данных и настроек системы. В отличие от файловой системы, где хранятся файлы с любыми данными и которые доступны всем пользователям компьютера, в реестре хранятся только настройки программы с самыми необходимыми данными которые доступны только определенным пользователям, а PlayerPrefs, в свою очередь, позволяет записывать и считывать эти самые данные из реестра.

Для начала рассмотрим способы записи данных в реестр с помощью PlayerPrefs.

Система имеет несколько методов и все они работают по одному и тому же принципу: сначала указываем ключ под которым хотим записать данные, после чего указываем сами данные которые необходимо записать.

  • SetInt. Метод используется для записи целого числа(integer) в реестр.
  • SetFloat. Метод для записи числа с “плавающей” запятой или дробного числа(float).
  • SetString. Метод для записи текстовых данных.

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

  • GetInt. Метод используется для считывания целого числа(integer) из реестра.
  • GetFloat. Метод для считывания дробного числа(float).
  • GetString. Метод для считывания текстовых данных.

И так, мы разобрали основные методы для работы с PlayerPrefs, теперь попробуем сохранить с помощью этой системы некоторые данные в игре.

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

Начнем с простого сохранения кол-ва уничтоженных кораблей.

Создадим небольшой скрипт Control унаследованный от MonoBehaviour.

В числовой переменной kills будем хранить кол-во уничтоженных кораблей.

Теперь добавим метод сохранения Save.

В игре этот метод вызывается через UI кнопку.

После нажатия этой кнопки переменная kills запишется в реестр под указанным ключом.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicvoid Save () <
  4. string key = “ MyGame” ;
  5. PlayerPrefs . SetInt ( key, this . kills );
  6. PlayerPrefs . Save ();
  7. >
  8. >

И так первым действие указываем в переменной key ключ под которым необходимо будет записать данные, пусть, к примеру название ключа будет MyGame, далее вызываем метод SetInt в который передаем ключ и переменную kills, в конце завершаем запись данных в реестре с помощью метода Save.

Проверить записи данных можно в реестре. Для быстрого входа в реестр необходимо нажать комбинацию кнопок Win + R, после чего в окошке “Выполнить” ввести regedit и нажать “Ok”.

Далее необходимо найти раздел с игрой. Все данные unity проектов хранятся в разделе HKEY_CURRENT_USER/Software/Unity/UnityEditor/DefaultCompany в этом разделе находим проектом по названию, там и будут храниться все записи программы.

В разделе “Параметр” можно увидеть название ключа под которым записаны данные, а в разделе “Значение” число равное кол-ву уничтоженных кораблей в игре.

Именно в этом разделе мы будем хранить все остальные данные из игры.

Теперь необходимо произвести чтение данных из реестра.

Загрузку будет проводить при старте игры, для этого заведем новый метод Start в скрипте Control.

В методе Load, в переменную key укажем ключ под которым записаны наши данные.

Теперь с помощью условия проверим: существуют ли наш ключ в реестре, для этого используем метод HasKey.

Если ключ существует значит можно загрузить данные из реестра.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. privatevoid Start () <
  4. Load ();
  5. >
  6. privatevoid Load () <
  7. string key = “ MyGame” ;
  8. if ( PlayerPrefs . HasKey ( key )) <
  9. this . kills = PlayerPrefs . GetInt ( key );
  10. >
  11. >
  12. /*…метод Save…*/
  13. >

Отлично, данные загрузились.

И так, теперь мы научились сохранять и загружать самые элементарные данные из реестра. Теперь попробуем проделать все тоже самое с кол-во очков в игре, для этого объявим новую дробную переменную scores.

Теперь немного расширим метод Save, чтобы сохранить эту новую переменную в реестр.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicfloat scores = 0f ;
  4. publicvoid Save () <
  5. string key = “ MyGame” ;
  6. PlayerPrefs . SetInt ( key, this . kills );
  7. PlayerPrefs . SetFloat ( key, scores );
  8. PlayerPrefs . Save ();
  9. >
  10. >

В методе Load проведем аналогичные действия только по загрузке переменной scores.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicfloat scores = 0f ;
  4. privatevoid Start () <
  5. Load ();
  6. >
  7. privatevoid Load () <
  8. string key = “ MyGame” ;
  9. if ( PlayerPrefs . HasKey ( key )) <
  10. this . kills = PlayerPrefs . GetInt ( key );
  11. this . scores = PlayerPrefs . GetFloat ( key );
  12. >
  13. >
  14. /*…метод Save…*/
  15. >
Читайте также:  В интернете используются различные сервисы электронная почта

Запускаем игру, чтобы проверить работоспособность системы.

У методов записи и загрузки данных есть один недостаток, заключается он в том, что под одним ключом может храниться только одна переменная определенного типа. Мы уже использовали ячейки для записи целого числа int и дробного float, больше данный ключ вместить данных не может, но в игре еще остались данные которые необходимо записать в реестр – это кол-во жизней главной базы.

Кол-во жизней базы это тоже дробное число float, а так как ячейка дробного числа уже занята переменной scores, то получается, что мы не сможем поместить еще одну. В этом случае на помощь приходят текстовые данные. Мы просто преобразуем все данные для сохранения в текст и запишем его в реестр в текстовую ячейку, которая все еще пустая. Для удобного преобразования множества данных в текст и обратно используем JSONUtility.

JSON – это удобный текстовый формат хранения данных. Он преобразует любой объект в читаемый текст и обратно. С помощью него можно хранить практически любое кол-во данных в виде текста.

И так объявим новую переменную health в скрипте Control где будем хранить кол-во жизней базы.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicfloat scores = 0f ;
  4. publicfloat health = 100 ;
  5. >

Теперь нам нужен объект который будет хранить все эти три переменные. Для этого подойдет простой класс SaveData. Создадим новый скрипт SaveData и уберем у него наследование от MonoBehaviour.

Переходим в метод Save, откуда сотрем последние два действия SetInt и SetFloat.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicfloat scores = 0f ;
  4. publicfloat health = 100 ;
  5. publicvoid Save () <
  6. string key = “ MyGame” ;
  7. SaveData data = new SaveData ();
  8. PlayerPrefs . Save ();
  9. >
  10. >

Сначала создаем новый экземпляр класса SaveData, после чего наполняем его данными.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicfloat scores = 0f ;
  4. publicfloat health = 100 ;
  5. publicvoid Save () <
  6. string key = “ MyGame” ;
  7. SaveData data = new SaveData ();
  8. data . kills = this . kills ;
  9. data . scores = this . scores ;
  10. data . health = this . health ;
  11. PlayerPrefs . Save ();
  12. >
  13. >

Теперь необходимо преобразовать объект data в текст, для чего воспользуемся методом ToJson класса JsonUtility.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicfloat scores = 0f ;
  4. publicfloat health = 100 ;
  5. publicvoid Save () <
  6. string key = “ MyGame” ;
  7. SaveData data = new SaveData ();
  8. data . kills = this . kills ;
  9. data . scores = this . scores ;
  10. data . health = this . health ;
  11. stringvalue = JsonUtility . ToJson ( data );
  12. PlayerPrefs . Save ();
  13. >
  14. >

После чего сохраняем полученный текст в реестр с помощью метода SetString.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicfloat scores = 0f ;
  4. publicfloat health = 100 ;
  5. publicvoid Save () <
  6. string key = “ MyGame” ;
  7. SaveData data = new SaveData ();
  8. data . kills = this . kills ;
  9. data . scores = this . scores ;
  10. data . health = this . health ;
  11. stringvalue = JsonUtility . ToJson ( data );
  12. PlayerPrefs . SetString ( key, value );
  13. PlayerPrefs . Save ();
  14. >
  15. >

Теперь необходимо проделать действия по загрузке данных в методе Load и перевести текст обратно в объект SaveData с помощью того же JSONUtility.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicfloat scores = 0f ;
  4. publicfloat health = 100 ;
  5. privatevoid Start () <
  6. Load ();
  7. >
  8. privatevoid Load () <
  9. string key = “ MyGame” ;
  10. if ( PlayerPrefs . HasKey ( key )) <
  11. stringvalue = PlayerPrefs . GetString ( key );
  12. >
  13. >
  14. /*…метод Save…*/
  15. >

Как и раньше проверяем существование ключа, после чего загружаем текст из реестра. Далее преобразуем полученный текст в объект SaveData с помощью метода FromJson класса JsonUtility.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicfloat scores = 0f ;
  4. publicfloat health = 100 ;
  5. privatevoid Start () <
  6. Load ();
  7. >
  8. privatevoid Load () <
  9. string key = “ MyGame” ;
  10. if ( PlayerPrefs . HasKey ( key )) <
  11. stringvalue = PlayerPrefs . GetString ( key );
  12. SaveData data = JsonUtility . FromJson SaveData >( value );
  13. >
  14. >
  15. /*…метод Save…*/
  16. >

В методе FromJson, в фигурных скобках указываем тип объекта который мы хотим получить из текста, а в сам метод передаем текстовую переменную value в которой находится загруженный текст из реестра. Получив целый объект из текста применяем сохраненные значения переменных обратно.

  1. publicclass Control : MonoBehaviour <
  2. publicint kills = 0 ;
  3. publicfloat scores = 0f ;
  4. publicfloat health = 100 ;
  5. privatevoid Start () <
  6. Load ();
  7. >
  8. privatevoid Load () <
  9. string key = “ MyGame” ;
  10. if ( PlayerPrefs . HasKey ( key )) <
  11. stringvalue = PlayerPrefs . GetString ( key );
  12. SaveData data = JsonUtility . FromJson SaveData >( value );
  13. this . kills = data . kills ;
  14. this . scores = data . scores ;
  15. this . health = data . health ;
  16. >
  17. >
  18. /*…метод Save…*/
  19. >

Запускаем для проверки.

Сохранение и загрузка работают исправно. Переходим в реестр и проверяем данные.

Теперь в разделе “Значение” мы видим текст со всеми переменными и их значениями.

Сохранение и загрузка данных через PlayerPrefs имеет свои преимущества перед другими видами локального хранения данных: во первых простотой работы, вам не нужно работать с файлами и лезть в файловую систему вообще, во вторых при работе с файловой системе, к примеру, на некоторых платформах вам нужно иметь разрешение на чтение и запись данных, для PlayerPrefs в этом нет необходимости он работает на всех устройствах одинаково. Поэтому PlayerPrefs отлично подходит для хранение небольшого кол-ва несложных данных на устройстве.

Ознакомится с проектом из статьи можно по ссылке .

Если вы пишете не казуалку под веб и не беспощадный суровый рогалик, без сохранения данных на диск не обойтись.
Как это делается в 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 с ней работать удобнее, чем на шарпе, но и на последнем всё в конечном итоге заводится. Кто хочет попробовать, начать можно отсюда.

    Этот текст никогда бы не получился без:

    и хабра. Спасибо им.
    Надеюсь, всё это принесёт кому-нибудь пользу, и никому — вреда 🙂

    admin

    Добавить комментарий

    Ваш e-mail не будет опубликован. Обязательные поля помечены *