Создание игры на Unity небольшой командой: особенности технологии

Разработчик детского мобильного паззла Fold the Adventure Алексей Галкин написал для ЦП колонку о том, на что следует обратить внимание при разработке мобильной игры на платформе Unity: как выбрать правильные ассеты из Asset Store, в каком сервисе хранить данные о прогрессе пользователей и где взять звуки для игры.

Начало

Чтобы сделать хорошую игру, нужна хорошая идея. Но даже имея идею, создать на её основе достойную игру совсем непросто. Помимо банальной удачи, существует огромное множество вещей, с которыми приходится иметь дело. Бесконечное число решений необходимо принять в весьма скромное время. Ведь время — это деньги, а бюджет проекта имеет тенденцию исчезать до того, как сделано хоть что-то достойное демонстрации (не говоря уже о выпуске).

Эта история о том, как наша небольшая инди-команда создавала игру Fold the Adventure («Сложи приключение») на основе оригинальной идеи. Поскольку игра выпущена, мы можем перевести дух и окинуть взглядом три месяца, потраченные на разработку. Несмотря на наличие многолетнего опыта в игровой индустрии, создание Fold the Adventure заставило нас изрядно попотеть ввиду непредсказуемых поворотов событий и весьма ограниченных ресурсов. Добро пожаловать в наше приключение!

Выбор движка: Unity

Сказать по правде, движок нам выбирать не пришлось. Мы сразу взяли Unity и не пожалели об этом. Нашей первостепенной задачей было создание сравнительно небольшой игры и запуск её на максимальном количестве платформ. При этом хотелось избежать возни с программированием на Objective C и Java. С Unity нам это удалось. И хотя всё было не так просто и гладко, как предполагалось, движком мы остались довольны.

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

Вот некоторые из рекомендаций, которые мы выработали после довольно продолжительного использования Unity (не ограниченного созданием Fold the Adventure):

  • Необходимо с самого начала потратить время на изучение базовой архитектуры Unity, включая игровые объекты, скриптуемые объекты, сцены, префабы, ассеты, методы-события и порядок их вызова, сопрограммы, измерение времени, обработку ввода, сериализацию. Unity может показаться лёгким в использовании движком, но без понимания его основ можно провести бесконечные часы за отладкой и рефакторингом.
  • С самого начала сформулируйте правила структуризации, а также именования ассетов и игровых объектов. Даже небольшая игра имеет тенденцию превращаться в хаос без должной организации. Существует много статей, посвящённых данному вопросу. Можно также ориентироваться на проекты, публикуемые разработчиками в Unity Assets Store.
  • Программируйте как можно меньше. Вместо этого активно используйте WYSIWYG (What You See Is What You Get — свойство прикладных программ, в которых содержание отображается в процессе редактирования и выглядит максимально близко похожим на конечную продукцию — ред.) возможности редактора Unity. Благодаря лёгкой расширяемости, редактор Unity позволяет превратить разрабатываемую игру в удобный конструктор, которым смогут пользоваться даже те, кто не умеет программировать. А это существенно упрощает и ускоряет создание игрового контента.
  • Избегайте использования привычных практик, которые плохо сочетаются с идеологией Unity. Какой бы заманчивой не была возможность размещения всех объектов игры в рамках одной сцены или сохранение параметров игровой механики в XML-файлах, подобный подход существенно осложнит жизнь на более поздних этапах разработки.
  • Будьте аккуратны и внимательны при использовании систем управления версиями. Unity имеет тенденцию непредсказуемо менять файлы. Например, внесение изменений в префаб влечёт за собой модификацию всех файлов сцен, в которых он используется, но только после их последующего сохранения. Всегда используйте force text в качестве режима сериализации ассетов и внимательно следите за файлами, которые заливаете на сервер, чтобы не уничтожить результат работы коллег.
  • Тестируйте игру на максимально возможном количестве платформ. При этом не забывайте, что настройки можно определить для каждой из платформ в отдельности. Без подобного тестирования, вы, как и мы, можете столкнуться с тем, что ваша игра ведёт себя по-разному в web player-е под Windows и OS X.

Ассеты: создавать или покупать

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

Если ассеты для игры могут быть сделаны силами команды — одной проблемой меньше (cкорее всего). Но есть ещё фаза прототипирования, а для неё также нужны ассеты, которые с большой долей вероятности будут выброшены. Поэтому практически каждой команде требуются ассеты, которые она не может или не хочет делать. Вопрос в том, где их взять.

Первым и самым очевидным ответом для нас, как для Unity разработчиков, был Unity Assets Store. Какой бы заманчивой ни казалась эта возможность, она влечёт за собой и ряд сложностей:

  • Ассеты в Unity Assets Store доступны всем без исключения. Однако никто не хочет, чтобы их игра была похожа на другие. По крайней мере, мы этого очень не хотели.
  • Практически невозможно купить один пак ассетов, который бы удовлетворил все нужды проекта, и возникает проблема стилистической совместимости. Для Fold the Adventure мы купили несколько паков, из которых использовали меньше половины.
  • Несмотря на то, что общее количество ассетов в Unity Assets Store довольно велико, найти среди них что-то подходящее для конкретных нужд бывает весьма непросто. При этом качество зачастую оставляет желать лучшего. Мы потратили часы на поиски, при этом не всегда имея возможность оценить качество пака без его покупки.

Вторым вариантом был аутсорсинг ассетов. Безусловно, этот путь отнимает больше времени и денег, чем просто покупка чего-то из Unity Assets Store. Однако, при удачном раскладе, получаемые ассеты уникальны и более качественны.

Существовала также возможность покупки ассетов из источников помимо Unity Assets Store. Однако здесь возникала проблема совместимости с Unity, и мы решили воздержаться от таких экспериментов.

Когда мы принимали решение о том, какие ассеты делать самостоятельно, а какие нет, мы начали с их категоризации. Как оказалось, ассетов, которые были уникальны для нашей игры и выделяли её среди прочих, было сравнительно немного. Их мы решили сделать своими силами, прибегая к помощи аутсорса там, где это было возможно. Остальное было куплено в Unity Assets Store.

Вот несколько простых правил, к которым мы пришли в ходе поиска и покупки ассетов в Unity Assets Store

  • Чтобы избежать стилистических расхождений, желательно покупать ассеты одного типа у одного автора. Это не мешает купить модели у одной студии, а партиклы у другой, но при этом нужно следить за совместимостью стилей.
  • Избегайте использования ассетов в том виде, в котором они были куплены. Достаточно внести небольшие изменения (например, немного перерисовать текстуры, реструкурировать партиклы) или использовать ассеты нестандартным образом.
  • Если вы планируете выпуск игры на мобильных платформах, убедитесь, что покупаемые ассеты оптимизированы под них.

Музыка и звуки заслуживают отдельного внимания. Они редко создаются инди-командой самостоятельно (если у вас есть композитор, то вам либо очень повезло, либо вы уже не инди). К счастью, существует большое количество сервисов, поставляющих royalty-free музыку и звуки, включая Unity Assets Store. И это совсем недорого.

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

Где хранить данные: Parse

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

В поисках лучшего решения мы обнаружили Parse. Parse — это кросс-платформенный сервис BaaS (Backend as a Service — платформа типа «бэкенд как сервис», предоставляет облачную серверную инфраструктуру для всех типов приложений — ред.), который позволяет приложению сохранять данные в облаке, поддерживает авторизацию пользователей, серверные функции, push-оповещения и аналитику. Это не исчерпывающий список функциональности, но он даёт общее представление о том, что есть Parse.

Одной из причин, по которым мы выбрали Parsе, была его интеграция с Facebook (первая версия Fold the Adventure создавалась именно для Facebook). И, несмотря на то, что позднее фокус разработки был смещён на мобильные платформы, мы продолжили использовать Parse.

Ещё одной приятной особенностью Parse является его ценовая политика. Поначалу она кажется немного странной, но, после размышлений и расчётов, оказывается более чем удачной. По существу, вы ограничены количеством запросов в секунду. Хорошая новость в том, что 30 запросов в секунду даются бесплатно. Может показаться, что это совсем немного, но на деле этого достаточно для поддержки тысяч пользователей. При условии, что вы используете эти запросы разумно.

В Parse вы делаете всё через запросы. Для получения и изменения полей данных, создания отношения между таблицами, формирования выборки - для всего нужен запрос. На первый взгляд, количество запросов должно быть довольно большим, но, на деле, большую часть элементарных операций можно объединить в один запрос с помощью метода ParseObject.SaveAllAsync. Кроме того, Parse выбросит исключение, если предел запросов в секунду превышен. Но ничего не мешает вам подождать некоторое время и выполнить запрос повторно. И хотя игра в таком случае может показаться пользователю «подвисшей», эта проблема легко обходится с помощью внесения небольших изменений в пользовательский интерфейс и логику сохранения и чтения данных.

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

Использование Parse в рамках Unity не сопряжено с особыми трудностями и хорошо документировано. По сути, вам необходимо скачать Parse SDK, настроить вашу игру на сервере Parse и в проекте Unity, а также немного попрограммировать. Один очевидный нюанс: вы не сможете использовать Parse, если устройство, на которое установлена ваша игра, не имеет доступа к интернету.

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

Стационарные платформы против мобильных: компромиссы

Несмотря на постепенное сближение в течение последних лет, стационарные и мобильные платформы по-прежнему сильно отличаются друг от друга. Это становится очевидным при попытке заставить «красивые» шейдеры работать приемлемо (не говоря уже о том, чтобы заставить их работать быстро) на всех устройствах, на которых предполагается запуск игры. Unity существенно облегчает этот процесс, но, к сожалению, не решает всех проблем.

Существует огромное количество статей, посвящённых оптимизации Unity-игр под мобильные платформы, так что здесь на эту тему будет сказано немного.

Вот несколько рекомендаций:

  • Если вы планируете запуск на мобильных платформах и используете тени, то ограничьтесь режимом forward lighting. Несмотря на запекание освещения и сокращения числа объектов, отбрасывающих тени, мы не смогли добиться приемлемой производительности в режиме deferred lighting. Возможно, мы недостаточно старались, но форумы Unity, по большей части, солидарны с нами в этом вопросе.
  • Минимизируйте количество draw call-ов. Большое количество полигонов в моделях не скажется так сильно на производительности, как дополнительные draw call-ы. Мы планировали большую оптимизацию, направленную на сокращение количества draw call-ов, чтобы лучше поддерживать старые модели мобильных устройств, но, к сожалению, не смогли сделать её в отведённые жёсткие сроки.
  • Не злоупотребляйте текстурными выборками в шейдерах. Это может привести к существенному падению производительности. В нашей игре мы были вынуждены использовать несколько специальных шейдеров вместо одного универсального — именно по этой причине.

Помимо различий в производительности и аппаратных ограничений, существует ещё одно важное отличие между стационарными и мобильными платформами. И это отличие — режим ввода. Игра, созданная для управления с помощью клавиатуры и мыши, плохо переносится на мультитач и акселерометр. Мы убедились в этом на своём горьком опыте. Прежде всего, в Unity разделена обработка мыши и мультитача. А потому было необходимо создать систему, унифицирующую этот аспект. Для этих целей мы использовали систему ввода из состава NGUI, которая, после небольших доработок, показала себя весьма хорошо. Она также позволила нам решить проблему распределения ввода между пользовательским интерфейсом и игровым управлением, которая доставляла нам некоторые неприятности на тот момент.

Пользовательский интерфейс в целом потребовал ряда модификаций для корректной поддержки мобильных устройств. Например, вместо прокручивания колёсика мыши и удерживания кнопок пришлось ввести мультитач-жесты. Некоторые из модификаций можно было потенциально упросить с помощью готовых решений из Unity Assets Store. Но в нашем случае это был простой pinch, а потому мы решили написать его за час с нуля, вместо того, чтобы тратить дни на подключение и отладку системы, которая «делает всё и даже больше».

Наибольшее количество проблем вызвало внутриигровое управление. Мы начали с традиционного набора ASWD + мышь для управления персонажем и камерой, планируя использовать экранный джойстик на мобильных устройствах. Но всё получилось не совсем так, как мы ожидали: игра стала практически неуправляемой. Нам пришлось срочно менять внутриигровое управление, при этом внося изменения даже в игровую механику. Методом проб и ошибок мы остановились на point-n-click управлении, которое на мобильных устройствах воспринимается интуитивно.

Локализация: чем проще, тем лучше

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

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

Надписей на текстурах лучше избегать, и использовать, например, текстовые поля NGUI. Если в игре необходима речь, то, скорее всего, потребуются и субтитры, поскольку локализация речи не только дорогое удовольствие, но ещё и требовательное с точки зрения места, занимаемого игрой.

Но минимизация локализуемого контента - это только начало. Следующий этап - подготовка самой игры к локализации. Плохая новость состоит в том, что Unity (на момент написания данной статьи) не имеет встроенных механизмов для этих целей. И хотя существует целый ряд специализированных решений в Unity Assets Store (например, l2 Localization), мы решили использовать систему локализации, идущую в составе NGUI.

Система локализации NGUI проста и понятна в использовании. Она построена на основе одного CSV-файла, содержащего колонку для каждого языка. Наличие такого файла очень удобно для отправки строк на перевод (для этого существует большое количество специализированных сервисов) и последующей вставки переведённой версии.

Мы использовали Google Docs Sheets для хранения файла локализации. В таком варианте он легко доступен всем членам команды, может быть быстро обновлён и скачен в CSV формате. Кроме того, мы открыли доступ к этому файлу ряду друзей, говорящих на других языках. Таким образом мы получили часть переводов бесплатно.

К сожалению, система локализации NGUI весьма ограничена в своих возможностях и не может быть использована в играх с большим количеством разнообразного локализуемого контента. Например, она не позволяет изменить шрифт текстового поля, а эта возможность необходима при локализации на такие языки, как японский. В связи с этим мы расширили функциональность класса NGUI UILocalize, добавив возможность изменять шрифт и варьировать положение текстовых полей.

Заключение

После того, как все сделано, игру необходимо тщательно протестировать (стоит это делать и неоднократно в процессе разработки). Этим должно заниматься максимальное количество людей на максимальном количестве устройств. Ничто не портит хорошую игру так, как пропущенный баг. При этом нужно обязательно слушать жалобы игроков. Поскольку эти жалобы превратятся в неудовлетворённых клиентов сразу после выхода игры на рынок. А этого точно никто не хочет.

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

Эта история, вместе с выпущенной игрой, завершает три месяца тяжёлой и кропотливой работ. И, несмотря на то, что мы столкнулись с множеством сложностей, процесс разработки Fold the Adventure был весьма захватывающим. Надеемся, что игра, которую мы создали, а также полученный нами опыт сделают мир немного лучше. Удачи в создании ваших игр!

(Можем дать ключи на игру в App Store — запросы пишите в комментариях.)

0
29 комментариев
Написать комментарий...
Индиана Девелопс

Вот такие статьи я люблю. Так как я сам индигейдевелопер.

Ответить
Развернуть ветку
Михаил Дымов

гейдевелопер?

Ответить
Развернуть ветку
Sergey Babaev

стандартная опечатка)

Ответить
Развернуть ветку
A K
Ответить
Развернуть ветку
Smart Steady

Всем привет, спасибо, что заинтересовались тем, как мы делали Fold the Adventure! :)

@Sergey Kritskiy: Здравствуйте!
Дело вовсе не в том, где лучше картинка/саппорт/цена.
На момент выбора движка - мы знали какую игру бы будем делать, что нам для это понадобится, какие ассеты, интерфейс, сеть, база данных и т.д. Честно скажу в сторону UE4 мы даже не смотрели зная, что в нём за 2 месяца мы не соберём игру ни при каких обстоятельствах, а на юнити мы это сделали. Однако мы не догматичны в своих утверждениях и если инди команда делает например крутой атмосферный проект, то UE4 со своим адским набором графических фич "из коробки" - будет идеальным решением для этой команды. Производство игры на UE4 - к сожалению все еще "по факту" стоит дороже чем производство игры на Unity, что в свою очередь для инди команды играет большую роль. Итого по совокупности факторов - «По нашему мнению, Unity — это лучший игровой движок для инди-разработки в настоящий момент».

@Anton Azarov: Здравствуйте!
Вы знаете, есть такой замечательный проект WoT, так вот на мой взгляд, надеюсь вы со мной будете солидарны, этому проекту стоило взять сразу UE или CryEngine и заткнуть за пояс по графике все ФПСММО на рынке. Но как мы знаем они взяли BigWorld и даже с ним заткнули за пояс все ФПСММО на рынке, но не по графике. Я думаю WoT не пожалели в итоге, что взяли BigWorld, также и мы не пожалели, что взяли Unity и нам удалось закончить проект в супер короткие сроки и запустить его в AppStore.

Если у читателей будут еще вопросы - пишите, постараюсь на них ответить :)

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

Ответить
Развернуть ветку
Sergey Babaev

не претендуя на тайное знание, но по ощущениям - чтобы допиливать BigWorld соразмерно росту своих потребностей - его пришлось купить и "руководить" допилкой самолично, без этого, не будь ВоТ настолько масштабным проектом, BigWorld вполне мог стать тем, о чем разработчики бы пожалели)

Ответить
Развернуть ветку
Smart Steady

Скорее всего так и было! Однако я думаю, что это не единственная причина по которой WG купили BW. ;)

Ответить
Развернуть ветку
Sergey Kritskiy

«По нашему мнению, Unity — это лучший игровой движок для инди-разработки в настоящий момент»
А не развернёте немного? Почему, например, не UE4, у которого на словах лучше картинка/саппорт/цена? Интересно, почему несмотря на всё это на UE4 до сих пор для телефонов вышла только шутка tappy chicken

Ответить
Развернуть ветку
Maxim Syabro

Во-первых Например тащемта дийкий ад С++ в сравнении с шарпом.
Во-вторых пока UE впаривали свой движок за хулиарды денег в ААА проекты Unity собрали большое комьюнити бесплатной версией. Погугли туториалы например для обоих проектов. Посмотри количество вопросов и частоту ответов на http://answers.unity3d.com/ и поищи аналог для UE

Но в скорости и качестве картинки UE далеко впереди

Ответить
Развернуть ветку
Sergey Babaev

побуду голосом справедливости, несмотря на то, что рекомендую Unity - UE уже доступен не за охулиард, причем ов многом благодаря политике Unity, с которой стало невозможно конкурировать при ценнике в миллион(ы) долларов. UDK и раньше был доступен, но не всем подходила его обрезанность и условия отчислений, а на обновление до фул UE условия тоже были далеки от демократичных.

Ответить
Развернуть ветку
Maxim Syabro

Ну так я и написал "впаривали" в прошедшем времени. Сейчас цены ок, но им нужно что-то решать с комьюнити.

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

Ответить
Развернуть ветку
Anton Azarov

Странная фраза "Мы сразу взяли Unity и не пожалели об этом." Если бы Вы сразу взяли UE4 - Вы бы тоже не пожалели в той же степени, в которой могли бы сразу взять Shiva или CryEngine. Скажите прямо - взяли первое попавшееся, которое вертелось на слуху.

По моему мнению после проб работы с UE4 и другими движками - Unity3D не первый в списке по качеству, возможностям, дружелюбности и т.д. Просто ребята не плохо разбираются в маркетинге. Своеобразные "Стивы Джобсы" в этом деле.

Ответить
Развернуть ветку
Sergey Babaev

я например тестировал UE для online-проекта и с вашей категоричной оценкой про "если бы - то" не согласен) За конкретно этот пример пусть авторы конечно расскажут.

Ответить
Развернуть ветку
Maxim Syabro

Ага, C++ охранять как дружелюбнее C#

Ответить
Развернуть ветку
Anton Azarov

Mono сделали C#, который теперь работает в UE4, например ;)

Ответить
Развернуть ветку
Maxim Syabro

Опа, круто
можно ссылку?

Ответить
Развернуть ветку
Иван Сурков
Ответить
Развернуть ветку
Иван Сурков

Пикачу-ассасин? O_o

Ответить
Развернуть ветку

Комментарий удален модератором

Развернуть ветку

Комментарий удален модератором

Развернуть ветку

Комментарий удален модератором

Развернуть ветку
Pavel Zabelin

А можно ключик? Из скриншотов геймплей неясен )

Ответить
Развернуть ветку
Sergey Babaev

конечно - AX3E6XMTJ3PT

Ответить
Развернуть ветку
Михаил Толстой

Хотел бы попробовать сыграть, дайте пожалуйста ключ.)

Ответить
Развернуть ветку
Sergey Babaev

LTR6M7YLENNW

Ответить
Развернуть ветку

Комментарий удален модератором

Развернуть ветку

Комментарий удален модератором

Развернуть ветку

Комментарий удален модератором

Развернуть ветку

Комментарий удален модератором

Развернуть ветку
Pavel Zabelin

Спасибо за ключ - поиграл. Вопрос - на какую ЦА вы ориентировались создавая эту игру? Для взрослых - слишком детский сеттинг. Для детей слишком сложная игра.

Ответить
Развернуть ветку
Smart Steady

Привет!
ЦА 7+, 25+
Детям легко дается, взрослым труднее... в целом аудиторию любителей головоломок.

Ответить
Развернуть ветку

Комментарий удален модератором

Развернуть ветку
Alexey Strelkov

Подлитесь ключиком, пожалуйста

Ответить
Развернуть ветку
Sergey Babaev

делимся) X7XXAJN9JN6F

Ответить
Развернуть ветку
Данил Тимофеев

Уж очень заманчиво, дай ключ ;)

Ответить
Развернуть ветку
Smart Steady

Лови: HJMYXPRLKWYT

Ответить
Развернуть ветку
Smart Steady

А мы немного прошли гринлайт :)
Так что ждите нас скоро еще и в стиме :)
http://steamcommunity.com/sharedfiles/filedetails/?id=320242015

Ответить
Развернуть ветку

Комментарий удален модератором

Развернуть ветку

Комментарий удален модератором

Развернуть ветку

Комментарий удален модератором

Развернуть ветку

Комментарий удален модератором

Развернуть ветку
26 комментариев
Раскрывать всегда