Как удалить json файл unity
У меня есть список элементов, отправленных из файла PHP в единство с использованием WWW .
Где я обрезаю лишнее [] из string . Когда я пытаюсь разобрать его с помощью Boomlagoon.JSON , извлекается только первый объект. Я обнаружил, что у меня есть deserialize() список, и импортировал MiniJSON.
Я использую класс
После обрезки [] я могу проанализировать json с помощью MiniJSON. Но возвращается только первая KeyValuePair .
Зачем убрали внешнее [ и ] ? Вот что делает его списком. Просто прекратите удалять это и десериализуйте его как массив или список, и я ожидаю, что все будет в порядке. Пожалуйста, опубликуйте код, который вы пробовали.
Покажите нам класс, используемый для десериализации. Формат странный, почему второй playerId не заключен в фигурные скобки? Он должен десериализации в список чего - то, как List
@MaximilianGerhardt Извините, фигурные скобки были опечаткой. Исправил это в вопросе и также добавил код. Спасибо.
@MaximilianGerhardt Я пробовал IDictionary
Unity добавила JsonUtility в свой API после обновления 5.3.3 . Забудьте обо всех сторонних библиотеках, если вы не делаете что-то более сложное. JsonUtility быстрее, чем другие библиотеки Json. Обновите Unity до версии 5.3.3 или выше и попробуйте решение, указанное ниже.
JsonUtility это легкий API. Поддерживаются только простые типы. Он не поддерживает такие коллекции, как Dictionary. Одно исключение List . Он поддерживает List и List массив!
Если вам нужно сериализовать Dictionary или сделать что-то иное, кроме простой сериализации и десериализации простых типов данных, используйте сторонний API. В противном случае продолжайте чтение.
Пример класса для сериализации:
1. ОДИН ОБЪЕКТ ДАННЫХ (НЕ МАССИВНЫЙ JSON)
Сериализация части A :
Выполните сериализацию в Json с помощью public static string ToJson(object obj); метода.
Сериализация части B :
Выполните сериализацию в Json с помощью public static string ToJson(object obj, bool prettyPrint); перегрузки метода. Простой переход true к JsonUtility.ToJson функции отформатирует данные. Сравните вывод ниже с выводом выше.
Десериализация части A :
Десериализуйте json с помощью public static T FromJson(string json); перегрузки метода.
Десериализация части B :
Десериализация части C :
Десериализуйте json с помощью public static void FromJsonOverwrite(string json, object objectToOverwrite); метода. Когда JsonUtility.FromJsonOverwrite используется, новый экземпляр этого объекта, который вы десериализуете, создаваться не будет. Он просто повторно использует переданный вами экземпляр и перезапишет его значения.
Это эффективно, и по возможности его следует использовать.
2. НЕСКОЛЬКО ДАННЫХ (МАССИВ JSON)
Ваш Json содержит несколько объектов данных. Например playerId появлялся не раз . Unity JsonUtility не поддерживает массив, поскольку он все еще новый, но вы можете использовать вспомогательный класс от этого человека, чтобы заставить работать массив JsonUtility .
Создайте класс с именем JsonHelper . Скопируйте JsonHelper прямо снизу.
Сериализация массива Json :
Десериализация массива Json :
Если это массив Json с сервера, и вы не создали его вручную :
Я сделал для этого простую функцию:
тогда вы можете использовать его:
3. десериализация строки json без класса && десериализация Json с числовыми свойствами
Это Json, который начинается с числа или числовых свойств.
Unity JsonUtility не поддерживает это, потому что свойство «15m» начинается с числа. Переменная класса не может начинаться с целого числа.
Загрузите SimpleJSON.cs из вики Unity .
Чтобы получить недвижимость «15 миллионов долларов»:
Чтобы получить собственность "15m" в ISK:
Чтобы получить недвижимость NZD "15m":
Остальные свойства Json, которые не начинаются с числовой цифры, могут обрабатываться Unity JsonUtility.
4.УСТРАНЕНИЕ НЕПОЛАДОК JsonUtility:
Проблемы при сериализации с помощью JsonUtility.ToJson ?
Получение пустой строки или " <> " с помощью JsonUtility.ToJson ?
. Убедитесь, что класс не является массивом. Если это так, используйте приведенный выше вспомогательный класс JsonHelper.ToJson вместо JsonUtility.ToJson .
B . Добавьте [Serializable] в начало сериализуемого класса.
C . Удалите свойство из класса. Например, в переменной public string playerId < get; set; >удалить < get; set; >. Unity не может сериализовать это.
Проблемы при десериализации с помощью JsonUtility.FromJson ?
. Если вы получили Null , убедитесь, что Json не является массивом Json. Если это так, используйте приведенный выше вспомогательный класс JsonHelper.FromJson вместо JsonUtility.FromJson .
B . Если NullReferenceException при десериализации у вас получится , добавьте [Serializable] в начало класса.
C. Любые другие проблемы, убедитесь, что ваш json действителен. Перейти на этот сайт здесь и вставьте JSON. Он должен показать вам, действителен ли json. Он также должен сгенерировать правильный класс с Json. Просто не забудьте удалить remove < get; set; >из каждой переменной, а также добавить [Serializable] в начало каждого сгенерированного класса.
Newtonsoft.Json:
Если по какой-то причине необходимо использовать Newtonsoft.Json, ознакомьтесь с разветвленной версией для Unity здесь . Обратите внимание, что при использовании определенной функции может возникнуть сбой. Быть осторожен.
Чтобы ответить на ваш вопрос :
Ваши исходные данные
Теперь у вас есть:
Для сериализации в несколько данных из PHP как массивы , теперь вы можете сделать
playerInstance[0] ваши первые данные
playerInstance[1] ваши вторые данные
playerInstance[2] это ваши третьи данные
или данные внутри класса с playerInstance[0].playerLoc , playerInstance[1].playerLoc , playerInstance[2].playerLoc .
Вы можете использовать, playerInstance.Length чтобы проверить длину, прежде чем получить к ней доступ.
ПРИМЕЧАНИЕ: Удалите < get; set; >из player класса. Если да < get; set; >, то не пойдет. Юнити JsonUtility это НЕ работать с членами класса , которые определяются как свойства .
Я возвращаю массив строк mysql запроса из PHP, используя json_encode($row) . Таким образом, ответ состоит из нескольких объектов JSONObject в формате [<"playerId":"1","playerLoc":"Powai">,,] . Я попробовал JsonUtility, но не смог десериализовать объекты и получить отдельные объекты json. Если вы можете мне с этим помочь."playerId":"1","playerLoc":"Powai">
Посмотрите на код, который я разместил выше. Он показывает вам три способа сделать это с помощью JsonUtility.FromJson . Я забыл сказать тебе, чтобы ты удалился < get; set; >из player класса. Если да < get; set; >, это не сработает. Сравните ваш player класс с тем, что я опубликовал выше, и вы поймете, о чем я говорю.
Нет проблем. Я отредактирую это, когда Unity добавит поддержку массива (что очень скоро), поэтому вам больше не понадобится этот класс Helper.
Я бы не стал говорить: «Забудьте обо всех сторонних библиотеках». У JsonUtility есть ограничения. Он не возвращает объект JSON, с которым вы можете выполнять действия. Например, я получаю файл json и хочу проверить, доступен ли ключ «успеха». Не могу. JsonUtility требует, чтобы потребитель знал точное содержимое файла json. Также нет конвертера словарей. Таким образом, он делает некоторые хорошие вещи, но использование сторонних разработчиков все еще требуется.
Используйте JsonHelper . Это замечательно. Если вы создаете с ним Json, вы также можете читать с ним json без дополнительных действий. Единственный раз, когда вам может потребоваться дополнительная работа, - это если вы получаете массив json с сервера, и это включено в решение, в моем ответе. Другой способ JsonHelper - поместить класс в другой класс, а затем сделать его классом List . Это работает для большинства людей. Если вы ищете способ сохранить и загрузить данные игры, посмотрите это . Вы загружаете и сохраняете с помощью одной строчки кода.
Предположим, у вас есть такой JSON
Чтобы проанализировать приведенный выше JSON в единстве, вы можете создать такую модель JSON.
А затем просто проанализируйте следующим образом .
На самом деле это работает довольно хорошо, если он работает с таким классом, как Symbol, который также не является массивом.
Используется в Unity 2018 и 2019. Отлично работает. Доступ к данным массива, например: Debug.Log(myObject.result[0].symbol[0].data); или for(var i = 0; i или foreach (var item in myObject.result)
@Narottam Goyal, ваш метод не работает в некоторых случаях, также очень сложное решение для новичков,
@JunedKhanMomin ваш ответ в основном такой же, но без учета того факта, что этот вопрос здесь касался массива корневого уровня в данных JSON, в частности. В общем, вам лучше обратиться к ответу программиста, который более подробен.
Я хочу скопировать свой существующий проект Unity в новый пустой проект Unity. Похоже, что все ссылки на объекты и многие сценарии не настроены должным образом / не присутствуют в Иерархии в моих сценах.
Я только скопировал папку с ресурсами / package.json в новый проект Unity, потому что остальные файлы грязные и кэшировали много бесполезной информации.
Где находятся файлы с подробными данными о сценах? Я хочу скопировать это, чтобы я мог запускать свои сцены со всеми ссылками на объекты и сценариями, установленными на правильные игровые объекты.
2 ответа
Убираться
Обычно вам всегда нужны папки Assets и ProjectSettings .
Все остальное вы можете удалить, и Unity воссоздаст / перекомпилирует их, когда вы снова откроете проект.
При резервном копировании проекта или добавлении проекта в репозиторий контроля версий вы должны включить основную папку проекта Unity, содержащую как активы и папки ProjectSettings . Вся информация в этих папках имеет решающее значение для работы Unity .
Как говорят имена
- Assets - это все ваши активы, такие как сценарии, изображения, сборные конструкции и т. Д., Включая также сцены.
- ProjectSettings - это общие настройки вашего проекта, касающиеся таких вещей, как качество, физика, струны плеера и т. Д.
Для меня также имеет смысл сохранить некоторые файлы Library/*.asset .. они хранят, например, настройки сборки, целевую платформу и т. д., поэтому мне не нужно настраивать их / переключать платформу с нуля каждый раз, когда я "перезагружаюсь". Конечно, вам решать, хотите ли вы это сделать.
Вы также можете использовать любую другую систему контроля версий . или, конечно, также удалить соответствующие папки и файлы вручную, но я всегда использую GIT для моих проектов.
Также см. Использование внешних систем контроля версий с Unity для получения общей информации о настройке версии. контроль вашего проекта (ов).
Поэтому, когда я хочу очистить репозиторий перед копированием, я обычно использую следующее как *.gitignore (есть также более полные версии, например, из Github).
Все, что здесь перечислено, можно в основном удалить и будет повторно скомпилировано при следующем открытии проекта в Unity. (Строки, начинающиеся с ! , являются исключениями, которые я добавил, потому что, как я сказал, имело смысл сохранить и их.)
Если это еще не сделано, сначала инициализируйте запущенный репозиторий.
Это покажет некоторые предупреждения для каждого файла, который указан в .gitignore , но вы можете игнорировать эти предупреждения. Он только говорит что-то подобное, например
Вы пытаетесь добавить игнорируемый файл в коммит, и он будет пропущен
Сделать вашу первую фиксацию.
Теперь, наконец, ты можешь бежать
Который удаляет все файлы, которые не отслеживаются (поэтому убедитесь, что у вас всегда есть все файлы, которые вы хотите сохранить, по крайней мере, в стадии постановки ( git add ) или, что лучше, в первую очередь) или будет проигнорирован *.gitignore .
-f
Если для переменной конфигурации Git clean.requireForce не задано значение false, git clean откажется удалять файлы или каталоги, если не указано -f, -n или -i. Git откажется удалять каталоги с подкаталогом или файлом .git, если не указан второй -f.-d
Удалите неотслеживаемые каталоги в дополнение к неотслеживаемым файлам. Если неотслеживаемый каталог управляется другим репозиторием Git, он не удаляется по умолчанию. Дважды используйте параметр -f, если вы действительно хотите удалить такой каталог.-x
Не используйте стандартные правила игнорирования, считываемые из .gitignore (для каждого каталога) и $ GIT_DIR / info / exclude, но все же используйте правила игнорирования, указанные с параметрами -e. Это позволяет удалить все неотслеживаемые файлы, включая продукты сборки. Это можно использовать (возможно, вместе с git reset) для создания нетронутого рабочего каталога для тестирования чистой сборки.
Миграция с помощью UnityPackage
Другой вариант перемещения сцен или частей активов между двумя разными проектами - использование UnityPackage
Выдержки из Документов:
Откройте проект, из которого хотите экспортировать активы.
Выберите Assets & rightarrow; Export Package из меню, чтобы открыть диалоговое окно Exporting Package. (См. Изображение диалогового окна «Экспорт пакета». ниже.)
В диалоговом окне выберите активы, которые вы хотите включить в пакет, щелкнув соответствующие флажки.
Не снимайте флажок "Включить зависимости", чтобы автоматически выбирать любые ресурсы, используемые выбранными вами.
Нажмите «Экспорт», чтобы открыть файловый менеджер и выбрать, где вы хотите сохранить файл пакета.
Назовите и сохраните пакет где угодно
В качестве альтернативы шагу 2 вы также можете щелкнуть правой кнопкой мыши по папке Assets в Project View и найти параметр Export Package в контекстное меню.
а затем в новом проекте импортируйте UnityPackage
- Откройте проект, в который вы хотите импортировать активы.
- Выберите Assets & rightarrow; Импорт пакета & rightarrow; Пользовательский пакет .
- В проводнике файлов выберите нужный пакет, и появится диалоговое окно «Импорт пакета Unity» со всеми предварительно отмеченными элементами пакета, готовыми к установке. (См. Изображение диалогового окна «Импорт пакета Unity» ниже.)
- Выберите Import , и Unity поместит содержимое пакета в папку Assets , к которой вы можете получить доступ из вашего Project view .
В качестве альтернативы шагам 2 и 4 вы даже можете просто перетащить файл unitypackage в папку Assets через редактор Unity.
Я хотел сделать что-то подобное, что применимо к части «Перенести на другой компьютер».
Я заметил, что мои проекты занимают довольно много места, и захотел очистить проект. Я выполнил derHugo, ответив на рекомендованную процедуру «git clean», но используя его связанный файл GitHub .gitignore.
Мне пришлось внести некоторые изменения, чтобы он работал на Unity 19.4:
По сути, я не игнорировал всю папку библиотеки и игнорировал только следующее:
Вот процедура, которой вы должны следовать:
- Сделайте резервную копию вашего проекта на всякий случай.
- Поместите прилагаемый файл .gitignore в корневой каталог вашего проекта.
- Выполните следующие команды:
* Обратите внимание, что опция -X написана с заглавной буквы в команде git clean, это позволяет избежать удаления любой пустой и неотслеживаемой папки.
Чтобы убедиться, что все в порядке:
- Загрузите свой проект Unity и убедитесь, что все по-прежнему работает правильно.
- Еще раз запустите git clean -Xfd, чтобы очистить файлы проекта, перестроенные Unity.
Еще раз благодарим derHugo за его подробный ответ, который использовался для работы с последними выпусками Unity.
Класс, который я использую выглядит так:
Пробовал десериализовать строку разными способами:
-
Использовал Boomlagoon.JSON , но он почему-то извлекает только первый объект.
Пытался использовать MiniJSON : обрезал [] у присланной строки и затем применял библиотеку:
Но оно возвращает первое значение `KeyValuePair:
А если оставить скобки, то вообще ничего не может распарсить json и поэтому логично в foreach кидает ошибку:
NullReferenceException: Object reference not set to an instance of an object
Как мне корректно сериализовать и десериализовать данные из json? Что использовать и как?
2 ответа 2
Начиная с версии 5.3.3 Unity добавила JsonUtility в своё API. Можете забыть о различных сторонних библиотеках, разве что вы не делаете что-то очень сложное.
JsonUtility быстрее, чем эти сторонние библиотеки, но и, меж тем, поддерживаются только просты типы. Оно не поддерживает коллекции, такие как словарь (Dictionary). Исключением является список (List). Оно поддерживает списки (List) и массив списков (List array).
Если вам нужно сериализовать словарь (Dictionary) или сделать что-то, чем простая сериализация и десеризация простых типов — используйте сторонние библиотеки. В ином же случае обновитесь до версии >= 5.3.3 и попробуйте решения, которые приведены ниже.
Пример класса для сериализации:
1. ОДИН ОБЪЕКТ (НЕ JSON МАССИВ)
Сериализация 1:
Сериализация 2:
Сериализация в json с помощью перегруженного метода public static string ToJson(object obj, bool prettyPrint);. Добавив флаг true вторым аргументом в функцию JsonUtility.ToJson отформатирует данные. Сравните вывод ниже с выводом, что был выше.
Десериализация 1:
Десериализация 2:
Десериализация json c помощью перегруженного метода public static object FromJson(string json, Type type);.
Десериализация 3:
Когда используется метод JsonUtility.FromJsonOverwrite , то новый экземпляр объекта не будет создан, а будет переиспользован объект, который вы передадите в качестве параметра в функцию, перезаписав его значения.
2. МНОЖЕСТВО ДАННЫХ (массив json): ваш json содержит несколько объектов с данными.
JsonUtility пока в нативном виде не поддерживает массивы, но можно воспользоваться небольшим классом-хэлпером, чтобы получить массив, который будет работать с JsonUtility.
Создаем класс JsonHelper . Можете скопировать код хэлпера, представленного ниже:
Сериализация json массива:
Десериализация json массива:
Примечание: если данный json массив приходит с сервера и вы не создаете его самолично вручную, то тогда надо добавить — в конец данной строки.
Вот простая функция, чтобы сделать это:
Затем использовать её:
3. ИСПРАВЛЕНИЕ ПРОБЛЕМ JsonUtility:
Возникают проблемы при сериализации при использовании JsonUtility.ToJson? Получаете пустую строку или " <> "?
- Убедитесь, что класс не является массивом. Если это не так, то используйте класс-хэлпер выше и его метод JsonHelper.ToJson , вместо JsonUtility.ToJson .
- Не забудьте добавить атрибут [Serializable] сверху над объявлением класса, который вы хотите сериализовать.
- Удалите свойства из класса. Например, в переменной public string playerId < get; set; >удалите< get; set; >. Unity не сможет сериализовать их.
Проблемы при десериализации при использовании JsonUtility.FromJson ?
- Если вы получаете Null , то убедитесь, что json не является json массивом. Если это так, то используйте класс-хэлпер выше и его метод JsonHelper.FromJson вместо JsonUtility.FromJson .
- Если вы получаете NullReferenceException в процессе десериализации, то добавьте атрибут [Serializable] сверху над объявлением класса.
Отвечая, на ваш вопрос:
Ваши первоначальные данные:
Код выглядит так:
Теперь у вас есть строка:
Чтобы сериализовать теперь это множество данных из php как массивы, вы можете сделать так:
- playerInstance[0] — данные из первого объекта
- playerInstance[1] — данные из второго объекта
- playerInstance[2] — данные из третьего объекта
Примечание: Удалите < get; set; >из класса player . Если у вас имеются < get; set; >, то работать не будет. JsonUtility НЕ работает с членами класса, которые объявлены как свойства.
Если они нужны, тогда придется использовать либо другие библиотеки, либо XmlSerializer/BinaryFormatter
Столкнулся с довольно-таки тривиальной проблемой. Сериализовать и десерилизовать данные.
Задача
Есть приложение, клиент-сервер. Клиент — Unity3d сервер PhotonServer. Есть модель, которая и на клиенте и на сервере должна быть эквивалентной. Требуется синхронизировать состояние модели и, возможно, дополнительные классы.
Решение
Protobuf
Самое логичное решение — это использовать бинарный протокол. В этом явный фаворит — ptotobuf (использовал proto-net 668). Он не поддерживает веб-сборку, но это допустимая жертва. Разметил требуемые классы. Проверяю. Все работает, небольшой размер и быстрый в работе. Шикарно. Но!
В один прекрасный момент Protobuf выплюнул екзепшен, мол, такой класс не найден. Как это?
Баг подробно с примером кода.
Начал различными способами решать эту проблему. Есть вариант скармливать Protobuf типы. Что уже не хорошо. Можно допустить достаточно много ошибок или забыть указать тот или иной тип. Более того, Protobuf не поддерживает многомерные массивы.
Как ни прискорбно, но Protobuf придется в сторону. К слову, однажды пытался использовать Protobuf в связке php и Unity. Со стороны php реализация Protobuf оказалась достаточно баганутой. В итоге в php и Unity использовал json. Это сработало, потому что между php и Unity ходили довольно-таки простые структуры данных.
Message pack
Сещуствует еще один примечательный сериализатор. Есть реализации на огромное количество языков. Замечательно. Решил попробовать. Примитивный тип сериализовал нормально. Размер 18 байт против моего 41 байта, против 19 байтов protobuf и против 44 байтов json. Отличный результат. В чем же хитрость? На официальном сайте есть пример, как он на самом деле все пакует. Вот ссылка.
Но сложный пример, который будет, далее не осилил message pack и protobuf.
Итог: Json хорош для не сложных структур. Но когда есть поля типа абстрактный класс или интерфейс, с ним возникают проблемы.
В любой вариации xml — достаточно громоздкий. Поэтому решил не рассматривать. Хоть и часть проекта на xml. Например, система локализации.
BinaryFormatter
Решил обратится к стандартным средствам. Разметил код, сериализуем. Success! Большой объем файла, правда, не есть хорошо. Не беда, пройдемся еще и компрессией. Использовал LZMA. Выиграл немного в размере, но проиграл по скорости работы. Допустимая жертва. Теперь сборки. Барабанная дробь. Веб не поддерживается, беда…
Теперь устроим обмен между клиентом и сервером. И… Очередной FAIL. Дело в том, что сборки у классов разные, хоть классы одни и те же. В юнити своя сборка на фотоне своя. Можно решить через костыльный способ. Забиндить сборки и вручную их переименовать, но сборка попадает в бинарный файл. Зачем она там нужна?
Решил что к этому способу вернусь, просмотрел еще парочку сериализаторов. Один из них Шарп сериализатор. Смог сериализовать поля типа интерфейс, но тоже прописывает сборку и не поддерживается в вебе. Тогда я решил сначала сформировать требования к сериализатору.
Требования к сериализатору
- Сериализовать в бинарный формат;
- Сериализовать в небольшой размер;
- Сериализовать классы используя конкретные сборки (при этом информация о сборке необязательно нужна в файле);
- Сериализовать пользовательские классы;
- Поддерживать массивы;
- Работать в веб версией юнити;
- Работать с фотоном.
Собственный сериализатор
Начал гуглить. Но безрезультатно. Что же делать?
Использовать стандартное решение — не очень хороший вариант. Большой размер. Проблемы с платформами.
Тогда я решил, под выше написанные требования написать собственный сериализатор. Почему бы и нет. Это оказалось гораздо эффективней, нежели в пустую разбирать и тестировать тот или иной сериализатор.
С чего лучше начать?
Как сохранить объекты и как их загрузить. В этом плане мне понравился подход protobuf-net 668. А именно — маркировать требуемые поля и свойства. Также маркировать и методы, которые будут вызваны до сериализации и после десериализации.
Карта
Для начала нужно сохранить карту. А именно — ключ и тип. Чтобы по этой карте можно было потом восстановить объект. Для стандартных типов значение < 0 и для пользовательских соответственно >0. Размер ключа int16.
Коллекции вынес в отдельные теги что бы логически разделить.
TData множество объектов
Теперь нужно достать все поля и свойства. Запаковать их в свою структуру, чтобы присвоить им теги.
Поскольку могут быть массивы, в контейнер добавляю информацию о мере массива.
Я специально не делил на контейнер типа коллекция и контейнер типа объект. Все данные представлены в виде множества. Что и есть массив, если объект значит ранг и размерность в нулях.
Теперь нужен контейнер, в который будет восстановлен сам объект.
Далее. Со структурами данных завершено. Теперь нужно их заполнить.
Сначала карту типов и карту объекта. Для этого нужен класс, который все это объединит. Создаю дополнительно абстрактные классы для записи и считывания. Это на случай, если, например, понадобится добавить еще один формат. Тот же json или xml.
Возможно, потом у них появятся дополнительные методы писать в поток и читать из потока. Но пока это не нужно. Теперь все это склеим в один класс.
Готово. Теперь перейдем к тестам. Осторожно много наборов данных.
По объему файла, конечно, проигрывает Protobuf и messagepack. Ведь я сохраняю карту типов и не использую хитрые махинации с смещением битов или конвертации строк «byte[] bytes = Encoding.UTF7.GetBytes((string)data.value)». Это дополнительная нагрузка, возможно, потом расширю в виде вариативности. Протестировал обмен данными между фотоном и юнити. Работает как и ожидалось. Ведь я создаю тип относительно сборки, которая является параметром в методе Deserialize.
Готовые решения, которых так много в интернете, не подошли по требованиям. Поэтому пришлось изобрести велосипед. Который оправдал затраченное на него время. Его можно расширять улучшать.
Заключение
Если вы используете примитивные типы, то вам подойдет любой из рассмотренных сериализаторов. Для примитивов я все же предпочел бы Protobuf. Но для сложных типов данных готовые решения подойдут не всегда.
По традиции, для начала определимся, что это и зачем нам это надо. Итак, что же такое эти внешние ресурсы. В рамках разработки игр, такими ресурсами может быть все, что требуется для функционирования приложения и не должно храниться в конечном билде проекта. Внешние ресурсы могут находится как на жестком диска компьютера пользователя, так и на внешнем веб-сервере. В общем случае такие ресурсы — это любой файл или набор данных, который мы загружаем в наше, уже запущенное приложение. Если говорить в рамках Unity 3d, то ими могут быть:
- Текстовый файл
- Файл текстуры
- Аудио файл
- Байт-массив
- AssetBundle (архив с ассетами проекта Unity 3d)
Возможности Unity 3d
Аналогичным образом можно получать не только текстовые данные, но и другие:
Работа с UWR в целом схожа с WWW в своей основе, однако есть и отличия, речь о которых пойдет дальше. Ниже приведен аналогичный пример загрузки текста.
Основные изменения, которые привнесла новая система UWR (помимо изменений принципа работы внутри) — это возможность назначать самому обработчиков для загрузки и скачивания данных с сервера, подробнее можно почитать здесь. По умолчанию это классы UploadHandler и DownloadHandler. Сам Unity предоставляет набор расширений этих классов для работы с различными данными, такими как аудио, текстуры, ассеты и т.п. Рассмотрим подробнее работу с ними.
Работа с ресурсами
Текст
Как видно из кода, здесь используется DownloadHandler по умолчанию. Свойство text это геттер, который преобразует byte массив в текст в кодировке UTF8. Основное применение загрузки текста с сервера — это получение json-файла (сериализованное представление данных в текстовом виде). Получить такие данные можно с использованием класса Unity JsonUtility.
Аудио
Для работы с аудио необходимо использовать специальный метод создания запроса UnityWebRequestMultimedia.GetAudioClip, а также для получения представления данных в нужном для работы в Unity виде, необходимо использовать DownloadHandlerAudioClip. Помимо этого, при создании запроса необходимо указать тип аудиоданных, представленный перечислением AudioType, который задает формат (wav, aiff, oggvorbis и т.д.).
Текстура
Загрузка текстур схожа с таковой для аудио файлов. Запрос создается с помощью UnityWebRequestTexture.GetTexture. Для получения данных в нужном для Unity виде используется DownloadHandlerTexture.
AssetBundle
Основные проблемы и решения при работе с веб-сервером и внешними данными
Выше были описаны простые способы взаимодействия приложения с сервером по части загрузки различных ресурсов. Однако на практике все обстоит гораздо сложнее. Рассмотрим основные проблемы, которые сопровождают разработчиков и остановимся на путях их решения.
Не хватает свободного места
Одной из первых проблем при загрузке данных с сервера является возможная нехватка свободного места на устройстве. Часто бывает, что пользователь использует для игр (особенно на Android) старые устройства, а также и сам размер скачиваемых файлов может быть достаточно большим (привет PC). В любом случае, эту ситуацию необходимо корректно обработать и заранее сообщить игроку, что места не хватает и сколько. Как это сделать? Первым дело необходимо узнать размер скачиваемого файла, это делается по средствам запроса UnityWebRequest.Head(). Ниже представлен код для получения размера.
Здесь важно отметить одну вещь, для правильной работы запроса, сервер должен уметь возвращать размер контента, в противном случае (как, собственно, и для отображения прогресса) будет возвращаться неверное значение.
После того, как мы получили размер скачиваемых данных, мы можем сравнить его с размером свободного места на диске. Для получения последнего, я использую бесплатный плагин из Asset Store.
Примечание: можно воcпользоваться классом Cache в Unity3d, он может показывать свободное и занятое место в кэше. Однако здесь стоит учесть момент, что эти данные являются относительными. Они рассчитываются исходя из размера самого кэша, по умолчанию он равен 4GB. Если у пользователя свободного места больше, чем размер кэша, то проблем никаких не будет, однако если это не так, то значения могут принимать неверные относительно реального положения дел значения.
Проверка доступа в интернет
Кэширование
Следующей, и одной из самых важных проблем, является кэширование скачиваемых файлов. Для чего же нужно это кэширование:
- Экономия траффика (не скачивать уже скаченные данные)
- Обеспечение работы в отсутствии интернета (можно показать данные из кэша).
Аналогично, получение данных из кэша.
Примечание: почему для загрузки текстур не используется тот же самый UWR с url вида file://. На данный момент наблюдается проблемы с этим, файл просто напросто не загружается, поэтому пришлось найти обходной путь.
Примечание: я не использую прямую загрузку AudioClip в проектах, все такие данные я храню в AssetBundle. Однако если необходимо, то это легко сделать используя функции класса AudioClip GetData и SetData.
В отличие от простых ресурсов для AssetBundle в Unity присутствует встроенный механизм кэширования. Рассмотрим его подробнее.
В своей основе этот механизм может использовать два подхода:
- Использование CRC и номера версии
- Использование Hash значения
Итак, каким образом осуществляется кэширование:
- Запрашиваем с сервера manifest файл бандла (данный файл создается автоматически при его создании и содержит описание ассетов, которые в нем содержаться, а также значения hash, crc, размера и т.п.). Файл имеет тоже самое имя, что и бандл плюс расширение .manifest.
- Получаем из manifest’a значение hash128
- Создаем запрос к серверу для получения AssetBundle, где помимо url, указываем полученное значение hash128
В приведенном примере, Unity при запросе на сервер, сначала смотрит, есть ли в кэше файл с указанным hash128 значением, если есть, то будет возвращен он, если нет, то будет загружен обновленный файл. Для управления всеми файлами кэша в Unity присутствует класс Caching, с помощью которого мы можем узнать, есть ли файл в кэше, получить все кэшированные версии, а также удалить ненужные, либо полностью его очистить.
Примечание: почему такой странный способ получения hash значения? Это связано с тем, что получение hash128 способом, описанным в документации, требует загрузки всего бандла целиком, а затем получения из него AssetBundleManifest ассета и оттуда уже hash значения. Минус такого подхода в том, что качается весь AssetBundle, а нам как раз нужно, чтобы этого не было. Поэтому мы сначала скачиваем с сервера только файл манифеста, забираем из него hash128 и только потом, если надо скачаем файл бандла, при этом выдергивать значение hash128 придется через интерпретацию строк.
Работа с ресурсами в режиме редактора
Последней проблемой, а точнее вопросом удобства отладки и разработки является работа с загружаемыми ресурсами в режиме редактора, если с обычными файлами проблем нет, то с бандлами не все так просто. Можно, конечно, каждый раз делать их билд, заливать на сервер и запускать приложение в редакторе Unity и смотреть как всё работает, но это даже по описанию звучит как “костыль”. С этим надо что-то делать и для этого нам поможет класс AssetDatabase.
Для того, чтобы унифицировать работу с бандлами я сделал специальную обертку:
Теперь нам необходимо добавить два режима работы с ассетами в зависимости от того в редакторе мы или же в билде. Для билда мы используем обертки над функциями класса AssetBundle, а для редактора используем упомянутый выше класс AssetDatabase.
Примечание: в коде используется класс TaskManager, о нем пойдет речь ниже, если кратко, то это обертка для работы с Coroutine.
Помимо описанного выше, также в ходе разработки полезно смотреть, что именно мы загрузили и что находится сейчас в кэше. С этой целью можно воспользоваться возможностью установки своей папки, которая будет использоваться для кэширования (в эту же папки можно записывать и скачанные текстовые и другие файлы):
Пишем менеджер сетевых запросов или работа с веб-сервером
Выше мы рассмотрели основные аспекты работы с внешними ресурсами в Unity, теперь бы мне хотелось остановиться на реализации API, которая обобщает и унифицирует все выше сказанное. И для начала остановимся на менеджере сетевых запросов.
Примечание: здесь и далее используется обертка над Coroutine в виде класса TaskManager. Об этой обертке я писал в другой статье.
Заведем соответствующий класс:
Статическое поле NetworkType требуется для того, чтобы приложение могло получать сведения о типе интернет-соединения. В принципе это значение можно хранить, где угодно, я решил, что в классе Network ей самое место.
Как видно из кода, способ обработки завершения запроса изменен, по сравнению с кодом в предыдущих разделах. Это сделано с целью отображения прогресса загрузки данных. Также, все посылаемые запросы сохраняются в списке, с тем чтобы, если это необходимо, их можно было отменить.
Аналогичным образом создаются функции для текстуры, аудио, текста, байт-массива.
Теперь необходимо обеспечить отправку данных сервер через команду Post. Часто нужно, что-то передать серверу, и в зависимости от того, что именно, получить ответ. Добавим соответствующие функции.
Теперь добавим публичные методы с помощью, которых мы будем осуществлять загрузку данных, в частности AssetBundle
Аналогично добавляются методы для текстуры, аудио-файла, текста и т.д.
И напоследок добавляем функцию получения размера скачиваемого файла и функцию очистки, для остановки всех созданных запросов.
На этом наш менеджер для работы с сетевыми запроса завершен. По необходимости, каждая подсистема игры, которая требует работы с сервером может создавать свои экземпляры класса.
Пишем менеджер загрузки внешних ресурсов
Помимо описанного выше класса, для полноценной работы с внешними данными, нам нужен отдельный менеджер, который будет не просто скачивать данные, но и уведомлять приложение о начале загрузке, завершении, прогрессе, отсутствии свободного места, а также заниматься вопросами кэширования.
Как видно, в конструкторе задается папка для кэширования в зависимости от того в редакторе мы находимся или нет. Также, мы завели приватное поле для экземпляра класса Network, который мы описали ранее.
Теперь добавим вспомогательные функции для работы с кэшем, а также определения размера скачиваемого файла и проверки свободного места для него. Далее и ниже код приводится на примере работы с AssetBundle, для остальных ресурсов все делается по аналогии.
Итак, что происходит в данной функции:
Аналогично описанному выше методу в менеджере можно/нужно завести и другие функции работы с данными: GetJson, GetTexture, GetText, GetAudio и т.д.
И напоследок необходимо завести метод, который позволит скачивает наборы ресурсов. Данный метод будет полезен, если нам надо на старте приложения, что-то скачать или обновить.
Здесь стоить понимать особенность работы TaskManager, который используется в менеджере сетевых запросов, по умолчанию он работает, выполняя все задачи по очереди. Поэтому загрузка файлов будет происходить соответственно.
Примечание: для тех, кто не любит Coroutine, все можно достаточно легко перевести на async/await, но в данном случае, в статье я решил использовать более понятный для новичков вариант (как мне кажется).
Заключение
В данной статье я постарался как можно более компактно описать работу с внешними ресурсами игровых приложений. Этот подход и код используется в проектах, которые были выпущены и разрабатываются при моем участии. Он достаточно прост и применим в несложных играх, где нет постоянного общения с сервером (ММО и другие сложные f2p игры), однако он сильно облегчает работу, в случае если нам надо скачать дополнительные материалы, языки, осуществить серверную валидацию покупок и другие данные, которые единовременно или не слишком часто используются в приложении.
Читайте также: