Unity чтение из файла

Вот моя ситуация: у меня есть игра, игровое поле которой представляет прямоугольник n*m, и мне очень удобно информацию о каждой клетке поля сохранять в файле txt (точнее 1 файл на 1 уровень). Пишу я на Unity, в папку Assets я закинул папку с уровнями. В редакторе всё работает хорошо. Неприятный тот момент, что когда я компилирую игру и запускаю её просто (как .exe файл), уровни програма не находит. Вопрос состоит в том, как исправить эту проблему, или, если такового решения нет, посоветуйте альтернативные решения. Зарание спасибо!

1 ответ 1

Скорее всего проблема в том, что вы положили свой текстовый файл не в папку Resources.

Все ресурсы вашей игры, которые вы хотите загружать динамически должны находиться в папке Resources, иначе загрузка будет работать только в редакторе Unity.

При этом стоит заметить, что в вашем проекте может быть несколько папок с именем Resources (в различных директориях проекта). Это разрешается тем, что при компиляции проекта Unity собирает все содержимое всех папок Resources в одну "виртуальную" папку.

Symptoms

  • I want to read and write data from a text file that can be in a specific directory or among my other Assets.

Resolution

If you don’t want to read the file from a directory, you can assign the Asset directly from the Editor using an exposed property of type TextAsset (as you can see in Figure 1) and get the text of the file the using the TextAsset.text property.

Figure 1. Text file assigned to a TextAsset

Another way to read and write data from a text file that’s in a specific directory is to use the StreamWriter and StreamReader classes from the System.IO namespace to access characters from a byte stream and then load the file as a TextAsset in Unity.

The following example Editor script prints the data from a file (if it exists) or writes text to a file in the specified path.

More Information

For more information, consult the following documentation:

Введение

Здравствуйте уважаемые читатели, сегодня речь пойдет о работе с внешними ресурсами в среде Unity 3d.

По традиции, для начала определимся, что это и зачем нам это надо. Итак, что же такое эти внешние ресурсы. В рамках разработки игр, такими ресурсами может быть все, что требуется для функционирования приложения и не должно храниться в конечном билде проекта. Внешние ресурсы могут находится как на жестком диска компьютера пользователя, так и на внешнем веб-сервере. В общем случае такие ресурсы — это любой файл или набор данных, который мы загружаем в наше, уже запущенное приложение. Если говорить в рамках Unity 3d, то ими могут быть:

  • Текстовый файл
  • Файл текстуры
  • Аудио файл
  • Байт-массив
  • AssetBundle (архив с ассетами проекта Unity 3d)

Ниже, мы рассмотрим подробнее встроенные механизмы работы с этими ресурсами, которые присутствуют в Unity 3d, а также напишем простые менеджеры для взаимодействия с веб-сервером и загрузки ресурсов в приложение.

Примечание: далее в статье используется код с использованием C# 7+ и рассчитан на компилятор Roslyn используемый в Unity3d в версиях 2018.3+.

Возможности Unity 3d

До версии Unity 2017 года для работы с серверными данными и внешними ресурсами использовался один механизм (исключая самописные), который был включен в движок – это класс WWW. Данный класс позволял использовать различные http команды (get, post, put и т.п.) в синхронном или асинхронном виде (через Coroutine). Работа с данным классом была достаточно проста и незамысловата.

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

  • Байт-массив — www.bytes
  • Текстура – www.texture
  • Аудио – www.GetAudioClip()
  • Ассет – www.assetBundle

Однако начиная с версии 2017 в Unity появилась новая система работы с сервером, представленная классом UnityWebRequest, который находится в пространстве имен Networking. До Unity 2018 она существовала вместе с WWW, но в последней версии движка WWW стал нерекомендуемым, а в дальнейшем будет полностью удален. Поэтому далее речь пойдет только о UnityWebRequest (в дальнейшем UWR).

Работа с UWR в целом схожа с WWW в своей основе, однако есть и отличия, речь о которых пойдет дальше. Ниже приведен аналогичный пример загрузки текста.

Основные изменения, которые привнесла новая система UWR (помимо изменений принципа работы внутри) — это возможность назначать самому обработчиков для загрузки и скачивания данных с сервера, подробнее можно почитать здесь. По умолчанию это классы UploadHandler и DownloadHandler. Сам Unity предоставляет набор расширений этих классов для работы с различными данными, такими как аудио, текстуры, ассеты и т.п. Рассмотрим подробнее работу с ними.

Работа с ресурсами

Текст

Работа с текстом является одним из самых простых вариантов. Выше уже был описан способ его загрузки. Перепишем его немного с использование создания прямого http запроса Get.

Как видно из кода, здесь используется DownloadHandler по умолчанию. Свойство text это геттер, который преобразует byte массив в текст в кодировке UTF8. Основное применение загрузки текста с сервера — это получение json-файла (сериализованное представление данных в текстовом виде). Получить такие данные можно с использованием класса Unity JsonUtility.

Аудио

Для работы с аудио необходимо использовать специальный метод создания запроса UnityWebRequestMultimedia.GetAudioClip, а также для получения представления данных в нужном для работы в Unity виде, необходимо использовать DownloadHandlerAudioClip. Помимо этого, при создании запроса необходимо указать тип аудиоданных, представленный перечислением AudioType, который задает формат (wav, aiff, oggvorbis и т.д.).

Текстура

Загрузка текстур схожа с таковой для аудио файлов. Запрос создается с помощью UnityWebRequestTexture.GetTexture. Для получения данных в нужном для Unity виде используется DownloadHandlerTexture.

AssetBundle

Как было сказано ранее бандл – это, по сути, архив с ресурсами Unity, которые можно использовать в уже работающей игре. Этими ресурсами могут быть любые ассеты проекта, включая сцены. Исключение составляют C# скрипты, их нельзя передать. Для загрузки AssetBundle используется запрос, который создается с помощью UnityWebRequestAssetBundle.GetAssetBundle. Для получения данных в нужном для Unity виде используется DownloadHandlerAssetBundle.

Основные проблемы и решения при работе с веб-сервером и внешними данными

Выше были описаны простые способы взаимодействия приложения с сервером по части загрузки различных ресурсов. Однако на практике все обстоит гораздо сложнее. Рассмотрим основные проблемы, которые сопровождают разработчиков и остановимся на путях их решения.

Не хватает свободного места

Одной из первых проблем при загрузке данных с сервера является возможная нехватка свободного места на устройстве. Часто бывает, что пользователь использует для игр (особенно на Android) старые устройства, а также и сам размер скачиваемых файлов может быть достаточно большим (привет PC). В любом случае, эту ситуацию необходимо корректно обработать и заранее сообщить игроку, что места не хватает и сколько. Как это сделать? Первым дело необходимо узнать размер скачиваемого файла, это делается по средствам запроса UnityWebRequest.Head(). Ниже представлен код для получения размера.

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

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

Примечание: можно воcпользоваться классом Cache в Unity3d, он может показывать свободное и занятое место в кэше. Однако здесь стоит учесть момент, что эти данные являются относительными. Они рассчитываются исходя из размера самого кэша, по умолчанию он равен 4GB. Если у пользователя свободного места больше, чем размер кэша, то проблем никаких не будет, однако если это не так, то значения могут принимать неверные относительно реального положения дел значения.

Проверка доступа в интернет

Очень часто, перед тем, как что-либо скачивать с сервера необходимо обработать ситуацию отсутствия доступа в интернет. Существует несколько способов это сделать: от пингования адреса, до GET запроса к google.ru. Однако, на мой взгляд, наиболее правильный и дающий быстрый и стабильный результат — это скачивание со своего же сервера (того же, откуда будут качаться файлы) небольшого файла. Как это сделать, описано выше в разделе работы с текстом.
Помимо проверки самого факта наличия доступа в интернет, необходимо также определить его тип (mobile или WiFi), ведь вряд ли игроку захочется качать несколько сот мегабайт на мобильном траффике. Это можно сделать через свойство Application.internetReachability.

Кэширование

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

  1. Экономия траффика (не скачивать уже скаченные данные)
  2. Обеспечение работы в отсутствии интернета (можно показать данные из кэша).

Что же нужно кэшировать? Ответ на этот вопрос – всё, все файлы, что вы качаете надо кэшировать. Как это делать, рассмотрим ниже, и начнем с простых текстовых файлов.
К сожалению, в Unity нет встроенного механизма кэширования текста, а также текстур и аудио файлов. Поэтому для этих ресурсов необходимо писать свою систему, либо не писать, в зависимости от потребностей проекта. В самом простом варианте, мы просто пишем файл в кэш и в случае отсутствия интернета берем файл из него. В чуть более сложном варианте (именно его я использую в проектах) мы отправляем запрос на сервер, который возвращает json с указанием версий файлов, которые хранятся на сервере. Запись и чтение файлов из кэша можно осуществлять с помощью C# класса File или любым другим удобным и принятым в вашей команде способом.

Аналогично, получение данных из кэша.

Примечание: почему для загрузки текстур не используется тот же самый UWR с url вида file://. На данный момент наблюдается проблемы с этим, файл просто напросто не загружается, поэтому пришлось найти обходной путь.

Примечание: я не использую прямую загрузку AudioClip в проектах, все такие данные я храню в AssetBundle. Однако если необходимо, то это легко сделать используя функции класса AudioClip GetData и SetData.

В отличие от простых ресурсов для AssetBundle в Unity присутствует встроенный механизм кэширования. Рассмотрим его подробнее.

В своей основе этот механизм может использовать два подхода:

  1. Использование CRC и номера версии
  2. Использование Hash значения

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

Итак, каким образом осуществляется кэширование:

  1. Запрашиваем с сервера manifest файл бандла (данный файл создается автоматически при его создании и содержит описание ассетов, которые в нем содержаться, а также значения hash, crc, размера и т.п.). Файл имеет тоже самое имя, что и бандл плюс расширение .manifest.
  2. Получаем из manifest’a значение hash128
  3. Создаем запрос к серверу для получения 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. Часто нужно, что-то передать серверу, и в зависимости от того, что именно, получить ответ. Добавим соответствующие функции.

Аналогично добавляются методы для текстуры, аудио-файла, текста и т.д.

На этом наш менеджер для работы с сетевыми запроса завершен. По необходимости, каждая подсистема игры, которая требует работы с сервером может создавать свои экземпляры класса.

Пишем менеджер загрузки внешних ресурсов

Помимо описанного выше класса, для полноценной работы с внешними данными, нам нужен отдельный менеджер, который будет не просто скачивать данные, но и уведомлять приложение о начале загрузке, завершении, прогрессе, отсутствии свободного места, а также заниматься вопросами кэширования.

Как видно, в конструкторе задается папка для кэширования в зависимости от того в редакторе мы находимся или нет. Также, мы завели приватное поле для экземпляра класса Network, который мы описали ранее.

Теперь добавим вспомогательные функции для работы с кэшем, а также определения размера скачиваемого файла и проверки свободного места для него. Далее и ниже код приводится на примере работы с AssetBundle, для остальных ресурсов все делается по аналогии.

Итак, что происходит в данной функции:

  • Директива предкомпиляции DONT_USE_SERVER_IN_EDITOR используется для отключения реальной загрузки бандлов с сервера
  • Первым делом выполняется запрос на сервер для получения файла манифеста для бандла
  • Затем мы получаем хеш-значение и проверяем его валидность, в случае неудачи смотрим, есть ли хеш-значение в БД (_externalResourcesStorage) для бандла, если есть, то берем его и выполняем запрос на загрузку бандла без проверки на свободное место (в данном случае, бандл будет взят из кэша), если нет, то возвращаем null значение
  • Если предыдущий пункт не актуален, то проверяем через класс Caching находится ли в кэше файл бандла, который мы хотим скачать и если да, то выполняем запрос без проверки на свободное место (файл же уже скачан)
  • В случае, если файла нет в кэше, мы проверяем наличие свободного места и, если его хватает, отправляем запрос на получение уже непосредственно самого бандла с указанием полученного ранее хеш-значения и сохраняем это значение в БД (только после реальной загрузки). Если места нет, то мы очищаем список всех запросов и отправляем сообщение в систему любым способом (об этом можно почитать в соответствующей статье)

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

Аналогично описанному выше методу в менеджере можно/нужно завести и другие функции работы с данными: GetJson, GetTexture, GetText, GetAudio и т.д.

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

Примечание: для тех, кто не любит Coroutine, все можно достаточно легко перевести на async/await, но в данном случае, в статье я решил использовать более понятный для новичков вариант (как мне кажется).

Заключение

В данной статье я постарался как можно более компактно описать работу с внешними ресурсами игровых приложений. Этот подход и код используется в проектах, которые были выпущены и разрабатываются при моем участии. Он достаточно прост и применим в несложных играх, где нет постоянного общения с сервером (ММО и другие сложные f2p игры), однако он сильно облегчает работу, в случае если нам надо скачать дополнительные материалы, языки, осуществить серверную валидацию покупок и другие данные, которые единовременно или не слишком часто используются в приложении.

Оцените статью
ПК Знаток
Добавить комментарий

Adblock
detector