Как сделать сохранения в unity
Сохранение и загрузка данных в Unity игре
Большинство проектов созданных в Unity часто имеют систему хранения игровых данных. Эта система включает в себя инструменты для сохранения и загрузки данных. Как и где хранить эти данные часто зависит от того что это за игра, кто в нее играет и какое кол-во данных необходимо сохранить. Обычно различают два вида хранения данных: локальную, облачную (удаленную) и комбинированную.
Локальную систему хранения данных часто используют в одиночных играх, где необходимо хранить несущественные данные, вроде прогресса прохождения или характеристик персонажей и тд.
Облачную систему чаще используют для многопользовательских проектов. В таких проектах игре необходимо иметь доступ к данным всех игроков, поэтому они используют сервера, где хранятся эти данные.
Комбинированную систему обычно используют в проектах, нацеленных как на одиночную игру, так и на многопользовательскую. В таких проектах необходимо хранить данные локально и удаленно.
В этой статье рассмотрим локальный тип хранения данных, и для этого в Unity есть очень простой инструмент PlayerPrefs.
PlayerPrefs – это небольшой набор методов для сохранения и загрузки данных из реестра системы. Сам реестр используется для иерархического хранения данных и настроек системы. В отличие от файловой системы, где хранятся файлы с любыми данными и которые доступны всем пользователям компьютера, в реестре хранятся только настройки программы с самыми необходимыми данными которые доступны только определенным пользователям, а PlayerPrefs, в свою очередь, позволяет записывать и считывать эти самые данные из реестра.
Для начала рассмотрим способы записи данных в реестр с помощью PlayerPrefs.
Система имеет несколько методов и все они работают по одному и тому же принципу: сначала указываем ключ под которым хотим записать данные, после чего указываем сами данные которые необходимо записать.
Для загрузки есть аналогичные методы, которые возвращают сохраненные ранее данные под определенным ключом.
И так, мы разобрали основные методы для работы с PlayerPrefs, теперь попробуем сохранить с помощью этой системы некоторые данные в игре.
Игра представляет собой небольшую аркаду в которой необходимо отстреливать инопланетные корабли до того как они захватят главную базу.
Начнем с простого сохранения кол-ва уничтоженных кораблей.
Создадим небольшой скрипт Control унаследованный от MonoBehaviour.
В числовой переменной kills будем хранить кол-во уничтоженных кораблей.
Теперь добавим метод сохранения Save.
В игре этот метод вызывается через UI кнопку.
После нажатия этой кнопки переменная kills запишется в реестр под указанным ключом.
И так первым действие указываем в переменной key ключ под которым необходимо будет записать данные, пусть, к примеру название ключа будет MyGame, далее вызываем метод SetInt в который передаем ключ и переменную kills, в конце завершаем запись данных в реестре с помощью метода Save.
Проверить записи данных можно в реестре. Для быстрого входа в реестр необходимо нажать комбинацию кнопок Win + R, после чего в окошке “Выполнить” ввести regedit и нажать “Ok”.
Далее необходимо найти раздел с игрой. Все данные unity проектов хранятся в разделе HKEY_CURRENT_USER/Software/Unity/UnityEditor/DefaultCompany в этом разделе находим проектом по названию, там и будут храниться все записи программы.
В разделе “Параметр” можно увидеть название ключа под которым записаны данные, а в разделе “Значение” число равное кол-ву уничтоженных кораблей в игре.
Именно в этом разделе мы будем хранить все остальные данные из игры.
Теперь необходимо произвести чтение данных из реестра.
Загрузку будет проводить при старте игры, для этого заведем новый метод Start в скрипте Control.
В методе Load, в переменную key укажем ключ под которым записаны наши данные.
Теперь с помощью условия проверим: существуют ли наш ключ в реестре, для этого используем метод HasKey.
Если ключ существует значит можно загрузить данные из реестра.
Отлично, данные загрузились.
И так, теперь мы научились сохранять и загружать самые элементарные данные из реестра. Теперь попробуем проделать все тоже самое с кол-во очков в игре, для этого объявим новую дробную переменную scores.
Теперь немного расширим метод Save, чтобы сохранить эту новую переменную в реестр.
В методе Load проведем аналогичные действия только по загрузке переменной scores.
Запускаем игру, чтобы проверить работоспособность системы.
У методов записи и загрузки данных есть один недостаток, заключается он в том, что под одним ключом может храниться только одна переменная определенного типа. Мы уже использовали ячейки для записи целого числа int и дробного float, больше данный ключ вместить данных не может, но в игре еще остались данные которые необходимо записать в реестр – это кол-во жизней главной базы.
Кол-во жизней базы это тоже дробное число float, а так как ячейка дробного числа уже занята переменной scores, то получается, что мы не сможем поместить еще одну. В этом случае на помощь приходят текстовые данные. Мы просто преобразуем все данные для сохранения в текст и запишем его в реестр в текстовую ячейку, которая все еще пустая. Для удобного преобразования множества данных в текст и обратно используем JSONUtility.
JSON – это удобный текстовый формат хранения данных. Он преобразует любой объект в читаемый текст и обратно. С помощью него можно хранить практически любое кол-во данных в виде текста.
И так объявим новую переменную health в скрипте Control где будем хранить кол-во жизней базы.
Теперь нам нужен объект который будет хранить все эти три переменные. Для этого подойдет простой класс SaveData. Создадим новый скрипт SaveData и уберем у него наследование от MonoBehaviour.
Переходим в метод Save, откуда сотрем последние два действия SetInt и SetFloat.
Сначала создаем новый экземпляр класса SaveData, после чего наполняем его данными.
Теперь необходимо преобразовать объект data в текст, для чего воспользуемся методом ToJson класса JsonUtility.
После чего сохраняем полученный текст в реестр с помощью метода SetString.
Теперь необходимо проделать действия по загрузке данных в методе Load и перевести текст обратно в объект SaveData с помощью того же JSONUtility.
Как и раньше проверяем существование ключа, после чего загружаем текст из реестра. Далее преобразуем полученный текст в объект SaveData с помощью метода FromJson класса JsonUtility.
В методе FromJson, в фигурных скобках указываем тип объекта который мы хотим получить из текста, а в сам метод передаем текстовую переменную value в которой находится загруженный текст из реестра. Получив целый объект из текста применяем сохраненные значения переменных обратно.
Запускаем для проверки.
Сохранение и загрузка работают исправно. Переходим в реестр и проверяем данные.
Теперь в разделе “Значение” мы видим текст со всеми переменными и их значениями.
Сохранение и загрузка данных через PlayerPrefs имеет свои преимущества перед другими видами локального хранения данных: во первых простотой работы, вам не нужно работать с файлами и лезть в файловую систему вообще, во вторых при работе с файловой системе, к примеру, на некоторых платформах вам нужно иметь разрешение на чтение и запись данных, для PlayerPrefs в этом нет необходимости он работает на всех устройствах одинаково. Поэтому PlayerPrefs отлично подходит для хранение небольшого кол-ва несложных данных на устройстве.
Unity: система сохранения для любого проекта
Игры надо сохранять. Сохраняемых сущностей может быть великое множество. Например, в последних выпусках TES и Fallout игра помнит расположение каждой закатившейся склянки. Необходимо решение, чтобы:
1) Написал один раз и используй в любом проекте для любых сущностей. Ну, насколько возможно.
2) Создал сущность — и она сохраняется сама собою, с минимумом дополнительных усилий.
Решение пришло из стана синглтонов. Не надоело ли вам писать один и тот же синглтон-код? А меж тем есть generic singleton.
Т.е. можно сделать Generic класс, статические поля которого будут уникальны для каждого входного типа.
И это как раз наш случай. Потому что поведение сохраняемого объекта полностью идентично, различаются только сохраняемые модели. И тип модели как раз и выступает в качестве входного.
Вот код интерфейса модели. Он примечателен тем, что метод SetValues примет в качестве аргумента только модель такого же (или производного) типа. Не чудо ли?
Для модели также нужен обобщенный контроллер, но с ним связан нижеследующий нюанс, поэтому пока что опустим.
От этих классов — абстрактной модели и обобщенного контроллера можно наследовать всё, что сохраняется и загружается. Написал модель, унаследовал контроллер — и забыл, всё работает. Отлично!
А что делать с сохранением и загрузкой? Ведь нужно сохранять и загружать сразу всё. А писать для каждой новой сущности код для сохранения и загрузки в каком-нибудь SaveLoadManager — утомительно и легкозабываемо.
И тут на помощь приходят статики.
1) Абстрактный класс с protected функциями сохранения и загрузки
2) У него — статичная коллекция All, куда каждый экземпляр класса-потомка добавляется при инициализации
3) И статичные публичные функции сохранения и загрузки, внутри которых перебираются все экземпляры из All и вызываются конкретные методы сохранения и загрузки.
И вот какой код получается в результате.
Примеры унаследованных конкретных классов:
Недостатки решения и способы их исправления.
1) Сохраняется (перезаписывается) всё. Даже то, что не было изменено.
Возможное решение: проверять перед сохранением равенство полей у исходной и текущей моделей и сохранять только при необходимости.
2) Загрузка из файла. Из json, например. Вот есть список моделей. Как загрузчику узнать, какой класс надо создать для этого json-текста?
Возможное решение: сделать словарь где регистрировать типы хардкодом. При загрузке из json берется строковой идентификатор типа и инстанцируется объект нужного класса. При сохранении объект проверяет, есть ли в словаре ключ его типа, и выдает сообщение/ошибку/исключение. Это позволит стороннему программисту не забыть добавить новый тип в словарь.
Посмотреть мой код с этим и другими хорошими решениями можно здесь (проекты в начальной стадии):
Замечания, улучшения, советы — приветствуются.
Предложения помощи и совместного творчества приветствуются.
Предложения о работе крайне приветствуются.
UPD:
Вижу, возникают вопросы а ля «Каков профит от твоего решения? Все равно же делать модели, делать сериализацию.»
Отвечаю:
Вы пришли на чекпоинт или нажали кнопку сохранить. Кнопка или чекпоинт сообщили классу-менеджеру, что нужно сохранить состояние игры. Что делает менеджер?
Плохой вариант 2: Каждый SaveLoadBehaviour подписывается на событие OnSave менеджера. Или регистрирует себя в каком-то «контейнере».
Плохо, потому что SaveLoadBehaviour должен знать о существовании менеджера/контейнера. Я же пытался сделать так, чтобы классы были максимально автономны, а все знания об их связях хранились в самом менеджере.
Плохой вариант 3: менеджер при инициализации ищет все сохраняемые компоненты.
1) Функция поиска может отличаться между платформами. GameObject.FindObjectsOfType() применима только для MonoBehaviour, а что если мы делаем shared-логику? Реализация должна быть максимально гибкой и кроссплатформенной.
2) Если мы решим переписать менеджер с нуля (для другой игры, например), то надо обязательно не забыть вставить функцию поиска.
Мой хороший вариант:
Еще мне задали вопрос, что делать, если мы хотим положить на один геймобжект несколько saveloadbehaviour? Как они при загрузке соберутся в один геймобжект?
Как сделать сохранение в Unity через файлы?
Довольно часто в играх можно найти функцию сохранения и загрузки некоторого прогресса прохождения в играх. Это фишка засела настолько глубоко в нашей жизни, что и зачастую в реальности хочется просто сохраниться, а когда придёт время, начать заново проживать жизнь от какого-либо момента.
Если говорить по научному, то: сохранение игры — запись на какое-либо постоянное запоминающее устройство текущего состояния прохождения компьютерной игры, с возможностью вернуться к нему в будущем.
Даже в стандартных играх на Windows, взять например карты, существуют сохранения текущей партии. Так к примеру, если открыть игру Microsoft Solitaire Collections, а далее выбрать режим игры и перемещать карты. То при повторном открытии, можно заметить, что карты остались на тех местах, на которые вы и переместили их.
Как сделать сохранение в файл?
Перед тем как перейти к множеству строк кода, я бы хотел вам объяснить, как будет работать пример, на котором и собираюсь рассказать принцип сохранения данных в файл. За основу я возьму небольшую сценку, в которой присутствует текстовое поле, и две кнопки, одна из которых будет сохранять данные, а другая извлекать их из файла.
Ни чего сложно, вводим текст в текстовое поле, нажимаем клавишу сохранить, очищаем поле, нажимаем клавишу загрузить, получаем результат.
Переходим к написанию скрипта, в первую очередь нам необходимо подключить две библиотеки:
Данные библиотеки позволяют работать с файлами, а также с UI элементами Unity3D.
Следующим шагом объявляем переменные, которые будем использовать при работе с созданием файла и записи в него данных. Ну и собственно, создаём саму функцию, отвечающую за создание файла и записи в него данных.
Перед тем как запустить игру, не забудьте закинуть скрипт на какой-либо из элементов, а также настроить его, указав наименование файла, текст из элемента InputField.
Кроме этого, не забудьте установить кнопке сохранения, функцию, отвечающая за сохранения данных в файл.
Запускаем игру, жмем на кнопку и с удивительными глазами смотрим на результат.
Как можно заметить в указанной мною папке, создался текстовый документ, в который и занеслась наша строка.
Как сделать чтение из файла?
С чтением из файла дела обстоят аналогично.
Запускаем проект и проверяем:
Именно таким способом можно реализовать загрузку и сохранение данных в Unity3D.
Данную систему, можно более развить, записывая в файл целый джейсон, предварительно превратив его в обычный текст.
Как сделать сохранения в unity
Текущее время: 24 янв 2021, 15:15
Сохранение данных
Re: Сохранение данных
Создай массив предметов. И сохраняй их индексы в массиве. А потом, при загрузке, выгружай объекты по сохраненному индексу.
Re: Сохранение данных
Создай массив предметов. И сохраняй их индексы в массиве. А потом, при загрузке, выгружай объекты по сохраненному индексу.
спасибо. вот первый способ насколько безопасен и остальные в принципе тоже? Я имею ввиду, возьмут взломают и поставят на персонажа что захотят.
Re: Сохранение данных
вообще, можешь перед записью шифровать данные, а потом при загрузке расшифровывать.
Re: Сохранение данных
вообще, можешь перед записью шифровать данные, а потом при загрузке расшифровывать.
Re: Сохранение данных
Re: Сохранение данных
Re: Сохранение данных
Re: Сохранение данных
я бы сделал так:
1 определил бы формат сохраняемых данных в виде структур или класов, не содержищих данные ссылочного типа, хотя некоторые можно, массивы, например
2 сериализовал получаемые по ходу игры структуры (binary formatter, например)
3 сохранял в бинарник полученый массив байтов
соответственно, чтобы вытащить инфу, нужно получить массив байтов и десериализовать их
Re: Сохранение данных
Re: Сохранение данных
BlackMamba писал(а): я бы сделал так:
1 определил бы формат сохраняемых данных в виде структур или класов, не содержищих данные ссылочного типа, хотя некоторые можно, массивы, например
2 сериализовал получаемые по ходу игры структуры (binary formatter, например)
3 сохранял в бинарник полученый массив байтов
соответственно, чтобы вытащить инфу, нужно получить массив байтов и десериализовать их
Re: Сохранение данных
BlackMamba писал(а): я бы сделал так:
1 определил бы формат сохраняемых данных в виде структур или класов, не содержищих данные ссылочного типа, хотя некоторые можно, массивы, например
2 сериализовал получаемые по ходу игры структуры (binary formatter, например)
3 сохранял в бинарник полученый массив байтов
соответственно, чтобы вытащить инфу, нужно получить массив байтов и десериализовать их
Re: Сохранение данных
Re: Сохранение данных
Кто сейчас на конференции
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 16
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
Русская поддержка phpBB
Практическое руководство по взлому (и защите) игр на Unity
Когда речь идёт о программном обеспечении, термин «взлом» зачастую ассоциируют с пиратством и нарушением авторских прав. Данная статья не об этом; напротив, я решительно не одобряю любые действия, которые прямо или косвенно могут навредить другим разработчикам. Тем не менее, эта статья всё же является практическим руководством по взлому. Используя инструменты и методы о которых далее пойдёт речь, вы сможете проверить защиту собственной Unity игры и узнаете, как обезопасить её от взлома и кражи ресурсов.
Введение
В основе взлома лежит знание: необходимо понимать особенности компиляции Unity-проекта, чтобы его взломать. Прочитав статью, вы узнаете, каким образом Unity компилирует ресурсы игры и как извлечь из них исходные материалы: текстуры, шейдеры, 3D-модели и скрипты. Эти навыки будут полезны не только для анализа безопасности проекта, но также для его продвинутой отладки. В связи с закрытостью исходного кода, Unity часто работает как «черный ящик» и порой единственный способ понять, что именно в нём происходит — это изучение скомпилированной версии скриптов. Кроме прочего, декомпиляция чужой игры может стать серьёзным подспорьем в поиске её секретов и «пасхальных яиц». Например, именно таким образом было найдено решение финальной головоломки в игре FEZ.
Находим ресурсы игры
Рассмотрим для примера игру, собранную под ОС Windows и загруженную через Steam. Чтобы добраться до директории, в которой находятся нужные нам ресурсы, откроем окно свойств игры в библиотеке Steam и в закладке «Local files» нажмём «Browse local files…».
Извлекаем текстуры и шейдеры
Графический интерфейс программы не отличается удобством, а также она страдает от нескольких критических багов. Не взирая на это, программа вполне способна извлечь большинство текстур и шейдеров из игры. Полученные в результате текстуры будут иметь формат DDS, который можно «прочитать» с помощью Windows Texture Viewer.
С шейдерами ситуация обстоит сложнее: они извлекаются в уже скомпилированным виде и, насколько мне известно, решений для их автоматической трансляции в удобочитаемый формат не существует. Тем не менее, это обстоятельство не мешает импортировать и использовать полученные шейдеры в другом Unity-проекте. Не забывайте, однако, что подобная «кража» нарушает авторские права и является актом пиратства.
Извлекаем 3D-модели
Трёхмерные модели в типовой Unity-сборке «разбросаны» по различным ресурсам, а некоторые из них и вовсе могут генерироваться во время игры. Вместо копания в файлах, существует интересная альтернатива — получить данные о геометрии прямиком из памяти графического ускорителя. Когда игра запущена, вся информация о текстурах и моделях, видимых на экране, находится в памяти видеокарты. С помощью утилиты 3D Ripper DX можно извлечь всю эту информацию и сохранить в формате, понятном 3D-редакторам (например, 3D Studio Max). Учтите, что программа не самая простая в обращении — возможно, придётся обратиться к документации.
Взламываем PlayerPrefs
Защищаем PlayerPrefs
Помешать пользователю редактировать значения в системном реестре мы не в силах. А вот проверить, изменялись ли эти значения без нашего ведома — вполне реально. В этом нам помогут хеш-функции: сравнив контрольные суммы хранимых данных, мы сможем убедиться, что никто и ничто, кроме нашего кода эти данные не изменяло.
Приведенный выше класс — упрощенный пример реализации, работающий со строковыми переменными. Для инициализации ему необходимо передать секретный ключ и список PlayerPrefs-ключей, значения которых должны быть защищены:
Затем его можно использовать следующим образом:
Взламываем исходный код
Данных подход особенно эффективен для наших целей: Unity очень скупо оптимизирует исходный код игровых скриптов, практически не изменяя его структуру, а также не скрывает названия переменных. Это позволяет с легкостью читать и понимать декомпилированый материал.
Защищаем исходный код
Раз Unity не заботится о сохранности нашего кода — сделаем это сами. Благо, существует утилита, готовая автоматически зашифровать плоды нашего интеллектуального труда: Unity 3D Obfuscator.
И хотя программа отлично справляется со своими обязанностями, многие классы, адресуемые извне родной библиотеки, всё же не могут быть зашифрованы без риска нарушения связанности — будьте осторожны!
Взламываем память игры
Cheat Engine — широко известная программа для взлома игр. Она находит ту область оперативной памяти, которая принадлежит процессу запущенной игры и позволяет произвольно её изменять.
Эта программа пользуется тем фактом, что разработчики игр очень редко защищают значения переменных. Рассмотрим следующий пример: в некой игре у нас есть 100 патронов; используя Cheat Engine, можно выполнить поиск участков памяти, которые хранят значение «100». Затем мы делаем выстрел — запас патронов составляет 99 единиц. Снова сканируем память, но теперь ищем значение «99». После нескольких подобных итераций можно с легкостью обнаружить расположение большинства переменных игры и произвольно их изменять.
Защищаем память игры
Использовать нашу новую структуру можно следующим образом:
Если вы выводите значения переменных на экран, хакеры всё ещё смогут перехватить и поменять их, но это не повлияет на действительные значения, хранящиеся в памяти и использующиеся в логике игры.
Заключение
К сожалению, существует не так уж много способов защитить игру от взлома. Будучи установленной на пользовательское устройство, она фактически раскрывает все ваши текстуры, модели и исходный код. Если кто-то захочет декомпилировать игру и украсть ресурсы — это лишь вопрос времени.
Невзирая на это, существуют действенные методы, которые позволят серьёзно усложнить жизнь злоумышленникам. Это не значит, что нужно вдаваться в панику, шифровать весь исходный код и защищать каждую переменную, но по крайней мере задумайтесь, какие ресурсы вашего проекта действительно важны и что вы можете сделать для их защиты.