Unity3d file что это

Работа с внешними ресурсами в Unity3D

Введение

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

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

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

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

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

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

Работа с 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.

Кэширование

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

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

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

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

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

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

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

В приведенном примере, 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, для остальных ресурсов все делается по аналогии.

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

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

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

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

Заключение

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

Источник

Unity3d. Начало работы, практические советы. Рецензия

Доброго времени суток. Данная статья посвящена мультиплатформенному инструменту Unity3d. В статье рассмотрены оптимальные, по моему мнению, способы работы с движком, даны практические советы по эксплуатации и дополнительные (общие) сведения; рассмотрены перспективы развития Unity3d.

Содержание:

Раздел 1. Введение

Unity3d является современным кросс-платформенным движком для создания игр и приложений, разработанный Unity Technologies. С помощью данного движка можно разрабатывать не только приложения для компьютеров, но и для мобильных устройств (например, на базе Android), игровых приставок и других девайсов.
Поговорим немного о характеристиках движка. Во-первых, стоит отметить то, что в среду разработки Unity интегрирован игровой движок, иными словами, вы можете протестировать свою игру не выходя из редактора. Во-вторых, Unity поддерживает импорт огромного количества различных форматов, что позволяет разработчику игры конструировать сами модели в более удобном приложении, а Unity использовать по прямому назначению — разработки продукта. В-третьих, написание сценариев (скриптов) осуществляется на наиболее популярных языках программирования — C# и JavaScript [1].
Таким образом, Unity3d является актуальной платформой, с помощью которой вы можете создавать свои собственные приложения и экспортировать их на различные устройства, будь то мобильный телефон или приставка Nintendo Wii.
Для того чтобы создать свою игру, вам, как минимум, нужно владеть одним из доступных (на Unity) языков программирования: C#, JavaScript или Boo.

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

Будет прекрасно, если вы владеете, например, 3Ds max’ом, это вам сильно поможет при создании игры. Что касается меня, то мне пришлось освоить 3Ds max на базовом уровне, ибо нигде не мог найти нужную мне модель дома [3]. В любом случае вам понадобится установленный 3Ds max на ваш компьютер, если вы собираетесь импортировать готовые 3D модели. Ибо, в большинстве своем, необходимые модели имеют формат проекта, т.е. необходимо будет зарендерить их в соответствующий для Unity3d формат, например, в *.3DS, и только после этого делать импорт в Unity, в противном случае последний выдаст ошибку.

Раздел 2. Начало работы

Вспомогательная литература

Обратимся к литературе, которая поможет нам изучить Unity3d, а именно, к Unity 3.x Game Development Essentials [2]. Книгу в свободном доступе можно найти в гугле, если вы предпочитаете бесплатный контент.
О чем книга? Пожалуй, обо всем, что только нужно для создания полноценной игры. Настоятельно рекомендую приобрести данную книгу и изучить самостоятельно.
Пару слов об игре и содержании книги. Ваш игрок оказывается на необитаемом острове, ему необходимо спастись, для этого он выполняет различные задания и, будем надеяться, спасается. Игру можно пройти за пару минут, однако создание игры занимает далеко не пару минут, даже не пару часов, пожалуй.
Книга содержит очень подробное руководство по созданию игры «с нуля». В ней описано многое, что вам, скорее всего, понадобится для создания вашей игры.

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

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

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

На это стоит обратить внимание

Приведу несколько советов при создании игры в Unity3d.

º Коллайдеры
Что это такое — коллайдер? Коллайдер — это область пространства, при взаимодействии с которой выполняются те или иные скрипты, действия. Чтобы лучше понять что это такое, представьте, что вы подходите к автоматически открывающимся дверям универмага. За пару метров от дверей, срабатывает датчик и двери распахиваются перед вами, но если вы пройдете за три метра, то ничего не произойдет. Как раз та зона, в которой действует датчик движения, отвечающий за открывание дверей, и есть коллайдер. Вы входите в зону — двери открываются, выходите из зоны — и датчик уже никак не будет реагировать на ваше присутствие. Точно так же и в игре.
Так вот, о коллайдерах. Предположим, мы импортировали в наш проект автомобиль и хотим сгенерировать для него коллайдер. Можно сгенерировать автоматически коллайдер для всего автомобиля, но тогда для каждой детали автомобиля — фары, уплотнителя, зеркала, покрышки — будет сгенерирован свой коллайдер.

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

Это абсолютно не оптимально. Безусловно, нам не нужно генерировать столь большое количество коллайдеров, достаточно ограничиться одним!

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

Обратите внимание, что это не тонкость. Стараться сэкономить память нужно везде. У вас есть какое-то место в игре, куда игрок не сможет попасть? Не генерируйте никакие коллайдеры для этого места вообще. У вас есть дверь, которую вы импортировали в проект из 3Ds max’а? Небось, она очень хорошо прорисована (сам сталкивался с тем, что у двери даже шурупы были прорисованы отдельным объектом), значит, создайте один box-коллайдер для всей двери — вы сэкономите много памяти! Поверьте, что вам эта сэкономленная память еще очень и очень пригодится.

º Оптимальная работа со сценами
Поговорим об оптимизации сцен, о самих же сценах будет речь вестись далее.
Представьте следующую ситуацию: у нас есть сцена «home» — когда игрок находится в здании и сцена «street» — когда игрок выходит из здания на улицу. Естественно, из второй сцены мы можем видеть дом, т.е. часть первой сцены, и наоборот (если, конечно, у здания есть окна). В таком случае оптимально следующее построение сцен. В первой сцене все, что касается улицы (т.е. того места, куда мы не можем попасть без перехода на другую сцену) надо максимально упростить. Иными словами, нам нужно оставить только внешний вид вида из окна, а всю начинку — распотрошить. Т.е. все коллайдеры удалить, разрешение и т.п. свести к минимуму. Действительно, зачем процессору напрягаться с обработкой той местности, которую мы не можем посетить? Аналогичным образом поступаем со второй сценой («street»). В данном случае мы можем вообще все удалить из дома и оставить только его «коробку».
Вы хотите создать minimap? Пожалуйста, только избавьтесь от всех коллайдеров и снизьте разрешение, в таком случае ваша игра будет оптимальна.
Именно таким образом работа со сценами становится оптимальной. Еще раз отмечу, что это не тонкости, это серьезные вещи, которые требуют внимания создателя.

Сцены

Готовая игра — это набор сцен, соединенных между собой (точно так же, как и жизнь — это набор дней). Об оптимальном проектировании игры мы сейчас и поговорим.
Прежде чем создать свой проект — подумайте, что он должен в себя включать (какие сцены). Составьте список сцен (на листочке), обдумайте, что каждая сцена будет в себе содержать. Теперь постарайтесь каждую сцену разбить на подсцены, чем больше их будет, тем легче будет вашему ЦП. Конечно, не стоит перебарщивать с количеством сцен. Предположим, что ваш игрок находится в здании и из него никогда не выходит, в здании есть, например, пару этажей, крыша, а на каждом этаже имеется три комнаты. В данном случае, по моему мнению, было бы оптимально «разбить» здание на две сцены — два этажа и крыша. Дробление же каждого этажа на сцены с комнатами — не оптимально. Это просто не нужно, если каждая из комнат слабо загружена. Иными словами, надо грамотно расходовать память процессора, но и забывать о получении удовольствия от игры тоже не стоит.

Отладка

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

Структурирование

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

Раздел 3. Практические советы

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

Написание скриптов

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

Видеоуроки

К счастью, программный продукт Unity обновляется постоянно, но, увы, видеоуроки сами это делать не могут. Получается так, что огромное количество видеороликов, актуальных год назад, теряют свою актуальность и становятся, попросту, бесполезными.
1 — здесь вы всегда найдете актуальные видеоуроки, понятное объяснение, детальный разбор скриптов и всех действий. Лучше этого канала пока что ничего не нашел. Если вы не имеете вообще никакого представления о создании игры, то вам однозначно нужно заглянуть на данный канал. 2 — серьезная команда, видеоуроков только, жаль, меньше, чем у 1. 3 — есть полезные вещи, жаль только, что видеоуроки стали почти неактуальными.

Устранение ошибок

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

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

Это ошибки, если такие имеются, то игра не запустится:

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

Раздел 4. Завершение работы

Выводы

Unity3d — очень гибкий движок, предоставляющий большую свободу действий пользователю. Чтобы упростить жизнь разработчику и потребителю, можно предпринять некоторые шаги для оптимизации своего проекта.
Теперь мы, с заложенной базой знаний, можем спокойно приступать к созданию своего ультра-популярного приложения!

Публикация игры

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

*Оптимизация

Если вы читаете данный подраздел, то либо вам просто интересно, что здесь написано, либо вы не прочитали NB! из раздела 2. Будем надеяться, что вы относитесь к первой категории. В данном подразделе еще раз хочется отметить то, что нужно сразу писать игру оптимальной, а не писать ее хоть как-то, и только затем отлаживать — это гиблый путь и поступать так не стоит. По поводу оптимизации — еще раз обратитесь ко второму разделу.

Раздел 5. Дополнительная информация

Теория графики

Практика без теории бывает не очень уж и интересной, поэтому советую ознакомиться с теорией компьютерной графики. www.intuit.ru/department/graphics/graphalg/lit.html
Протестировать свои теоретические знания можно тут www.intuit.ru/department/graphics/graphalg

Полезные ссылки
Мнение

Unity3d действительно мощный движок [4], с помощью которого можно создавать настоящие, работающие игры. Однако, как и у любого программного продукта, у него есть, по моему мнению, свои недостатки.

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

º Префабы
Хотелось бы видеть больше префабов, каких-то готовых вещей, объектов, например, домов, предметов элементарного интерьера — столов, стульев. Да, это все есть в магазине Unity[5], но, увы, многие вещи там платны, а бесплатные продукты лишь изредка достойны внимания. Отдельный разговор про 3rd person controller — извините, но это просто неприемлемо:

Unity3d file что это. image loader. Unity3d file что это фото. Unity3d file что это-image loader. картинка Unity3d file что это. картинка image loader

º Оптимизация изображения
Хотелось бы, чтобы работа с оптимизацией изображения под различные разрешения экрана была проще. Ведь достаточно чуть-чуть поработать с преобразованием координат и тогда не надо будет задумываться о том, что надо вычислять размеры, координаты через размер (разрешение) экрана.

Источник

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

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