Русский
Русский
English
Статистика
Реклама

Разработка игр

Анонс как Data Science продает вам рекламу

14.09.2020 12:17:21 | Автор: admin


Сегодня, в 20:00 в прямом эфире выступит Никита Александров Data Scientist в Unity.

Никита закончил ФКН ВШЭ, во время последнего курса получил стипендию Эразмуса и съездил на семестр по обмену в Финляндию. Несмотря на то, что Никита получил оффер на работу в Токио, он решил поступить в магистратуру университета Аалто. Магистратуру он не закончил и ушел работать Data Scientistом в Unity Ads, где сейчас улучшает алгоритмы конверсии. Никита расскажет о том, как устроен IT-рынок Финляндии, что там есть интересного и какие задачи решают дата-саентисты в Unity. Кроме того, Никита готов поделиться инсайдами, как правильно рекламировать свою игру при помощи Unity, чтобы ее чаще устанавливали.

Стрим можно будет посмотреть в любых наших соцсетях где удобно, там и смотрите.




Эфир разделится на три блока


Что ловить айтишникам в Финляндии?

  • Как студенту уехать по обмену на семестр в Европу: как Никита получал стипендию Эразмуса.
  • Магистратура в Финляндии: зачем ехать, что есть интересного? Почему из финской магистратуры невозможно вылететь?
  • Как прожить в Финляндии на 500 евро и почему я решил этого не делать.
  • Рынок IT-труда в Финляндии.

Про Data Science

  • Что дата-саентисты делают в Unity: что они делают именно в Unity, что значит эта позиция в Unity.
  • Что конкретно я делаю в Unity как дата-саентист.
  • Как улучшить конверсию рекламы, не меняя продукт: техники доступные дата-саентисту.
  • Как изменение настроек приватности в iOS 14 может повлиять на мир таргетированной рекламы в гейм-индустрии.
  • Федеративное обучение: что это и может ли это решить проблему с iOS 14.

Как рекламировать свои игры в Unity

  • Зачем разработчики покупают трафик прямо в Unity когда есть Facebook и Google.
  • Как устроен показ рекламы в Unity.
  • Разные виды трагетированных реклам и их эффективность: баннеры, видео, играбельные рекламы, за вознаграждение и остальные.
  • Как дата-сайнс помогает зарабатывать на хлеб гейм-девелоперам.
  • Техники, которые позволяют эффективно и постоянно продвигать свою игру в рекламе: что делать, чтобы игру устанавливали.

Вы можете задать Никите вопрос здесь в комментариях, в инстаграме и прямо во время эфира.



Куда жать, чтобы не пропустить эфир?


Жмите на колокольчик на ютубе или подписывайтесь на наш инстаграм, тогда вам придет уведомление о начале эфира.

Еще раз напоминаем дату и время: понедельник, 7 сентября, 20:00



До встречи в эфире!




Подробнее..

Перевод Как рендерится кадр DOOM Ethernal

11.09.2020 14:12:59 | Автор: admin


Вступление


Doom Eternal не нуждается в отдельном представлении: это прямой преемник Doom 2016, разработанный благодаря седьмой итерации id Tech, внутреннего движка студии id Software. В свое время меня поразило и высокое качество визуальной составляющей Doom 2016, и простота и элегантность технических решений. В этом отношении Doom Eternal превосходит своего предшественника во многих областях, и некоторые из них достойны детального разбора. В этой аналитической статье я постараюсь обсудить их все.

Мой анализ вдохновлен трудом Adrian Courrges про Doom 2016 (перевод). Я считаю, что подобные работы позволяют взглянуть на подходы к решению некоторых проблем рендеринга AAA-проектов и тем самым становятся превосходными обучающими материалами. В этом анализе я планирую обсудить общие особенности и не погружаться слишком глубоко в тонкости каждого способа и прохода рендеринга. Кроме того, некоторые проходы в Doom Eternal почти не отличаются от своих аналогов в Doom 2016 и уже были разобраны в труде Adrian Courrges, поэтому я могу их пропустить.

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

Итак, приступим.

С выходом id Tech 7 переход движка с OpenGL на Vulkan API позволил разработчикам эффективнее работать с особенностями текущего поколения графических процессоров, например несвязанными ресурсами (bindless resources).

Один кадр в Doom Eternal




Выше мы можем видеть близкую к началу секцию игры: интерьер с несколькими противниками и объемным освещением. По аналогии с его предшественником, процессом визуализации в Doom Eternal заведует прямой рендеринг, однако если Doom 2016 вынужден проводить прямой рендеринг совместно с G-буферизацией отражающих поверхностей, в нашем случае буфер не используется и прямой рендеринг берет на себя все задачи.

Уход от мегатекстур


С выходом созданной на движке id Tech 5 игры Rage мир познакомился с концептом реализации текстур под названием мегатекстуры. Этот метод применяется в Doom 2016 и на каждый кадр он рендерит так называемую виртуальную текстуру с информацией о видимых текстурах. Виртуальная текстура анализируется в следующем кадре, чтобы определить какие текстуры следует подгрузить с диска. Однако у мегатекстур есть очевидная проблема: как только текстура попадает поле зрения, подгружать ее уже поздновато, поэтому на первых нескольких кадрах после появления текстура выглядит размыто. С выходом id Tech 7 разработчики отказались от такого метода.

Скиннинг через графический процессор


Обычно еще до отрисовки каких-либо текстур и шейдинга оценку скиннинга проводит вершинный шейдер. Скиннинг id Tech 7 проводится заранее вычислительным шейдером с записью итоговых вершин в буфер. Благодаря такому подходу вершинному шейдеру больше не нужны данные скиннинга, и так как при каждом проходе геометрии он больше не проводится, в итоге перестановки шейдеров случаются реже.

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

По ссылке можно ознакомиться с отличной статьей о скиннинге через вычислительный шейдер за авторством Jnos Turnszki.

Полезно также отметить, что в Doom Eternal используется интересный вид кеширования Alembic Cache, сравнимый с сильно сжатым обратно воспроизводимым видео. В таких кешах хранится запеченная анимация для выдачи и разжатия в ходе исполнения программы. Цитируя технический анализ Digital Foundry, Alembic Cache применяется в широком диапазоне анимаций, начиная с масштабных кинематографичных сцен и заканчивая крохотными щупальцами на полу. Особенно удобен такой подход для анимаций со сложностями реализации через скиновую анимацию, например для органики и симуляции тканей. Если вас заинтересовала эта технология, рекомендую ознакомиться с презентацией Axel Gneiting на Siggraph 2014.

Карты теней


Следующим этапом является рендеринг теней, и подход к генерации их карт на первый взгляд у id Tech 7 и его предшественника не отличается.

Как можно видеть ниже, тени рендерятся в большую текстуру глубиной 24 бита и размером 4096 на 8196 пикселей, местами различаясь по уровню качества. Текстура не меняется между кадрами, и согласно презентации Devil is in the Details на Siggraph 2016, статичная геометрия кешируется в карте теней, чтобы не перерисовать ее для каждого кадра. Идея сама по себе проста: нам не нужно обновлять тени до тех пор, пока перед источником света не начнется движение, и таким образом мы можем объявить кешированную карту теней: обычную карту со статичной геометрией, так как мы полагаем, что геометрия не меняется. Если в конусе обзора движется динамический объект, кешированная карта теней копируется в основную, и поверх этого перерисовывается динамическая геометрия. Такой подход позволяет не перерисовывать всю сцену в конусе обзора при каждом ее обновлении. Естественно, при смещении света всю сцену придется перерисовать с нуля.

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

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



Скорость и предварительный проход обработки глубины


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


Оружие игрока


Статические объекты


Динамические объекты

В предварительном проходе рендерится не только глубина, но и целевой цвет. В динамической геометрии скорость рендерится через векторы движения, то есть положение текущей позиции, вычитаемое из положения пикселя на предыдущем кадре. Так как движение хранится в красном и зеленом каналах 16 битной цели рендеринга с плавающей точкой, нам достаточно знать движение по осям X и Y. В дальнейшем эта информация используется при постобработке для применения размытости и репроекции временного сглаживания. Статичной геометрии векторы движения не нужны, поскольку она двигается только относительно камеры и ее движение может быть высчитано из движения самой камеры. Как можно видеть на скриншоте ниже, движения в нашей сцене не так уж и много.



Z-иерархическая глубина


Следующий шаг это генерация иерархической mip-цепи буфера глубины: эта цепь похожа на mip карту, но вместо усреднения четырех соседних пикселей берет их максимальное значение. Такой подход часто используется в графике для множества задач, например для ускорения отражений и отбрасывания прегражденной геометрии. В нашем случае mip-цепь отбрасывает освещение и декальные текстуры, о которых мы поговорим позднее. В последнее время mip-генерацию проводят за один проход, с записью сразу в несколько mip-ов, но в Doom Eternal запись все еще ведется отдельно для каждого mip.



Декали сеток


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

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

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

Ниже мы можем видеть сетку декальных текстур. Для удобства визуализации идентификаторы покрашены в разные цвета.



Отбрасывание света и декалей


Свет в Doom Eternal полностью динамичен, и одновременно в область обзора может попадать до нескольких сотен источников. Кроме того, как мы уже отметили ранее, декали в игре имеют большое значение, например в том же Doom 2016 число декалей перевалило за тысячи. Все это требует особого подхода к отбрасыванию лишнего, иначе производительность не выдержит тяжести пиксельных шейдров.

В Doom 2016 использовался процессорный вариант кластерного отбрасываения света: свет и декали собирались в конусообрзаные фроксели, которые затем считывались в ходе шейдинга через определения индекса кластера из позиции пикселя. Размер каждого кластера составлял 256 пикселей и для сохранения квадратной формы логарифмически делился на 24 сегмента. Такой прием вскоре переняли многие другие разработчики, и похожие методы встречаются, например, в Detroit: Become Human и Just Cause.

Учитывая рост числа источников динамического освещения (сотни) и декалей (тысячи), процессорной кластеризации отбрасывания освещения в Doom Eternal уже не хватало, так как воксели становились слишком грубыми. В итоге для id Tech 7 разработчики придуман иной подход, и через исполняемые на различных этапах вычислительные шейдеры создали программный растеризатор. Сначала декали и свет связываются в гексаэдр (шестигранник) и передаются в вычислительный растеризатор, откуда вершины проецируются в экранное пространство. Затем второй вычислительный шейдер обрезает треугольники по границам экрана и собирает их в тайлы размером 256 на 256 пикселей. Одновременно с этим по аналогии с кластерным отбрасыванием отдельные элементы источников света и декалей записываются во фроксели, после чего следующий вычислительный шейдер проводит похожую процедуру под тайлы 32 на 32 пикселя. В каждом тайле прошедшие тест на глубину элементы помечаются в битовое поле. Последний вычислительный шейдер переводит битовые поля в список источников света, которые в итоге используются при проходе освещения. Что характерно, индексы элементов все еще записываются в трехмерные фроксели размером 256 на 256 пикселей по аналогии с кластерным подходом. В местах со значительным прерыванием глубины, для определения числа источников света в каждом тайле сравнивается минимальное значение и нового списка источников света, и старого списка кластерных источников.

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

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

Преграждение окружающего света в экранном пространстве


Преграждение окружающего света вычисляется в половинном разрешении вполне стандартным путем: сначала 16 случайных лучей исходят из позиции каждого пикселя в полусфере, а затем при помощи буфера глубины определяются пересекающиеся с геометрией лучи. Чем больше лучей пересекает геометрии, тем больше будет преграждение. Эта техника называется преграждение направленного света в экранном пространстве (Screen Space Directional Occlusion), или SSDO, и подробное его описание за авторством Yuriy O`Donnell можно прочитать по ссылке. Вместо традиционного хранения значений преграждения в одноканальной текстуре, направленное преграждение хранится в трехкомпонентной текстуре, а итоговое преграждение определяется через скалярное произведение над нормалью пикселя.

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



Непрозрачный прямой проход


В этом проходе многие элементы наконец встают на свои места. В отличие от Doom 2016, здесь все рендерится напрямую через несколько массивных мегашейдеров. Во всей игре предположительно около 500 процессорных состояний и дюжина макетов дескрипторов. Сначала рендерится оружие игрока, потом динамические объекты, и затем статические. Обратите внимание, что порядок не особо-то и важен, ведь благодаря препроходу глубины мы уже получили буфер глубины, и он может заранее исключать не соответствующие глубине пиксели.


Оружие игрока


Динамические объекты


Первый набор статических объектов


Второй набор статических объектов

У большинства движков AAA-игр графы шейдеров и особенности статических шейдеров позвляют разработчикам возможность креативно подходить к работе со всевозможными материалами и поверхностями, и каждый материал, каждая поверхность ведет к созданию своего собственного уникального шейдера. В результате мы сталкиваемся с невероятным многообразием перестановок шейдеров для всех возможных комбинаций особенностей движка. Однако id Tech сильно отличается от других AAA-проектов: он комбинирует почти все материалы и особенности во всего лишь несколько массивных мегашейдеров. Такой подход позволяет графическим процессам жестче объединять геометрию, что в свою очередь положительно сказывается на производительности. Позже мы еще это обсудим.

Несвязанные ресурсы


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



Динамическое слияние вызовов отрисовки


Поверх архитектуры полностью несвязанных ресурсов все данные геометрии выделяются из одного большого буфера. В этом буфере попросту хранится смещение всей геометрии.

Здесь в игру вступает самая интересная технология idTech 7: динамическое слияние вызовов отрисовки. Она полагается на архитектуру несвязанных ресурсов и обобщенную память вершин, и в итоге значительно уменьшает число вызовов отрисовки и время работы процессора. Еще до начала какого-либо рендеринга вычислительный шейдер динамически создает непрямой буфер индексов для эффективного слияния геометрий из невзаимосвязанных сеток в единый непрямой вызов отрисовки. Без несвязанных ресурсов добиться слияния вызовов не получилось бы, поскольку оно работает с геометриями с несовпадающими свойствами материалов. В дальнейшем воспользоваться динамическим буфером индекса можно будет вновь, как для препрохода глубины, так и для препрохода освещения.

Отражения


Чаще всего для создания отражений экранного пространства вычислительный шейдер использует алгоритм raymarching. Алгоритм испускает из пикселя луч в мировое пространство в сторону отражения, которое зависит от неровности отражающей поверхности. Точно так же дело обстояло в Doom 2016, там как часть прямого прохода записывался небольшой G-буфер. Однако в Doom Eternal уже никакого G-буфера нет, и даже отражения экранного пространства вычисляются не в вычислительном шейдере по отдельности, а сразу в прямом шейдере. Интересно узнать, насколько такое отклонение в пиксельном шейдере влияет на производительность, поскольку создается ощущение, что ценой повышенной нагрузки на регистр разработчики пытались снизить количество целей рендера и как следствие сократить нагрузку на пропускную способность памяти.

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



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

Частицы


Симуляция


В Doom Eternal часть процессорной симуляции частиц ложится на плечи вычислительных шейдерв, поскольку у некоторых систем частиц есть зависимости от информации экранного пространства, например буфера глубины для симуляции столкновений. Тогда как другие системы частиц могут прогоняться в кадре сразу и вычисляться асинхронно, таким симуляциям предварительно необходимы данные препрохода глубины. Что характерно, в отличие от традиционной шейдерной симуляции частиц, здесь симуляция ведется через выполнение последовательности команд из хранящегося в вычислительном шейдере буфера. Каждый поток шейдера прогоняет все команды, среди которых может быть по несколько kill, emit или модификаций параметра частицы. Все это похоже на записанную в шейдере виртуальную машину. Я многое не понимаю в тонкостях работы такой симуляции, но основан подход на презентации Brandon Whitney The Destiny Particle Architecture на Siggraph 2017. Метод в презентации очень похож на описанный мною выше и используется во множестве других игр. Например, я уверен, что похожим образом в Unreal Engine 4 работает система симуляции частиц Niagara.

Освещение


По аналогии с Doom 2016 и описанному на Siggraph 2016 методу, разрешение частиц освещения отделено от собственно разрешения экрана, что дает разработчикам управлять разрешением каждой системы частиц в зависимости от качества, размера экрана и прямого управления. Для низкочастотных эффектов освещение можно предоставить в значительно более низком разрешении почти без потери в качестве по сравнению с, например, искрами, которым требуется высокое разрешение. Освещение и доминирующее направление света хранятся в двух атласах размером 2048 на 2048 пикселей, оба они доступны для каждого прохода благодаря несвязанным ресурсам, как и любая другая текстура. В дальнейшем для рендера частиц простая геометрия отрисовывается через сэмплинг этих атласов.


Увеличенный фрагмент атласа освещения.

Небо и рассеяние


Теперь мы поговорим про объемное освещение. Его генерация состоит из четырех проходов и начинается с создания 3D LUT текстуры для атмосферы неба через raymarching сквозь само небо в сторону источника света.



С первого раза можно не понять, что именно отображает текстура на картинке, но если мы повернем ее на 90 градусов и растянем по горизонтали, все станет ясно: перед нами рассеяние атмосферы. Поскольку оно более вариативно по вертикали чем по горизонтали, то и разрешение по вертикали больше. Атмосфера представлена сферой, поэтому горизонтальное вращение обычно называется долготой, а вертикальное широтой. Атмосферное рассеяние вычисляется полусферой и покрывает 360 градусов долготы и 180 градусов широты верхней части сферы. Для покрытия различных расстояний до наблюдателя в LUT текстуре содержится 32 сегмента глубины, и вместо перевычисления данных неба в каждом кадре процесс распределяется на 32 кадра.



Благодаря LUT текстуре, следующий проход вычисляет рассеяние света на наблюдаемый фроксель по аналогии с кластерным преграждением света в меньшем масштабе. Пронаблюдать несколько сегментов, от ближнего к дальнему, можно ниже.



В третьем проходе данные рассеяния для каждой клетки множатся в каждую последующую клетку в сторону обзора и пишутся в новую 3D текстуру.

В итоге поверх рендеририуемого изображения через сэмплинг только что сгенерированной 3D текстуры на основе глубины пикселей ставится объемное освещение.


До


После

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



Прозрачность


По аналогии с Doom 2016, прозрачность рендерится прямым проходом после непрозрачной геометрии при наличии данных о рассеянии света. Текстура сцены при этом теряет в разрешении (downsamples), а для имитации прозрачности на основе гладкости поверхности подбирается подходящий mip-уровень. Данные о рассеянии света помогают создать изнутри поверхности визуально хорошее рассеяние.

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


Для прозрачности теряют в разрешении только имеющие к ней отношение пиксели.

Пользовательский интерфейс


Обычно последним проходом в кадре становится пользовательский интерфейс. Как это обычно и происходит, интерфейс рендерится во вторичную с полным разрешением LDR (восьмибитную) цель рендера, и цвет предварительно умножается на альфа-канал. В ходе тональной компрессии интерфейс накладывается на HDR текстуру. Обычно заставить интерфейс работать с остальным HDR контентом кадра не так-то просто, но в Doom Eternal при тональной компресии интерфейс волшебным образом скалируется и выглядит естественно на фоне прочего 3D контента.



Постобработка


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

Далее идет целевое воздействие: эта RG (двухцветная) текстура форматом 1 на 1 содержит в себе среднее значение освещения всей сцены и вычисляется путем последовательного уменьшения разрешения цветовой текстуры и получения средней освещенности группы пикселей. Чаще всего такой прием используется для имитации привыкания человеческого глаза к резкой смене окружающей яркости. Также средняя освещенность используется при вычислении воздействия в ходе тональной компрессии.



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

Затем тональная компрессия объединяет все эффекты. Один-единственный вычислительный шейдер делает следующее:

  • Применяет искажение
  • Рендерит поверх Bloom текстуру
  • Вычисляет виньетирование, грязь на камере, хроматическую аберрацию, отблеск от линз и множество других эффектов
  • Получает значение воздействия на основе средней освещенности
  • Дает тональной компрессии распределить HDR цвета по корректным диапазонам как для LDR, так и для HDR через кастомный оператор тональной компрессии.

И наконец, сверху накладывается интерфейс.

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



Заключение


Наш поверхностный разбор одного кадра Doom Eternal подошел к концу, хоть я и уверен, что не затронул несколько влияющих на внешний вид игры моментов. На мой взгляд, Doom Eternal это невероятный успех в техническом плане, и в будущем id Software сможет еще больше повысить планку. Команда разработчиков успешно продемонстрировала нам, как разумный подход и эффективное планирование помогли создать высококачественную игру, и я верю, что это отличный пример для подражания, равно как и обучающий материал. С нетерпением жду будущих разработок id Software.

Rip and tear, until it is done.

Справочные материалы


Подробнее..

Перевод Вставка реальных объектов в Unity с помощью Meshroom

14.09.2020 08:09:56 | Автор: admin
image

Из этой статьи вы узнаете, как использовать фотограмметрию для фотосканирования и вставки объектов реального мира в проекты Unity при помощи Meshroom.

В контексте 3d-моделей фотограмметрия это процесс создания 3D-моделей из отсканированных объектов.

Вам нужны реалистичные 3D-модели для идеи игры, но у вас ограничен бюджет? Хотите создать сверхреалистичное игровое окружение? Если да, то продолжайте чтение!

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


В этом туториале мы используем несколько фотографий статуи дракона для создания детализированной 3D-модели, подчистим её в Blender и импортируем результат в тёмную и мрачную сцену деревни викингов.

Также вы научитесь:

  • Выбирать хороший объект для фотосканирования.
  • Делать подходящие фотографии.
  • Решать, когда и где делать фотографии.
  • Убирать плохие фотографии.
  • Использовать Meshroom для создания 3D-сканов.
  • Импортировать модели в Blender для очистки и ретуши.
  • Уменьшать количество полигонов.
  • Подготавливать и импортировать 3D-модели в Unity.



Приступаем к работе


Примечание: для выполнения фотосканирования в Meshroom за разумное время требуется довольно мощный компьютер.

Скачайте материалы проекта. Далее распакуйте файл zip в удобное для вас место.

Архив содержит файлы проектов Starter и Final, до которых мы доберёмся позже.

Примечание: нужно будет скачать ещё один файл, но подробнее об этом ниже.

Для создания хорошего фотоскана нужно сфотографировать модель со множества углов. В этом туториале у нас уже будут все необходимые фотографии.

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

Поиск хороших объектов для съёмки


Разумеется, вам хочется снимать объекты, соответствующие игровому окружению или идее. Кроме этого, есть некоторые условия, упрощающие фотосканирование объектов:

  • Плоские основания.
  • Достаточная контрастность цветов или оттенков.
  • Не слишком тёмные, иначе будет сложно различить детали.
  • Не слишком большие, иначе будет сложно обходить объекты и снимать их с разных углов.

Стоит также подумать над созданием фотосканов объектов, которые сложно моделировать. Например, природные объекты трудно моделировать, потому что возникают сложности с созданием органических материалов.

Хорошим примером этого являются камни из The Tales Factory в Unity Asset Store.


Когда делать фотографии


Что касается фотографий, то есть определённые условия и время суток, позволяющие создавать качественные фотосканы.

Если у вас есть студия в помещении и освещение, то можно смоделировать эти условия самостоятельно. Если их нет, то придётся работать на открытом воздухе.

При работе на открытом воздухе помните о следующем:

  • Делайте фотографии при дневном свете.
  • Лучше всего подходит пасмурное небо. Облака рассеивают освещение, что приводит к более равномерному распределению света по модели.
  • Сухость это хорошо, влажность это плохо! В условиях влажности вы, скорее всего, получите больше отражений на готовом результате. В некоторых случаях на 3D-модели даже будут яркие блики, разрушающие реализм.

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

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

Съёмка фотографий


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

Скачать фотографии скульптуры дракона (файл zip, 695 МБ)

Далее извлеките файл zip в удобное для вас место и откройте папку. Вы увидите примерно 100 фотографий скульптуры дракона, сделанные с разных углов и высот.


Именно к такому следует стремиться при съёмке фотографий. Покрытие фотографиями на 360 градусов с разных углов и высот обеспечивает передачу всех нужных деталей.

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

Не двигайте и не поворачивайте модель во время съёмки, потому что это сбивает с толку некоторые программные пакеты. Двигайтесь сами вокруг объекта, не перемещая его.

Настройки камеры


Чтобы получить однородно выглядящий набор фотографий, переключите камеру в ручной режим или режим pro. Большинство камер смартфонов позволяют переключаться в этот режим, а DSLR-камеры всегда имеют ручной режим или режим M.

  • Сначала установите режим Fixed для ISO. Эта настройка изменяет чувствительность датчика цифровой камеры к освещению. Чем выше значения, тем проще делать фотографии в темноте, но они получаются более шумными. При хорошем освещении на открытом воздухе обычно можно устанавливать значение 100 или 200. Для фотосканируемых изображений всегда выбирайте фиксированное значение.
  • Если вы новичок в фотографии, используйте режим фиксированного затвора или фиксированной апертуры. Иногда они называются режимами Priority. Это помогает зафиксировать один из параметров, позволяя камере управлять другими параметрами для сохранения равномерной выдержки. Это тоже делается для получения хорошо освещённых фотографий.
  • При съёмке фотографий они всегда должны быть в фокусе. Избегайте размытых изображений.
  • Всегда делайте снимки в одной ориентации, альбомной или портретной.


Для полудня и пасмурного освещения в этой ситуации подошло фиксированное значение ISO 200.

Создание фотосканов при помощи Meshroom


Meshroom это open-source-приложение для фотограмметрии, которое можно использовать для создания 3D-моделей из фотографий. Мы будем использовать его для создания фотоскана скульптуры дракона.

Скачайте последнюю версию отсюда и установите её. Для запуска ПО потребуется Windows или Linux.

После запуска Meshroom вы увидите основной интерфейс. В этом режиме можно делать всё необходимое для создания фотосканов.

Вот краткое описание каждой части основного интерфейса:


  1. Сюда перетаскиваются фотографии, которые нужно воссоздать в 3D.
  2. Image Viewer это просто окно предварительного просмотра для отдельных фотографий.
  3. После начала процесса в окне 3D Viewer будут отображаться результаты воссоздания объекта. Также в нём демонстрируются различные углы, под которыми сделаны фотографии, на основании метаданных, полученных программой из изображений.
  4. В Graph Editor отображается конвейер процессов, выполняемых приложением для воссоздания объекта в 3D. Каждый этап представлен узлом (нодом, Node). На каждом этапе происходит серия вводов и выводов с обработкой. Фотографии проходят через этот конвейер, а результат каждого этапа передаётся на следующий этап.
  5. В окне Node можно получить доступ к подробностям отдельных нодов в Graph Editor. Здесь можно изменять настройки каждого нода, но в рамках нашего туториала достаточно будет настроек по умолчанию.

Импорт фотографий в Meshroom


Перейдите к скачанным фотографиям скульптуры дракона. Затем перетащите их в окно Images приложения Meshroom.

Фотографии в порядке имён файлов отобразятся в окне Images.


Далее нажмите на File Save As, выберите новое расположение папки на диске и сохраните проект как Dragon.mg.


Далее закройте все ненужные приложения, чтобы подготовиться к процессу воссоздания Meshroom. Сканирование не начнётся, если у компьютера мало ресурсов.

Начинаем процесс воссоздания Meshroom


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


Пока процесс выполняется, можно отслеживать ход его выполнения в окне Graph Editor. Обратите внимание на небольшие цветные индикаторы выполнения на каждом ноде. Процессор вашего компьютера, скорее всего, достигнет завершения через 15-30 минут, это зависит от его скорости.


После завершения полученный скан отобразится в окне 3D Viewer.


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


Далее откройте папку, в которую сохранили файл проекта Meshroom. В ней вы найдёте папку MeshroomCache. Именно сюда выводились файлы отсканированного объекта.


Импорт в Blender


Далее мы воспользуемся популярным приложением создания 3D-графики для импорта скана 3D-модели дракона и его очистки, чтобы подготовить импортировать в Unity.

Если у вас нет Blender версии 2.80 или выше, то скачайте и установите его. Скачать его можно здесь.

После завершения установки запустите Blender.

Если вы новичок в Blender, то ничего не бойтесь и расслабьтесь! Хотя его интерфейс и управление могут сбивать с толку, здесь вы найдёте многие полезные для нас сочетания клавиш и скриншоты.

После открытия экрана запуска Blender нажмите на General в разделе New File. По умолчанию новый файл содержит объект-куб. Нажмите на куб, чтобы выбрать его, затем нажмите Delete, чтобы от него избавиться. Теперь у нас есть свободное место для работы:


Далее нажмите на File Import Wavefront (.obj). Перейдите в папку MeshroomCache, в которую Meshroom сохранил файлы модели отсканированного объекта. В этой папке найдите модель в подпапке MeshroomCache/Texturing/GUID. GUID это имя папки со случайным идентификатором, назначаемым после завершения сканирования.

Затем дважды нажмите на texturedMesh.obj для импортирования скана в Blender.


Импорт может занять долгое время, потому что отфотосканированная модель довольно велика.


Начнём с того, что получим представление о том, как по умолчанию выглядит отфотосканированный дракон. Нажмите на кнопку Viewport Shading в основном окне Blender, чтобы включить полное затенение и текстурирование этого окна.


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

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


Вносим исправления в Blender


Модель сложна и имеет большое количество вершин и деталей. Теперь мы уменьшим количество полигонов при помощи быстрого и эффективного модификатора Blender под названием Decimate.

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

На панели Scene Collection редактора Blender нажмите texturedMesh Modifiers Add Modifier.


В списке выберите Decimate.


Появится небольшое поле настройки с опциями Decimate.

Выберите Collapse со значением Ratio, равным 0.1, а Factor, равным 2.0. Затем нажмите на Apply.

Это уменьшит детализацию всей модели и снизит количество вершин примерно в 10 раз! Вы не заметите особой потери деталей, но профилировщик Unity и скорость игры будут вам благодарны!

Избавляемся от травы


Теперь нам нужно заняться самым очевидным.

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

Теперь сосредоточимся на удалении из меша травы.

В Blender переключимся из Object Mode в Edit Mode. Для этого можно использовать UI или нажать Tab на клавиатуре.


Обратите внимание на изменения в окне: теперь там отображаются вершины дракона, и все они выделены.


Выбираем вершины


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

Учтите, для следующего этапа потребуется много терпения.

Сначала выберем инструмент Select Lasso, нажав Shift + Space, L. (Shift и Space одновременно, а затем L).

Или воспользуемся панелью UI для выбора инструмента Select Lasso.


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

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

Вот GIF, иллюстрирующий этот процесс:


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

Если вы выбрали некоторые вершины, которые не хотите удалять, то нажмите на них по отдельности Shift + левой клавишей мыши, чтобы убрать их из выбранных вершин.

Выбрав всю траву и окружающие её вершины, нажмите Delete Vertices, чтобы удалить их.



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

После завершения вы увидите подобную устрашающую картину:


Закрываем основание меша


Теперь перейдите в Edit Mode, если ещё не находитесь в нём. Переключение режимов выполняется нажатием клавиши Tab. Затем выберите всю нижнюю границу вершин меша дракона при помощи инструмента Select и удерживая Shift. Для этого потребуется терпение.


Или можно использовать для этого инструмент Select Lasoo.


Выбрав вершины, нажмите Vertex Smooth Vertices.


Это немного сгладит выбранное нами кольцо вершин.

Теперь выберите инструмент 3D Cursor, нажав Shift + Space, Space и расположите его на уровне самой нижней вершины в выбранной нижней границе, нажав левую клавишу мыши. Для определения самой нижней вершины можно панорамировать и вращать окно просмотра, чтобы посмотреть на дракона сбоку.

Затем нажмите клавишу . (точка) для выбора Pivot Point, далее выберите 3D Cursor, чтобы использовать в качестве опорной точки позицию 3D-курсора.

Далее нажмите S, Z, 0, Enter (это ноль, а не O). Эта серия команд отмасштабирует все выбранные вершины до той же высоты, что и у опорной точки, которую мы создали при помощи 3D-курсора.

Теперь мы получили примерно такой меш:


Наконец, нажмите Vertex New Edge/Face from Vertices.

Так мы создадим новое основание для дракона, закрыв все выбранные вершины и создав новую грань.



Перемещаем дракона


Затем вернёмся в Object Mode, нажав Tab, и выберем объект texturedMesh. Переместим дракона к точке начала координат сцены с помощью инструмента Move, нажав Shift + Space, G. Применим инструмент Rotate, нажав Shift + Space Bar, R, и повернём его в нужное положение.

Теперь выберем инструмент 3D Cursor и нажмём примерно посередине спины дракона. Нажмём F3, введём Set Origin и нажмём Enter. Далее выберем в появившемся списке команд Origin to 3D Cursor.

В середине 3D-курсора появится жёлтая точка, обозначающая новую точку начала координат модели.


Мы готовы экспортировать модель!

Далее нажмём File Export Wavefront (.obj).

Выберите Path Mode Copy и убедитесь, что в Export Settings выбрана опция Selection Only. Сохраните файл как Dragon.obj в удобном для вас месте.


Значение Copy параметра Path Mode экспорта гарантирует, что текстуры копируются и на них указываются ссылки из той же папки, где находится модель .obj. Это позволяет избежать проблем с потерянными текстурами при импорте в Unity.

Переносим всё в Unity


Теперь откроем проект Starter в Unity, а затем откроем сцену Assets/RW/Scenes/PhotoscanDragon. Найдём пустой пьедестал между камнями в деревне. Именно здесь мы установим отфотосканированную модель дракона.


Теперь перетащим экспортированную модель и связанные с ней экспортированные файлы в папку проекта Assets/RW/Models/Dragon.


Затем воспользуемся окном Hierarchy Unity для нахождения GameObject FocalPoint. Перетащим модель Assets/RW/Models/Dragon/Dragon.obj в сцену, и сделаем её дочерней относительно GameObject FocalPoint.


Теперь изменим позицию и поворот компонента Transform объекта Dragon следующим образом:

  • Position: (X:-33.32, Y:0.53, Z:5.08)
  • Rotation: (X:-2.229, Y:90, Z:-0.6)


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

Теперь нажмите Play и полюбуйтесь нашим собственным отфотосканированным драконом, взятым из реального мира и воссозданного в полном 3D.


Куда двигаться дальше?


Готовый проект можно скачать по ссылке в начале статьи.

Чтобы добиться результата, мы сделали множество шагов, узнав при этом о фотограмметрии, редактировании 3D-мешей в Blender, операциях с вершинами и переносе всей модели в Unity!

Я бы хотел поблагодарить The Tales Factory из Unity Asset Store за то, что позволили мне использовать ассет Photoscanned MountainsRocks PBR в сцене Dragon Viking Village.

Рекомендую изучить их превосходные фотосканированные ассеты.

Следующие действия зависят от вас. Что бы вы хотели воссоздать и встроить в свои Unity-проекты? Ниже представлены ресурсы и дополнительное чтение, которые могут вам помочь в дальнейшей работе:

Подробнее..

Из песочницы Основы компьютерной геометрии. Написание простого 3D-рендера

21.09.2020 22:05:33 | Автор: admin
Привет меня зовут Давид, а вот я собственной персоной отрендеренный своим самописным рендером:

image

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

Идея


С развитием шейдерных языков и увеличением мощностей GPU все больше людей заинтересовались программированием графики. Появились новые направления, такие как например Ray marching со стремительным ростом своей популярности.

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

Выбор языка изначально падал на c++ или rust, но я остановился на c# из-за простоты написания кода и широких возможностей для оптимизации. Итоговым продуктом данной статьи будет рендер, способный выдавать подобные картинки:

image

image

Все модели, использованные мной здесь, распространяются в открытом доступе, не занимайтесь пиратством и уважайте труд художников!

Математика


Само собой куда же писать рендеры без понимания их математических основ. В этом разделе я изложу только те концепции, которые я использовал в коде. Тем кто не уверен в своих знаниях пропускать данный раздел не советую, без понимания этих основ трудно будет понять дальнейшее изложение. Так же я рассчитываю, что тот кто решил изучать computation geometry будет иметь базовые знания в линейной алгебре, геометрии, а так же тригонометрии(углы, вектора, матрицы, скалярное произведение). Для тех кто хочет понять вычислительную геометрию глубже, могу порекомендовать книгу Е. Никулина Компьютерная геометрия и алгоритмы машинной графики.

Повороты вектора. Матрица поворота


Поворот это одно из основных линейных преобразований векторного пространства. Так же оно является еще и ортогональным преобразованием, так как сохраняет длины преобразованных векторов. В двумерном пространстве существует два типа поворотов:

  • Поворот относительно начала координат
  • Поворот относительно некоторой точки

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

Давайте выведем формулы для вращения вектора в двумерном пространстве. Обозначим координаты исходного вектора {x, y}. Координаты нового вектора, повернутого на угол f, обозначим как {x y}.

image

Мы знаем, что длина у этих векторов общая и поэтому можем использовать понятия косинуса и синуса для того, чтобы выразить эти вектора через длину и угол относительно оси OX:

image

Заметьте, что мы можем использовать формулы косинуса и синуса суммы для того, чтобы разложить значения x' и y'. Для тех, кто подзабыл я напомню эти формулы:

image

Разложив координаты повернутого вектора через них получим:

image

Здесь нетрудно заметить, что множители l * cos a и l * sin a это координаты исходного вектора: x = l * cos a, y = l * sin a. Заменим их на x и y:

image

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

image

Умножьте и проверьте что результат эквивалентен тому, что мы вывели.

Поворот в трехмерном пространстве


Мы рассмотрели поворот в двумерном пространстве, а так же вывели матрицу для него. Теперь возникает вопрос, а как получить подобные преобразования для трех измерений? В двумерном случае мы вращали вектора на плоскости, здесь же бесконечное количество плоскостей относительно которых мы можем это сделать. Однако существует три базовых типа вращений, при помощи которых можно выразить любой поворот вектора в трехмерном пространстве это XY, XZ, YZ вращения.

XY вращение.

При таком повороте мы вращаем вектор относительно оси OZ координатной системы. Представьте, что вектора это вертолётные лопасти, а ось OZ это мачта на которой они держаться. При XY вращении вектора будут поворачиваться относительно оси OZ, как лопасти вертолета относительно мачты.

image

Заметьте, что при таком вращении z координаты векторов не меняются, а меняются x и x координаты поэтому это и называется XY вращением.

image

Нетрудно вывести и формулы для такого вращения: z координата остается прежней, а x и y изменяются по тем же принципам, что и в 2д вращении.

image

То же в виде матрицы:

image

Для XZ и YZ вращений все аналогично:

image
image

Проекция


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

В том понимании которое мы используем здесь проекция на вектор это тоже вектор. Его координаты точка пересечения перпендикуляра опущенного из вектора a на b с вектором b.

image

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

image

Направление вектора проекции по определению совпадает с вектором b, значит проекция определяется формулой:

image

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

Теперь представим все через скалярное произведение:

image

Получаем удобную формулу для нахождения проекции:

image

Системы координат. Базисы


Многие привыкли работать в стандартной системе координат XYZ, в ней любые 2 оси будут перпендикулярны друг другу, а координатные оси можно представить в виде единичных векторов:

image

На деле же систем координат бесконечное множество, каждая из них является базисом. Базис n-мерного пространства является набором векторов {v1, v2 vn} через которые представляются все вектора этого пространства. При этом ни один вектор из базиса нельзя представить через другие его вектора. По сути каждый базис является отдельной системой координат, в которой вектора будут иметь свои, уникальные координаты.

Давайте разберем, что из себя представляет базис для двумерного пространства. Возьмём для примера всем знакомую декартову систему координат из векторов X {1, 0}, Y {0, 1}, которая является одним из базисов для двумерного пространства:

image


Любой вектор на плоскости можно представить в виде суммы векторов этого базиса с некими коэффициентами или же в виде линейной комбинации. Вспомните, что вы делаете когда записываете координаты вектора вы пишете x координату, а далее y. Таким образом вы на самом деле определяете коэффициенты разложения по векторам базиса.

image


Теперь возьмём другой базис:

image


Через его вектора также можно представить любой 2д вектор:

image


А вот такой набор векторов не является базисом двухмерного пространства:

image


В нем два вектора {1,1} и {2,2} лежат на одной прямой. Какие бы их комбинации вы не брали получать будете только вектора, лежащие на общей прямой y = x. Для наших целей такие дефектные не пригодятся, однако, понимать разницу, я считаю, стоит. По определению все базисы объединяет одно свойство ни один из векторов базиса нельзя представить в виде суммы других векторов базиса с коэффициентами или же ни один вектор базиса не является линейной комбинацией других. Вот пример набора из 3-х векторов который так же не является базисом:

image


Через него можно выразить любой вектор двумерной плоскости, однако вектор {1, 1} в нем является лишним так как сам может быть выражен через вектора {1, 0} и {0,1} как {1,0} + {0,1}.

Вообще любой базис n-мерного пространства будет содержать ровно n векторов, для 2д это n соответственно равно 2.

Перейдем к 3д. Трехмерный базис будет содержать в себе 3 вектора:

image


Если для двумерного базиса достаточно было двух векторов не лежащих на одной прямой, то в трехмерном пространстве набор векторов будет базисом если:

  • 1)2 вектора не лежат на одной прямой
  • 2)3-й не лежит на плоскости образованной двумя другими.


С данного момента базисы, с которыми мы работаем будут ортогональными (любые их вектора перпендикулярны) и нормированными (длина любого вектора базиса 1). Другие нам просто не понадобятся. К примеру стандартный базис

image


удовлетворяет этим критериям.

Переход в другой базис


До сих пор мы записывали разложение вектора как сумму векторов базиса с коэффициентами:

image

Снова рассмотрим стандартный базис вектор {1, 3, 6} в нем можно записать так:

image

Как видите коэффициенты разложения вектора в базисе являются его координатами в этом базисе. Разберем следующий пример:

image


Этот базис получен из стандартного применением к нему XY вращения на 45 градусов. Возьмем вектор a в стандартной системе имеющий координаты {0 ,1, 1}

image


Через вектора нового базиса его можно разложить таким образом:

image


Если вы посчитаете эту сумму, то получите {0, 1, 1} вектор а в стандартном базисе. Исходя из этого выражения в новом базисе вектор а имеет координаты {0.7, 0.7, 1} коэффициенты разложения. Это будет виднее если взглянуть с другого ракурса:

image


Но как находить эти коэффициенты? Вообще универсальный метод это решение довольно сложной системы линейных уравнений. Однако как я сказал ранее использовать мы будем только ортогональные и нормированные базисы, а для них есть весьма читерский способ. Заключается он в нахождении проекций на вектора базиса. Давайте с его помощью найдем разложение вектора a в базисе X{0.7, 0.7, 0} Y{-0.7, 0.7, 0} Z{0, 0, 1}

image


Для начала найдем коэффициент для y. Первым шагом мы находим проекцию вектора a на вектор y (как это делать я разбирал выше):

image


Второй шаг: делим длину найденной проекции на длину вектора y, тем самым мы узнаем сколько векторов y помещается в векторе проекции это число и будет коэффициентом для y, а также y координатой вектора a в новом базисе! Для x и z повторим аналогичные операции:

image


Теперь мы имеем формулы для перехода из стандартного базиса в новый:

image


Ну а так как мы используем только нормированные базисы и длины их векторов равны 1 отпадет необходимость делить на длину вектора в формуле перехода:

image


Раскроем x-координату через формулу проекции:

image


Заметьте, что знаменатель (x', x') и вектор x' в случае нормированного базиса так же равен 1 и их можно отбросить. Получим:

image


Мы видим, что координата x базисе выражается как скалярное произведение (a, x), координата y соответственно как (a, y), координата z (a, z). Теперь можно составить матрицу перехода к новым координатам:

image


Системы координат со смещенным центром


У всех систем координат которые мы рассмотрели выше началом координат была точка {0,0,0}. Помимо этого существуют еще системы со смещенной точкой начала координат:

image


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

Пишем геометрический движок. Создание проволочного рендера.



Ну вот, думаю тому кто прошел раздел с математикой и не закрыл статью можно промывать мозги более интересными вещами! В этом разделе мы начнем писать основы 3д движка и рендеринга. Вообще рендеринг это довольно сложная процедура, которая включает в себя много разных операций: отсечение невидимых граней, растеризация, расчет света, обработку различных эффектов, материалов(иногда даже физику). Все это мы частично разберем в дальнейшем, а сейчас мы займемся более простыми вещами напишем проволочный рендер. Суть его в том, что он рисует объект в виде линий, соединяющих его вершины, поэтому результат выглядит похожим на сеть из проволок:

image


Полигональная графика


Традиционно в компьютерной графике используется полигональное представление данных трехмерных объектов. Таким образом представляются данные в форматах OBJ, 3DS, FBX и многих других. В компьютере такие данные хранятся в виде двух множеств: множество вершин и множество граней(полигонов). Каждая вершина объекта представлена своей позицией в пространстве вектором, а каждая грань(полигон) представлена тройкой целых чисел которые являются индексами вершин данного объекта. Простейшие объекты(кубы, сферы и т.д.) состоят из таких полигонов и называются примитивами.

В нашем движке примитив будет основным объектом трехмерной геометрии все остальные объекты будут наследоваться от него. Опишем класс примитива:

    abstract class Primitive    {        public Vector3[] Vertices { get; protected set; }        public int[] Indexes { get; protected set; }    }

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

   public class Cube : Primitive      {        public Cube(Vector3 center, float sideLen)        {            var d = sideLen / 2;            Vertices = new Vector3[]                {                    new Vector3(center.X - d , center.Y - d, center.Z - d) ,                    new Vector3(center.X - d , center.Y - d, center.Z) ,                    new Vector3(center.X - d , center.Y , center.Z - d) ,                    new Vector3(center.X - d , center.Y , center.Z) ,                    new Vector3(center.X + d , center.Y - d, center.Z - d) ,                    new Vector3(center.X + d , center.Y - d, center.Z) ,                    new Vector3(center.X + d , center.Y + d, center.Z - d) ,                    new Vector3(center.X + d , center.Y + d, center.Z + d) ,                };            Indexes = new int[]                {                    1,2,4 ,                    1,3,4 ,                    1,2,6 ,                    1,5,6 ,                    5,6,8 ,                    5,7,8 ,                    8,4,3 ,                    8,7,3 ,                    4,2,8 ,                    2,8,6 ,                    3,1,7 ,                    1,7,5                };        }    }int Main(){        var cube = new Cube(new Vector3(0, 0, 0), 2);}

image

Реализуем системы координат


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

image

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

При работе с объектом мы будем производить операции с его вершинами в локальной системе координат, при рендеринге будем предварительно переводить все объекты сцены в единую систему координат глобальную. Добавим системы координат в код. Для этого создадим объект класса Pivot (стержень, точка опоры) который будет представлять локальный базис объекта и его центральную точку. Перевод точки в систему координат представленную Pivot будет производиться в 2 шага:

  • 1)Представление точки относительно центра новых координат
  • 2)Разложение по векторам нового базиса

Наоборот же, чтобы представить локальную вершину объекта в глобальных координатах необходимо выполнить эти действия в обратном порядке:

  • 1)Разложение по векторам глобального базиса
  • 2)Представление относительно глобального центра

Напишем класс для представления систем координат:

    public class Pivot    {        //точка центра        public Vector3 Center { get; private set; }        //вектора локального базиса - локальные координатные оси        public Vector3 XAxis { get; private set; }        public Vector3 YAxis { get; private set; }        public Vector3 ZAxis { get; private set; }        //Матрица перевода в локальные координаты        public Matrix3x3 LocalCoordsMatrix => new Matrix3x3            (                XAxis.X, YAxis.X, ZAxis.X,                XAxis.Y, YAxis.Y, ZAxis.Y,                XAxis.Z, YAxis.Z, ZAxis.Z            );        //Матрица перевода в глобальные координаты        public Matrix3x3 GlobalCoordsMatrix => new Matrix3x3            (                XAxis.X , XAxis.Y , XAxis.Z,                YAxis.X , YAxis.Y , YAxis.Z,                ZAxis.X , ZAxis.Y , ZAxis.Z            );        public Vector3 ToLocalCoords(Vector3 global)        {            //Находим позицию вектора относительно точки центра и раскладываем в локальном базисе            return LocalCoordsMatrix * (global - Center);        }        public Vector3 ToGlobalCoords(Vector3 local)        {            //В точности да наоборот - раскладываем локальный вектор в глобальном базисе и находим позицию относительно глобального центра            return (GlobalCoordsMatrix * local)  + Center;        }        public void Move(Vector3 v)        {            Center += v;        }        public void Rotate(float angle, Axis axis)        {            XAxis = XAxis.Rotate(angle, axis);            YAxis = YAxis.Rotate(angle, axis);            ZAxis = ZAxis.Rotate(angle, axis);        }    }

Теперь используя данный класс добавим в примитивы функции вращения, передвижения и увеличения:

    public abstract class Primitive    {        //Локальный базис объекта        public Pivot Pivot { get; protected set; }        //Локальные вершины        public Vector3[] LocalVertices { get; protected set; }        //Глобальные вершины        public Vector3[] GlobalVertices { get; protected set; }        //Индексы вершин        public int[] Indexes { get; protected set; }        public void Move(Vector3 v)        {            Pivot.Move(v);            for (int i = 0; i < LocalVertices.Length; i++)                GlobalVertices[i] += v;        }        public void Rotate(float angle, Axis axis)        {            Pivot.Rotate(angle , axis);            for (int i = 0; i < LocalVertices.Length; i++)                GlobalVertices[i] = Pivot.ToGlobalCoords(LocalVertices[i]);        }        public void Scale(float k)        {            for (int i = 0; i < LocalVertices.Length; i++)                LocalVertices[i] *= k;            for (int i = 0; i < LocalVertices.Length; i++)                GlobalVertices[i] = Pivot.ToGlobalCoords(LocalVertices[i]);        }    }

image

Вращение и перемещение объекта с помощью локальных координат

Рисование полигонов. Камера


Основным объектом сцены будет камера с помощью нее объекты будут рисоваться на экране. Камера, как и все объекты сцены, будет иметь локальные координаты в виде объекта класса Pivot через него мы будем двигать и вращать камеру:

image

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

image

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

image


Наоборот же, зная высоту дерева он может найти высоту точки на проекционной плоскости:

image


Теперь вернемся к нашей камере. Представьте, что к оси z координат камеры прикреплена проекционная плоскость на расстоянии z' от начала координат. Формула такой плоскости z = z', ее можно задать одним числом z'. На эту плоскость падают лучи от вершин различных объектов. Попадая на плоскость луч будет оставлять на ней точку. Соединяя такие точки можно нарисовать объект.

image


Такая плоскость будет представлять экран. Координату проекции вершины объекта на экран будем находить в 2 этапа:

  • 1)Переводим вершину в локальные координаты камеры
  • 2)Находим проекцию точки через отношение подобных треугольников

image


Проекция будет 2-мерным вектором, ее координаты x' и y' и будут определять позицию точки на экране компьютера.

Класс камеры 1
public class Camera{    //локальные координаты камеры    public Pivot Pivot { get; private set; }    //расстояние до проекционной плоскости    public float ScreenDist { get; private set; }    public Camera(Vector3 center, float screenDist)    {        Pivot = new Pivot(center);        ScreenDist = screenDist;    }    public void Move(Vector3 v)    {        Pivot.Move(v);    }    public void Rotate(float angle, Axis axis)    {        Pivot.Rotate(angle, axis);    }    public Vector2 ScreenProection(Vector3 v)    {        var local = Pivot.ToLocalCoords(v);        //через подобные треугольники находим проекцию        var delta = ScreenDist / local.Z;        var proection = new Vector2(local.X, local.Y) * delta;        return proection;    }}


Данный код имеет несколько ошибок, о исправлении которых мы поговорим далее.

Отсекаем невидимые полигоны


Спроецировав таким образом на экран три точки полигона мы получим координаты треугольника который соответствует отображению полигона на экране. Но таким образом камера будет обрабатывать любые вершины, включая те, чьи проекции выходят за область экрана, если попытаться нарисовать такую вершину велика вероятность словить ошибок. Камера так же будет обрабатывать полигоны которые находятся позади нее (координаты z их точек в локальном базисе камеры меньше z') такое затылковое зрение нам тоже ни к чему.

image


Для отсечения невидимых вершин в open gl используются метод усекающей пирамиды. Заключается он в задании двух плоскостей ближней(near plane) и дальней(far plane). Все, что лежит между этими двумя плоскостями будет подлежать дальнейшей обработке. Я же использую упрощенный вариант с одной усекающей плоскостью z'. Все вершины, лежащие позади нее будут невидимыми.

Добавим в камеру два новых поля ширину и высоту экрана.
Теперь каждую спроецированную точку будем проверять на попадание в область экрана. Так же отсечем точки позади камеры. Если точка лежит сзади или ее проекция не попадает на экран то метод вернет точку {float.NaN, float.NaN}.

Код камеры 2
public Vector2 ScreenProection(Vector3 v){    var local = Pivot.ToLocalCoords(v);    //игнорируем точки сзади камеры    if (local.Z < ScreenDist)    {        return new Vector2(float.NaN, float.NaN);    }    //через подобные треугольники находим проекцию    var delta = ScreenDist / local.Z;    var proection = new Vector2(local.X, local.Y) * delta;    //если точка принадлежит экранной области - вернем ее    if (proection.X >= 0 && proection.X < ScreenWidth && proection.Y >= 0 && proection.Y < ScreenHeight)    {        return proection;    }    return new Vector2(float.NaN, float.NaN);}


Переводим в экранные координаты


Здесь я разъясню некоторый момент. Cвязан он с тем, что во многих графических библиотеках рисование происходит в экранной системе координат, в таких координатах начало это верхняя левая точка экрана, x увеличивается при движении вправо, а y при движении вниз. В нашей проекционной плоскости точки представлены в обычных декартовых координатах и перед отрисовкой необходимо переводить эти координаты в экранные. Сделать это нетрудно, нужно только сместить начало координат в верхний левый угол и инвертировать y:

image


Код камеры 3
public Vector2 ScreenProection(Vector3 v){    var local = Pivot.ToLocalCoords(v);    //игнорируем точки сзади камеры    if (local.Z < ScreenDist)    {        return new Vector2(float.NaN, float.NaN);    }    //через подобные треугольники находим проекцию    var delta = ScreenDist / local.Z;    var proection = new Vector2(local.X, local.Y) * delta;    //этот код нужен для перевода проекции в экранные координаты    var screen = proection + new Vector2(ScreenWidth / 2, -ScreenHeight / 2);    var screenCoords = new Vector2(screen.X, -screen.Y);    //если точка принадлежит экранной области - вернем ее    if (screenCoords.X >= 0 && screenCoords.X < ScreenWidth && screenCoords.Y >= 0 && screenCoords.Y < ScreenHeight)    {        return screenCoords;    }    return new Vector2(float.NaN, float.NaN);}


Корректируем размер спроецированного изображения


Если вы используете предыдущий код для того, чтобы нарисовать объект то получите что-то вроде этого:

image


Почему то все объекты рисуются очень маленькими. Для того, чтобы понять причину вспомните как мы вычисляли проекцию умножали x и y координаты на дельту отношения z' / z. Это значит, что размер объекта на экране зависит от расстояния до проекционной плоскости z'. А ведь z' мы можем задать сколь угодно маленьким значением. Значит нам нужно корректировать размер проекции в зависимости от текущего значения z'. Для этого добавим в камеру еще одно поле угол ее обзора.

image


Он нам нужен для сопоставления углового размера экрана с его шириной. Угол будет сопоставлен с шириной экрана таким образом: максимальный угол в пределах которого смотрит камера это левый или правый край экрана. Тогда максимальный угол от оси z камеры составляет o / 2. Проекция, которая попала на правый край экрана должна иметь координату x = width / 2, а на левый: x = -width / 2. Зная это выведем формулу для нахождения коэффициента растяжения проекции:

image


Код камеры 4
public float ObserveRange { get; private set; }public float Scale => ScreenWidth / (float)(2 * ScreenDist * Math.Tan(ObserveRange / 2));public Vector2 ScreenProection(Vector3 v){    var local = Pivot.ToLocalCoords(v);    //игнорируем точки сзади камеры    if (local.Z < ScreenDist)    {        return new Vector2(float.NaN, float.NaN);    }    //через подобные треугольники находим проекцию и умножаем ее на коэффициент растяжения    var delta = ScreenDist / local.Z * Scale;    var proection = new Vector2(local.X, local.Y) * delta;    //этот код нужен для перевода проекции в экранные координаты    var screen = proection + new Vector2(ScreenWidth / 2, -ScreenHeight / 2);    var screenCoords = new Vector2(screen.X, -screen.Y);    //если точка принадлежит экранной области - вернем ее    if (screenCoords.X >= 0 && screenCoords.X < ScreenWidth && screenCoords.Y >= 0 && screenCoords.Y < ScreenHeight)    {        return screenCoords;    }    return new Vector2(float.NaN, float.NaN);}


Вот такой простой код отрисовки я использовал для теста:

Код рисования объектов
public DrawObject(Primitive primitive , Camera camera){    for (int i = 0; i < primitive.Indexes.Length; i+=3)    {        var color = randomColor();        // индексы вершин полигона        var i1 = primitive.Indexes[i];        var i2 = primitive.Indexes[i+ 1];        var i3 = primitive.Indexes[i+ 2];        // вершины полигона        var v1 = primitive.GlobalVertices[i1];        var v2 = primitive.GlobalVertices[i2];        var v3 = primitive.GlobalVertices[i3];        // рисуем полигон        DrawPolygon(v1,v2,v3 , camera , color);    }}public void DrawPolygon(Vector3 v1, Vector3 v2, Vector3 v3, Camera camera , color){    //проекции вершин    var p1 = camera.ScreenProection(v1);    var p2 = camera.ScreenProection(v2);    var p3 = camera.ScreenProection(v3);    //рисуем полигон    DrawLine(p1, p2 , color);    DrawLine(p2, p3 , color);    DrawLine(p3, p2 , color);}


Давайте проверим рендер на сцене и кубов:

image


И да, все прекрасно работает. Для тех, кому разноцветные кубики не кажутся пафосными я написал функцию парсинга моделей формата OBJ в объекты типа Primitive, залил фон черным и отрисовал несколько моделей:

Результат работы рендера

image

image


Растеризация полигонов. Наводим красоту.



В прошлом разделе мы написали проволочный рендер. Теперь мы займемся его модернизацией реализуем растеризацию полигонов.

По простому растеризировать полигон это значит закрасить его. Казалось бы зачем писать велосипед, когда есть уже готовые функции растеризации треугольника. Вот что будет если нарисовать все дефолтными инструментами:

image


Современное искусство, полигоны сзади нарисовались поверх передних, одним словом каша. К тому же как таким образом текстурировать объекты? Да, никак. Значит нам нужно написать свой имба-растерайзер, который будет уметь в отсечение невидимых точек, текстуры и даже в шейдеры! Но для того чтобы это сделать стоит понять как вообще красить треугольники.

Алгоритм Брезенхема для рисования линии.


Начнем с линий. Если кто не знал алгоритм Брезенхема это основной алгоритм рисования прямых в компьютерной графике. Он или его модификации используется буквально везде: рисование прямых, отрезков, окружностей и т.п. Кому интересно более подробное описание читайте вики. Алгоритм Брезенхема

Имеется отрезок соединяющий точки {x1, y1} и {x2, y2}. Чтобы нарисовать отрезок между ними нужно закрасить все пиксели которые попадают на него. Для двух точек отрезка можно найти x-координаты пикселей в которых они лежат: нужно лишь взять целые части от координат x1 и x2. Чтобы закрасить пиксели на отрезке запускаем цикл от x1 до x2 и на каждой итерации вычисляем y координату пикселя который попадает на прямую. Вот код:

void Brezenkhem(Vector2 p1 , Vector2 p2){    int x1 = Floor(p1.X);    int x2 = Floor(p2.X);    if (x1 > x2) {Swap(x1, x2); Swap(p1 , p2);}    float d = (p2.Y - p1.Y) / (x2 - x1);    float y = p1.Y;    for (int i = x1; i <= x2; i++)    {        int pixelY = Floor(y);        FillPixel(i , pixelY);        y += d;    }}

image

Картинка из вики

Растеризация треугольника. Алгоритм заливки


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

image


Заметьте теперь у нас есть две части в которых явно выражены нижняя и верхняя границы. все что осталось это залить все пиксели находящиеся между ними! Сделать это можно в 2 цикла: от x1 до x2 и от x3 до x2.

void Triangle(Vector2 v1 , Vector2 v2 , Vector2 v3){    //хардкодим BubbleSort для упорядочивания по x    if (v1.X > v2.X) { Swap(v1, v2); }    if (v2.X > v3.X) { Swap(v2, v3); }    if (v1.X > v2.X) { Swap(v1, v2); }    //узнаем на сколько увеличивается y границ при увеличении x    //избегаем деления на 0: если x1 == x2 значит эта часть треугольника - линия    var steps12 = max(v2.X - v1.X , 1);    var steps13 = max(v3.X - v1.X , 1);    var upDelta = (v2.Y - v1.Y) / steps12;    var downDelta = (v3.Y - v1.Y) / steps13;    //верхняя граница должна быть выше нижней    if (upDelta < downDelta) Swap(upDelta , downDelta);    //изначально у координаты границ равны y1    var up = v1.Y;    var down = v1.Y;    for (int i = (int)v1.X; i <= (int)v2.X; i++)    {        for (int g = (int)down; g <= (int)up; g++)        {            FillPixel(i , g);        }        up += upDelta;        down += downDelta;    }    //все то же самое для другой части треугольника    var steps32 = max(v2.X - v3.X , 1);    var steps31 = max(v1.X - v3.X , 1);    upDelta = (v2.Y - v3.Y) / steps32;    downDelta = (v1.Y - v3.Y) / steps31;    if (upDelta < downDelta) Swap(upDelta, downDelta);    up = v3.Y;    down = v3.Y;    for (int i = (int)v3.X; i >=(int)v2.X; i--)    {        for (int g = (int)down; g <= (int)up; g++)        {            FillPixel(i, g);        }        up += upDelta;        down += downDelta;    }}

Несомненно этот код можно отрефакторить и не дублировать цикл:

void Triangle(Vector2 v1 , Vector2 v2 , Vector2 v3){    if (v1.X > v2.X) { Swap(v1, v2); }    if (v2.X > v3.X) { Swap(v2, v3); }    if (v1.X > v2.X) { Swap(v1, v2); }    var steps12 = max(v2.X - v1.X , 1);    var steps13 = max(v3.X - v1.X , 1);    var steps32 = max(v2.X - v3.X , 1);    var steps31 = max(v1.X - v3.X , 1);    var upDelta = (v2.Y - v1.Y) / steps12;    var downDelta = (v3.Y - v1.Y) / steps13;    if (upDelta < downDelta) Swap(upDelta , downDelta);    TrianglePart(v1.X , v2.X , v1.Y , upDelta , downDelta);    upDelta = (v2.Y - v3.Y) / steps32;    downDelta = (v1.Y - v3.Y) / steps31;    if (upDelta < downDelta) Swap(upDelta, downDelta);    TrianglePart(v3.X, v2.X, v3.Y, upDelta, downDelta);}void TrianglePart(float x1 , float x2 , float y1  , float upDelta , float downDelta){    float up = y1, down = y1;    for (int i = (int)x1; i <= (int)x2; i++)    {        for (int g = (int)down; g <= (int)up; g++)        {            FillPixel(i , g);        }        up += upDelta; down += downDelta;    }}

Отсечение невидимых точек.


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

image


Для того, чтобы понять видима точки или нет, в рендеринге применяют механизм zbuffer-а(буфера глубины). zbuffer можно представить как двумерный массив (можно сжать в одномерный) с размерностью width * height. Для каждого пикселя на экране он хранит значение z координаты на исходном полигоне откуда эта точка была спроецирована. Соответственно чем ближе точка к наблюдателю, тем меньше ее z координата. В конечном итоге если проекции нескольких точек совпадают растеризировать нужно точку с минимальной z координатой:

image


Теперь возникает вопрос как находить z-координаты точек на исходном полигоне? Это можно сделать несколькими способами. Например можно пускать луч из начала координат камеры, проходящий через точку на проекционной плоскости {x, y, z'}, и находить его пересечение с полигоном. Но искать пересечения крайне затратная операция, поэтому будем использовать другой способ. Для рисования треугольника мы интерполировали координаты его проекций, теперь, помимо этого, мы будем интерполировать также и координаты исходного полигона. Для отсечения невидимых точек будем использовать в методе растеризации состояние zbuffer-а для текущего фрейма.

Мой zbuffer будет иметь вид Vector3[] он будет содержать не только z координаты, но и интерполированные значения точек полигона(фрагменты) для каждого пикселя экрана. Это сделано в целях экономии памяти так как в дальнейшем нам все равно пригодятся эти значения для написания шейдеров! А пока что имеем следующий код для определения видимых вершин(фрагментов):

Код
public void ComputePoly(Vector3 v1, Vector3 v2, Vector3 v3 , Vector3[] zbuffer){    //находим проекцию полигона    var v1p = Camera.ScreenProection(v1);    var v2p = Camera.ScreenProection(v2);    var v3p = Camera.ScreenProection(v3);    //упорядочиваем точки по x - координате    //Заметьте, также меняем исходные точки - они должны соответствовать проекциям    if (v1p.X > v2p.X) { Swap(v1p, v2p); Swap(v1p, v2p); }    if (v2p.X > v3p.X) { Swap(v2p, v3p); Swap(v2p, v3p); }    if (v1p.X > v2p.X) { Swap(v1p, v2p); Swap(v1p, v2p); }    //считаем количество шагов для построения линии алгоритмом Брезенхема    int x12 = Math.Max((int)v2p.X - (int)v1p.X, 1);    int x13 = Math.Max((int)v3p.X - (int)v1p.X, 1);    //теперь помимо проекций будем интерполировать и исходные точки    float dy12 = (v2p.Y - v1p.Y) / x12; var dr12 = (v2 - v1) / x12;    float dy13 = (v3p.Y - v1p.Y) / x13; var dr13 = (v3 - v1) / x13;    Vector3 deltaUp, deltaDown; float deltaUpY, deltaDownY;    if (dy12 > dy13) { deltaUp = dr12; deltaDown = dr13; deltaUpY = dy12; deltaDownY = dy13;}    else { deltaUp = dr13; deltaDown = dr12; deltaUpY = dy13; deltaDownY = dy12;}    TrianglePart(v1 , deltaUp , deltaDown , x12 , 1 , v1p , deltaUpY , deltaDownY , zbuffer);    //вторую часть треугольника аналогично - думаю вы поняли}public void ComputePolyPart(Vector3 start, Vector3 deltaUp, Vector3 deltaDown,    int xSteps, int xDir, Vector2 pixelStart, float deltaUpPixel, float deltaDownPixel , Vector3[] zbuffer){    int pixelStartX = (int)pixelStart.X;    Vector3 up = start - deltaUp, down = start - deltaDown;    float pixelUp = pixelStart.Y - deltaUpPixel, pixelDown = pixelStart.Y - deltaDownPixel;    for (int i = 0; i <= xSteps; i++)    {        up += deltaUp; pixelUp += deltaUpPixel;        down += deltaDown; pixelDown += deltaDownPixel;        int steps = ((int)pixelUp - (int)pixelDown);        var delta = steps == 0 ? Vector3.Zero : (up - down) / steps;        Vector3 position = down - delta;        for (int g = 0; g <= steps; g++)        {            position += delta;            var proection = new Point(pixelStartX + i * xDir, (int)pixelDown + g);            int index = proection.Y * Width + proection.X;            //проверка на глубину            if (zbuffer[index].Z == 0 || zbuffer[index].Z > position.Z)            {                zbuffer[index] = position;            }        }    }}


image

Анимация шагов растеризатора(при перезаписи глубины в zbuffer-е пиксель выделяется красным):

Для удобства я вынес весь код в отдельный модуль Rasterizer:

Класс растеризатора
    public class Rasterizer    {        public Vertex[] ZBuffer;        public int[] VisibleIndexes;        public int VisibleCount;        public int Width;        public int Height;        public Camera Camera;        public Rasterizer(Camera camera)        {            Shaders = shaders;            Width = camera.ScreenWidth;            Height = camera.ScreenHeight;            Camera = camera;        }        public Bitmap Rasterize(IEnumerable<Primitive> primitives)        {            var buffer = new Bitmap(Width , Height);            ComputeVisibleVertices(primitives);            for (int i = 0; i < VisibleCount; i++)            {                var vec = ZBuffer[index];                var proec = Camera.ScreenProection(vec);                buffer.SetPixel(proec.X , proec.Y);            }            return buffer.Bitmap;        }        public void ComputeVisibleVertices(IEnumerable<Primitive> primitives)        {            VisibleCount = 0;            VisibleIndexes = new int[Width * Height];            ZBuffer = new Vertex[Width * Height];            foreach (var prim in primitives)            {                foreach (var poly in prim.GetPolys())                {                    MakeLocal(poly);                    ComputePoly(poly.Item1, poly.Item2, poly.Item3);                }            }        }        public void MakeLocal(Poly poly)        {            poly.Item1.Position = Camera.Pivot.ToLocalCoords(poly.Item1.Position);            poly.Item2.Position = Camera.Pivot.ToLocalCoords(poly.Item2.Position);            poly.Item3.Position = Camera.Pivot.ToLocalCoords(poly.Item3.Position);        }    }


Теперь проверим работу рендера. Для этого я использую модель Сильваны из известной RPG WOW:

image


Не очень понятно, правда? А все потому что здесь нет ни текстур ни освещения. Но вскоре мы это исправим.

Текстуры! Нормали! Освещение! Мотор!


Почему я объединил все это в один раздел? А потому что по своей сути текстуризация и расчет нормалей абсолютно идентичны и скоро вы это поймете.

Для начала рассмотрим задачу текстуризации для одного полигона. Теперь помимо обычных координат вершин полигона мы будем хранить еще и его текстурные координаты. Текстурная координата вершины представляется двумерным вектором и указывает на пиксель изображения текстуры. В интернете я нашел хорошую картинку чтобы показать это:

image

Заметьте, что начало текстуры (левый нижний пиксель) в текстурных координатах имеет значение {0, 0}, конец (правый верхний пиксель) {1, 1}. Учитывайте систему координат текстуры и возможность выхода за границы картинки когда текстурная координата равна 1.

Сразу создадим класс для представления данных вершины:

  public class Vertex    {        public Vector3 Position { get; set; }        public Color Color { get; set; }        public Vector2 TextureCoord { get; set; }        public Vector3 Normal { get; set; }        public Vertex(Vector3 pos , Color color , Vector2 texCoord , Vector3 normal)        {            Position = pos;            Color = color;            TextureCoord = texCoord;            Normal = normal;        }    }

Зачем нужны нормали я объясню позже, пока что просто будем знать, что у вершин они могут быть. Теперь для текстуризации полигона нам необходимо каким-то образом сопоставить значение цвета из текстуры конкретному пикселю. Помните как мы интерполировали вершины? Здесь нужно сделать то же самое! Я не буду еще раз переписывать код растеризации, а предлагаю вам самим реализовать текстурирование в вашем рендере. Результатом должно быть корректное отображение текстур на модели. Вот, что получилось у меня:

текстурированная модель
image


Вся информация о текстурных координатах модели находятся в файле OBJ. Для того, чтобы использовать это изучите формат: Формат OBJ.

Освещение



С текстурами все стало гораздо веселее, но по настоящему весело будет когда мы реализуем освещение для сцены. Для имитации дешевого освещения я буду использовать модель Фонга.

Модель Фонга


В общем случае этот метод имитирует наличие 3х составляющих освещения: фоновая(ambient), рассеянная(diffuse) и зеркальная(reflect). Сумма этих трех компонент в итоге даст имитацию физического поведения света.

image

Модель Фонга

Для расчета освещения по Фонгу нам будут нужны нормали к поверхностям, для этого я и добавил их в классе Vertex. Где же брать значения этих нормалей? Нет, ничего вычислять нам не нужно. Дело в том, что великодушные 3д редакторы часто сами считают их и предоставляют вместе с данными модели в контексте формата OBJ. Распарсив файл модели мы получаем значение нормалей для 3х вершин каждого полигона.

image

Картинка из вики

Для того, чтобы посчитать нормаль в каждой точке на полигоне нужно интерполировать эти значения, мы уже умеем делать это. Теперь разберем все составляющие для вычисления освещения Фонга.

Фоновый свет (Ambient)


Изначально мы задаем постоянное фоновое освещение, для нетекстурированных объектов можно выбрать любой цвет, для объектов с текстурами я делю каждую из компонент RGB на некий коэффициент базового затенения (baseShading).

Рассеянный свет (Diffuse)


Когда свет падает на поверхность полигона он равномерно рассеивается. Для расчета diffuse значения на конкретном пикселе учитывается угол под которым свет падает на поверхность. Чтобы рассчитать этот угол можно применить скалярное произведение падающего луча и нормали(само собой вектора перед этим нужно нормализировать). Этот угол будет умножаться на некий коэффициент интенсивности света. Если скалярное произведение отрицательно это значит, что угол между векторами больше 90 градусов. В этом случае мы начнем рассчитывать уже не осветление, а, наоборот, затенение. Стоит избегать этого момента, сделать это можно с помощью функции max.

Код
public interface IShader    {        void ComputeShader(Vertex vertex, Camera camera);    }    public struct Light    {        public Vector3 Pos;        public float Intensivity;    }public class PhongModelShader : IShader    {        public static float DiffuseCoef = 0.1f;        public Light[] Lights { get; set; }        public PhongModelShader(params Light[] lights)        {            Lights = lights;        }        public void ComputeShader(Vertex vertex, Camera camera)        {            if (vertex.Normal.X == 0 && vertex.Normal.Y == 0 && vertex.Normal.Z == 0)            {                return;            }            var gPos = camera.Pivot.ToGlobalCoords(vertex.Position);            foreach (var light in Lights)            {                var ldir = Vector3.Normalize(light.Pos - gPos);                var diffuseVal = Math.Max(VectorMath.Cross(ldir, vertex.Normal), 0) * light.Intensivity;                vertex.Color = Color.FromArgb(vertex.Color.A,                    (int)Math.Min(255, vertex.Color.R * diffuseVal * DiffuseCoef),                    (int)Math.Min(255, vertex.Color.G * diffuseVal * DiffuseCoef,                    (int)Math.Min(255, vertex.Color.B * diffuseVal * DiffuseCoef));            }        }    }


Давайте применим рассеянный свет и рассеем тьму:

image

Зеркальный свет (Reflect)


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

image

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

код
    public class PhongModelShader : IShader    {        public static float DiffuseCoef = 0.1f;        public static float ReflectCoef = 0.2f;        public Light[] Lights { get; set; }        public PhongModelShader(params Light[] lights)        {            Lights = lights;        }        public void ComputeShader(Vertex vertex, Camera camera)        {            if (vertex.Normal.X == 0 && vertex.Normal.Y == 0 && vertex.Normal.Z == 0)            {                return;            }            var gPos = camera.Pivot.ToGlobalCoords(vertex.Position);            foreach (var light in Lights)            {                var ldir = Vector3.Normalize(light.Pos - gPos);                //Следующие три строчки нужны чтобы найти отраженный от поверхности луч                var proection = VectorMath.Proection(ldir, -vertex.Normal);                var d = ldir - proection;                var reflect = proection - d;                var diffuseVal = Math.Max(VectorMath.Cross(ldir, -vertex.Normal), 0) * light.Intensivity;                //луч от наблюдателя                var eye = Vector3.Normalize(-vertex.Position);                var reflectVal = Math.Max(VectorMath.Cross(reflect, eye), 0) * light.Intensivity;                var total = diffuseVal * DiffuseCoef + reflectVal * ReflectCoef;                vertex.Color = Color.FromArgb(vertex.Color.A,                    (int)Math.Min(255, vertex.Color.R * total),                    (int)Math.Min(255, vertex.Color.G * total),                    (int)Math.Min(255, vertex.Color.B * total));            }        }    }


Теперь картинка выглядит следующим образом:

image


Тени


Конечной точкой моего изложения будет реализация теней для рендера. Первая тупиковая идея которая зародилась у меня в черепушке для каждой точки проверять не лежит ли между ней и светом какой-нибудь полигон. Если лежит значит не нужно освещать пиксель. Модель Сильваны содержит 220к с лихвой полигонов. Если так для каждой точки проверять пересечение со всеми этими полигонами, то нужно сделать максимум 220000 * 1920 * 1080 * 219999 вызовов метода пересечения! За 10 минут мой компьютер смог осилить 10-у часть всех вычислений (2600 полигонов из 220000), после чего у меня случился сдвиг и я отправился на поиски нового метода.

В интернете мне попался очень простой и красивый способ, который выполняет те же вычисления в тысячи раз быстрее. Называется он Shadow mapping(построение карты теней). Вспомните как мы определяли видимые наблюдателю точки использовали zbuffer. Shadow mapping делает тоже самое! В первом проходе наша камера будет находиться в позиции света и смотреть на объект. Таким образом мы сформируем карту глубин для источника света. Карта глубин это знакомый нам zbuffer. Во втором проходе мы используем эту карту, чтобы определять вершины которые должны освещаться. Сейчас я нарушу правила хорошего кода и пойду читерским путем просто передам шейдеру новый объект растеризатора и он используя его создаст нам карту глубин.

Код
public class ShadowMappingShader : IShader{    public Enviroment Enviroment { get; set; }    public Rasterizer Rasterizer { get; set; }    public Camera Camera => Rasterizer.Camera;    public Pivot Pivot => Camera.Pivot;    public Vertex[] ZBuffer => Rasterizer.ZBuffer;    public float LightIntensivity { get; set; }    public ShadowMappingShader(Enviroment enviroment, Rasterizer rasterizer, float lightIntensivity)    {        Enviroment = enviroment;        LightIntensivity = lightIntensivity;        Rasterizer = rasterizer;        //я добвил события в объекты рендера, привязав к ним перерасчет карты теней        //теперь при вращении/движении камеры либо при изменение сцены шейдер будет перезаписывать глубину        Camera.OnRotate += () => UpdateDepthMap(Enviroment.Primitives);        Camera.OnMove += () => UpdateDepthMap(Enviroment.Primitives);        Enviroment.OnChange += () => UpdateDepthMap(Enviroment.Primitives);        UpdateVisible(Enviroment.Primitives);    }    public void ComputeShader(Vertex vertex, Camera camera)    {        //вычисляем глобальные координаты вершины        var gPos = camera.Pivot.ToGlobalCoords(vertex.Position);        //дистанция до света        var lghDir = Pivot.Center - gPos;        var distance = lghDir.Length();        var local = Pivot.ToLocalCoords(gPos);        var proectToLight = Camera.ScreenProection(local).ToPoint();        if (proectToLight.X >= 0 && proectToLight.X < Camera.ScreenWidth && proectToLight.Y >= 0            && proectToLight.Y < Camera.ScreenHeight)        {            int index = proectToLight.Y * Camera.ScreenWidth + proectToLight.X;            if (ZBuffer[index] == null || ZBuffer[index].Position.Z >= local.Z)            {                vertex.Color = Color.FromArgb(vertex.Color.A,                    (int)Math.Min(255, vertex.Color.R + LightIntensivity / distance),                    (int)Math.Min(255, vertex.Color.G + LightIntensivity / distance),                    (int)Math.Min(255, vertex.Color.B + LightIntensivity / distance));            }        }        else        {            vertex.Color = Color.FromArgb(vertex.Color.A,                    (int)Math.Min(255, vertex.Color.R + (LightIntensivity / distance) / 15),                    (int)Math.Min(255, vertex.Color.G + (LightIntensivity / distance) / 15),                    (int)Math.Min(255, vertex.Color.B + (LightIntensivity / distance) / 15));        }    }    public void UpdateDepthMap(IEnumerable<Primitive> primitives)    {        Rasterizer.ComputeVisibleVertices(primitives);    }}


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

image


Многие из вас наверное заметили артефакты данного шейдера(необработанные светом черные точки). Опять же обратившись в всезнающую сеть я нашел описание этого эффекта с противным названием shadow acne(да простят меня люди с комплексом внешности). Суть таких зазоров заключается в том, что для определения тени мы используем ограниченное разрешение карты глубин. Это значит, что несколько вершин при рендеринге получают одно значение из карты глубин. Такому артефакту наиболее подвержены поверхности на которые свет падает под пологим углом. Эффект можно исправить, увеличив разрешение рендера для света, однако существует более элегантный способ. Заключается он в том, чтобы добавлять определенный сдвиг для глубины в зависимости от угла между лучом света и поверхностью. Это можно сделать при помощи скалярного произведения.

Улучшенные тени
public class ShadowMappingShader : IShader{    public Enviroment Enviroment { get; set; }    public Rasterizer Rasterizer { get; set; }    public Camera Camera => Rasterizer.Camera;    public Pivot Pivot => Camera.Pivot;    public Vertex[] ZBuffer => Rasterizer.ZBuffer;    public float LightIntensivity { get; set; }    public ShadowMappingShader(Enviroment enviroment, Rasterizer rasterizer, float lightIntensivity)    {        Enviroment = enviroment;        LightIntensivity = lightIntensivity;        Rasterizer = rasterizer;        //я добвил события в объекты рендера, привязав к ним перерасчет карты теней        //теперь при вращении/движении камеры либо при изменение сцены шейдер будет перезаписывать глубину        Camera.OnRotate += () => UpdateDepthMap(Enviroment.Primitives);        Camera.OnMove += () => UpdateDepthMap(Enviroment.Primitives);        Enviroment.OnChange += () => UpdateDepthMap(Enviroment.Primitives);        UpdateVisible(Enviroment.Primitives);    }    public void ComputeShader(Vertex vertex, Camera camera)    {        //вычисляем глобальные координаты вершины        var gPos = camera.Pivot.ToGlobalCoords(vertex.Position);        //дистанция до света        var lghDir = Pivot.Center - gPos;        var distance = lghDir.Length();        var local = Pivot.ToLocalCoords(gPos);        var proectToLight = Camera.ScreenProection(local).ToPoint();        if (proectToLight.X >= 0 && proectToLight.X < Camera.ScreenWidth && proectToLight.Y >= 0            && proectToLight.Y < Camera.ScreenHeight)        {            int index = proectToLight.Y * Camera.ScreenWidth + proectToLight.X;            var n = Vector3.Normalize(vertex.Normal);            var ld = Vector3.Normalize(lghDir);            //вычисляем сдвиг глубины            float bias = (float)Math.Max(10 * (1.0 - VectorMath.Cross(n, ld)), 0.05);            if (ZBuffer[index] == null || ZBuffer[index].Position.Z + bias >= local.Z)            {                vertex.Color = Color.FromArgb(vertex.Color.A,                    (int)Math.Min(255, vertex.Color.R + LightIntensivity / distance),                    (int)Math.Min(255, vertex.Color.G + LightIntensivity / distance),                    (int)Math.Min(255, vertex.Color.B + LightIntensivity / distance));            }        }        else        {            vertex.Color = Color.FromArgb(vertex.Color.A,                    (int)Math.Min(255, vertex.Color.R + (LightIntensivity / distance) / 15),                    (int)Math.Min(255, vertex.Color.G + (LightIntensivity / distance) / 15),                    (int)Math.Min(255, vertex.Color.B + (LightIntensivity / distance) / 15));        }    }    public void UpdateDepthMap(IEnumerable<Primitive> primitives)    {        Rasterizer.ComputeVisibleVertices(primitives);    }}

image


Бонус

Играем с нормалями


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

image


Двигаем свет


Реализовав простой цикл получим набор отрендеренных картинок с разными позициями света на сцене:

            float angle = (float)Math.PI / 90;            var shader = (preparer.Shaders[0] as PhongModelShader);            for (int i = 0; i < 180; i+=2)            {                shader.Lights[0] = = new Light()                    {                        Pos = shader.Lights[0].Pos.Rotate(angle , Axis.X) ,                        Intensivity = shader.Lights[0].Intensivity                    };                Draw();            }

image

Производительность


Для теста использовалась следующие конфигурации:

  • Модель Сильваны: 220к полигонов.
  • Разрешение экрана: 1920x1080.
  • Шейдеры: Phong model shader
  • Конфигурация компьютера: cpu core i7 4790, 8 gb ram

FPS рендеринга составлял 1-2 кадр/сек. Это далеко не realtime. Однако стоит все же учитывать, что вся обработка происходила без использования многопоточности, т.е. на одном ядре cpu.

Заключение


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

Морской бой в PostgreSQL

22.09.2020 12:22:19 | Автор: admin

Программисты ведут ожесточенные споры о вреде и пользе хранимых процедур в базах данных. Сегодня мы отвлечемся от них и снова сделаем невероятное в невозможных условиях.

Сегодня разработчики по возможности стараются не выстраивать бизнес-логику в базах данных. Тем не менее, находятся энтузиасты, которые бросают себе вызов и создают, например, матчер биржи, а иногда целые компании переводят серверную часть на хранимые процедуры БД. Авторы таких проектов утверждают, что на базах данных можно сделать все, что угодно, если захотеть.

Тут невольно вспоминается морской бой по BGP. Возможно ли сделать эту игру на SQL? Для ответа на этот вопрос мы воспользуемся услугами PostgreSQL 12, а также языком PLpgSQL. Для тех, кому не терпится посмотреть под капот, ссылка на репозиторий.

Игра морской бой требует постоянного ввода информации от пользователя на протяжении всей игры. Самый простой способ взаимодействия базы данных с пользователем клиент командной строки.

Ввод данных


Получение данных от пользователя наиболее сложная задача в данном проекте. Самый простой с точки зрения разработки способ просить пользователя писать корректные SQL-запросы для вставки необходимой информации в специально подготовленную таблицу. Данный способ относительно медленный и требует от пользователя повторения запроса раз за разом. Хочется иметь возможность забирать данные без написания SQL-запроса.

PostgreSQL предлагает использовать COPY FROM STDIN для сохранения данных из стандартного потока ввода в таблицу. Но у этого решения есть два недостатка.

Во-первых, оператор COPY нельзя ограничить по количеству загружаемой информации. Оператор COPY завершает свое выполнение только при получении признака конца файла. Таким образом, пользователю дополнительно придется вводить EOF для обозначения завершения ввода информации.

Во-вторых, в хранимых процедурах и функциях нет файлов stdin и stdout. Стандартные потоки ввода и вывода доступы при выполнении обычных SQL-запросов через клиент, но там недоступны циклы. Таким образом, нельзя запустить игру в одну SQL-команду. Это могло бы стать концом истории, однако нашлось хитрое решение.

В PostgreSQL есть возможность логировать все запросы, в том числе некорректные. Более того, логирование может быть в формате CSV, а оператор COPY умеет работать с этим форматом. Настроим логирование в файле конфигурации postgresql.conf:

log_destination = 'csvlog'logging_collector = onlog_directory = 'pg_log'log_filename = 'postgresql.log'log_min_error_statement = errorlog_statement = 'all'

Теперь в файле postgresql.csv будут записаны все SQL-запросы, которые выполняются в PostgreSQL. В документации, в разделе Using CSV-Format Log Output, описан способ подгрузки csv-логов при включенной ротации. Нас же интересует подгрузка логов с интервалом в одну секунду.

Так как проводить ротацию логов каждую секунду нецелесообразно, то будем раз за разом загружать файл логов, дополняя таблицу с логами. Решение в лоб из одного оператора COPY сработает только первый раз, а далее будет выводить ошибку из-за конфликтов первичных ключей. Данная проблема решается использованием промежуточной таблицы и предложения ON CONFLICT DO NOTHING.

Загрузка логов в таблицу
CREATE TEMP TABLE tmp_table ON COMMIT DROPAS SELECT * FROM postgres_log WITH NO DATA;COPY tmp_table FROM '/var/lib/postgresql/data/pg_log/postgresql.csv' WITH csv;INSERT INTO postgres_logSELECT * FROM tmp_table WHERE query is not null AND command_tag = 'idle' ON CONFLICT DO NOTHING;

Можно также добавить фильтр при переносе данных из временной таблицы в postgres_log, сократив количество ненужной информации в таблице логов. Так как мы не планируем получать от пользователя корректные SQL-запросы, то можем ограничиться запросами, где есть текст запроса и тег команды равен idle.

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

Теперь любая введенная пользователем строка, не являющаяся корректным SQL-запросом, появится в таблице postgres_log. Хотя этот способ требует обязательного введения разделителя точки с запятой, это гораздо проще, чем отправка EOF.

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

Клиент-экран (слева) и клиент-клавиатура (справа)
Для сопряжения клавиатуры экран генерирует псевдослучайную последовательность символов, которую необходимо ввести на клиенте-клавиатуре. Экран идентифицирует клавиатуру по уникальному идентификатору сессии клиента (session_id) и далее выбирает из таблицы логов только строки с нужным идентификатором сессии.

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

psql <<<'select keyboard_init()' & psql >/dev/null 2>&1

Теперь у нас есть возможность вводить в базу данных информацию со стандартного потока ввода и использовать хранимые процедуры.

Игровой цикл


Активная часть игры
Игра условно разделена на следующие фазы:

  • сопряжение клиента-экрана с клиентом-клавиатурой;
  • создание лобби или подключение к существующему;
  • расстановка кораблей;
  • активная часть игры.

Игра состоит из пяти таблиц:

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

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

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

Различия в разработке тоже есть, и они заставляют мозг закипать.

Время коммитов


Вся логика игры запускается клиентом-экраном, то есть от начала до завершения выполняется одна процедура. Причем за одну транзакцию, если явно не указан оператор COMMIT.

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

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

Запуск игры


Начало игры
Запускать такую игру в реальном окружении мы не рекомендуем. К счастью, есть возможность быстро и без сложностей развернуть базу данных с игрой. В репозитории можно найти Dockerfile, который соберет образ с PostgreSQL 12.4 и необходимой конфигурацией. Сборка и запуск образа:

docker build -t sql-battleships .docker run -p 5432:5432 sql-battleships

Подключение к БД в образе:

psql -U postgres <<<'call screen_loop()' & psql -U postgres

Обратите внимание, что PostgreSQL в контейнере использует политику аутентификации trust, то есть разрешает все подключения без пароля. Не забывайте отключать контейнер после завершения всех партий!

Заключение


Использование специальных инструментов не по назначению часто вызывает негатив со стороны профессионалов. Однако решение бессмысленных, но интересных задач тренирует нестандартное мышление и позволяет изучить инструмент с разных точек зрения в поиске подходящего решения.

Сегодня мы лишний раз подтвердили, что на SQL при желании можно написать все, что угодно. Тем не менее, мы рекомендуем в продакшене использовать инструменты по назначению, а такие забавы делать исключительно как маленькие домашние проекты.

Подробнее..

История разработки The Light Remake. Часть 1

07.09.2020 20:15:27 | Автор: admin


Приветствую, читатель! Меня зовут Сергей, я являюсь инди-разработчиком компьютерных игр. В моем портфолио имеется уже несколько инди-проектов, часть из которых была самостоятельно выпущена в Steam. Игра Свет или The Light, вышедшая в 2012 году, была моей первой пробой пера, открывшей путь в мир игровой разработки. Проект распространялся бесплатно, но реакция публики и отзывы игроков подарили мне серьезную мотивацию для дальнейшей работы. The Light стал для меня чем-то вроде философской притчи о человечестве и его судьбе. Сюжет абстрактен и не преследует каких-то конкретных целей, это лишь возможность поразмышлять на обширную тему.

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




О чем речь?


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

Часть 1.


Первоначальная версия игры в уже далеком 2012 году была собрана на Unity 4.2. Тогда в моем информационном поле еще не было разговоров о PBR материалах, рефлекшн пробах и других актуальных сейчас методах. Базовые шейдеры в Unity были весьма простыми и не блистали особой реалистичностью. Конечно, можно было дописать множество дополнительных элементов, вроде отражений по френелю, но тогда шейдерное программирование для меня было тайной за семью печатями. Основным используемым шейдером был Normal Bumped Specular и AlphaTest diffuse для объектов с прозрачностью (листья деревьев, фигурные металлические решетки).

Рилтайм тени отсутствовали, почти все освещение было запечено в текстуру и разрешение этих теней местами было очень низким. Запекание света ко всему прочему свело практически к нулю эффект рельефа от Bumped Specular шейдера, в результате чего при ближайшем рассмотрении объекты больше походили на плоский картон.

Картинку в значимой степени преображали постэффекты на камере, в частности Bloom и Color Correction. Они добавляли красок и разнообразия, хотя и были местами слишком навязчивыми.



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

Спустя 7 лет на любое свое творение глядишь уже другими глазами. Технологии сильно ушли вперед, изменился инструментарий, последние версии движка Unity значительно отличаются от предшественников. В связи со всем сказанным выше перенос проекта с 4 версии на версию 2017 (решил остановится на ней в силу ряда причин) является достаточно долгой и кропотливой работой. К тому же, учитывая тот факт, что первоначальный проект и игрой полноценной называться не мог (большинство действий в игре осуществлялись с помощью одного скрипта с функцией триггера), нужно было с нуля писать всю логику, взаимодействие с объектами, инвентарь, систему меню, систему сохранений, достижений, настройки и т.д. В общем, меня ждал весьма масштабный объем работы!

Начало. Шейдеры и свет


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



Новый Standart
Было принято решение начать с замены старых шейдеров на новый PBR Standart. В предыдущих статьях на тему разработки 35ММ я уже упоминал (ссылка) о новом типе PBR шейдеров (Physically Based Rendering), что подразумевает физический правильный рендеринг. Новый материал Standart уже не имеет привычного из прошлых версий o.Gloss и o.Specular, здесь у нас карта металличности (o.Metallic) и Smoothness. Также, имеется больше слотов для текстур различной категории. К примеру, у нас появляется возможность добавить карту Occlusion для мягких затенений. Эффект очень полезный, поскольку позволяет подчеркнуть объем и затенения в тех местах модели, куда света попадает меньше. Без этой карты и лайтмап текстура объекты смотрятся плоско и нереалистично.



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



Растительность
После замены основных материалов я перешел к растительности. В оригинальной сборке игры был использован стандартный Alpha Test шейдер без спекуляра и Normal Map. Конечно же, данное положение вещей в 2019 году меня не устраивало. Можно было купить или найти на просторах интернета готовое решение, есть целые паки с шейдерами и готовыми моделями со множеством фитч, имитацией колыхания на ветру и т.д. Но уже традиционно я в таких вопросах пытаюсь разобраться сам и экспериментирую. Это что-то вроде спортивного интереса. За основу моего нового Vegetable шейдера был взят референс Toon Ramp из учебной статьи Unity.

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



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



К сожалению, в таком режиме автоматическая отрисовка задней стороны полигона методом Cull Off давала не очень корректный результат, поэтому Backface пришлось добавлять вручную в редакторе. Далее к шейдеру была добавлена карта Normal и Specular. Использовать модель освещения PBR шейдера и подключить Reflection Probes (зонды отражений) мне уже не удалось, но с помощью масок и Emission карты была добавлена имитация Ambient Occlusion. Ну и наконец, крайне необходимо было все это дело оживить и придать растительности движения. С помощью функции Vertex и той же маски нужные области плашек с листвой начинают оживать. За основу смещения вертексов взят пример Normal Extrusion with Vertex Modifier из мануала Unity.



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



Свет
Значительно был изменен подход к самому устройству освещения в сцене. В оригинале весь свет был запечен в лайтмапы, а рендеринг работал в режиме Forward. В ремейке важным аспектом было использование реалтайм света для возможности изменения времени суток. Режим рендера был сменен на Differed. Главный источник света (солнце) использовался в режиме Mixed, направленное освещение и тени рисовались а реалтайме, а глобальное освещение запекалось в текстуры. Это позволяло менять уровень освещенности и направление, но при этом сохраняло эффект мягкого глобального освещения и рефлексов, которые всегда придают картинке дополнительного реализма. Запекание лайтмап-текстур происходило не для всех объектов, в основном для крупных и более-менее простых. Мелкие и сложные для создания развертки оставались динамичными и подсвечивались либо лайт -пробами, рефлекшн пробами, либо просто базовым амбиент лайтом ( который указывается в главных настройках освещения). Запекание осуществлялось Progressive лайтмапером.







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



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

Также, при смене времени скрипт настраивал нужный цвет для постэффекта Global Fog, поскольку в дневное время туман должен выглядеть как светлая серо-голубая дымка, а в ночное иметь более темный, почти черный тон.



Модели и новый контент


Основная часть локации и базовые модели: модель главного здания, деревья, скамейки, фонарные столбы остались оригинальными. Многие другие пропсы были полностью или частично переделаны. Была переработана модель керосиновой лампы, создан новый дизельный генератор с отдельными анимируемыми элементами, новая модель кинопроектора и т.д.







Близлежащие строения, в которые раньше нельзя было попасть, были переработаны и стали доступны для игрока. Внутренняя планировка главного здания была также изменена. Помимо переработок старого контента был добавлен и новый, например модель автобуса ПАЗ, игровой автомат, автомат для продажи газировки, пазл электрощиток, гермоворота в подвале, предметы инвентаря и т.д. Часть контента была создана самостоятельно, некоторые же модели были заказаны на аутсорсе.



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

Эффекты и нестандартные шейдеры.



Вода



С каждым новым проектом хочется учесть нюансы, на которые ранее закрывались глаза. Общая картина создается из отдельных деталей и даже не особо важные элементы могут повлиять на восприятие. Плюс, это в некотором роде челлендж каждый раз улучшать что-то, с чем уже работал. Особенное удовлетворение доставляет самостоятельный поиск решений, а не использование готовых ассетов. Во всех предыдущих проектах мне приходилось работать с водой. Как правило это был достаточно простой вертексный шейдер, который не реагировал на игрока и освещение в сцене. За основу был взят Mirror Reflection с Unity Wiki, который являлся примером реализации зеркала.



Поверхность была динамичной за счет смещения вертексов (имитация волн), но всегда слишком однообразной и несколько скучной. Путем проб и ошибок для проекта Свет мне удалось создать Surface вариант аналогичного шейдера, который как и образец может: принимать текстуру зеркального отражения от скрипта Mirror, деформировать вертексы для имитации волн, записывать экран в Grabpass текстуру для создания преломлений под водой, иметь мягкие Alpha края при пересечении с геометрией ( depth fade). Также для эффекта реакции на игрока в шейдер передается информация о координатах положения игрока. В точке координат рисуется динамичное пятно, которое имитирует всплески, когда персонаж находится непосредственно в толще воды. Самое же главное, что позволил Surface шейдер принимать освещение от любых источников света. Таким образом вода кажется более осязаемой, объемной субстанцией и позволяет поиграть с ней с помощью эффектов освещения.



Каустика
Еще одной важной деталью стало создание эффекта каустики световых отражений, падающих на поверхности. В темноте затопленных водой подвальных туннелей этот прием был просто необходим. Эффект создан с помощью объекта Projector и светящегося материала с анимированной текстурой. В шейдере смешивается 2 текстуры каустики, которые смещаются в разных направлениях, в результате создается эффект динамики. В большинстве своих шейдеров для экономии я обычно применяю текстуры-маски, содержащие в себе 4 канала (RGBA) каждый для определенной цели. В канале R может быть основная текстура, в канале G более мягкие световые пятна, а в канале B текстура шума для искажения рисунка каустики.



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



Частицы
Интересный эффект был реализован для мелких частичек в воздухе, которые заметны на свету. На саму идею меня вдохновил проект Homesick, в котором я однажды увидел подобное. На определенном расстоянии частицы имеют обычную текстуру, что-то вроде текстуры тополиного пуха. Но по мере приближения камеры текстура в шейдере плавно меняется на второй вариант, напоминающий эффект расфокуса. В динамике это выглядит достаточно приятно и частички кажутся более осязаемыми.



Как уже отмечалось выше, почти все базовые поверхности в сцене используют модифицированный вариант Standart PBR shader. В некоторых добавлены маски с различными вариантами пятен для дополнительной детализации Albedo текстур. Поверхностям вроде асфальта или брусчатки добавлена маска пятен для имитации дождевых луж. Шейдер напольной плитки в главном здании имеет дополнительную карту для отражений, которые рисуются скриптом Mirror аналогично водной поверхности. Для оптимизации в прорисовку отражений включены лишь базовые и крупные объекты, а на определенном расстоянии рендер отражений выключается.

Туман/дым
Важная деталь, которую мне хотелось учесть при создании ремейка это динамичный туман и влияние на него освещения, в частности света фонаря. В более ранних работах основным шейдером для дыма и тумана был стандартный Particle blend и его модификации. Это вертексный шейдер, который хорош в плане производительности, но совсем не реагирует на свет. Отображение тумана с таким материалом будет всегда одинаковым в тени и на свету, его нельзя подсветить фонарем и выглядит это не всегда привлекательно и натурально. В задуманных мной подземных катакомбах дымка должна была высвечиваться фонарем из темноты, подчеркиваться динамичным освещением. Для решения этой задачи был использован шейдер из бесплатного Ассета. Высокой производительностью шейдер похвастать не может, но визуально со своей задачей он отлично справился.



Декали
Еще одним важным моментом стал поиск подходящего шейдера для декалей. В игре планировалось множество всевозможных граффити, которые чаще всего устанавливались с помощью старенького плагина decal system ( еще со старой версии Unity 4.6). Для граффити, надписей и знаков был создан большой атлас размером 4096 на 4096. Изображения на декалях полупрозрачные и отрисовываются в режиме Transparent alpha, поэтому в случае с динамическим освещением они не всегда выглядят адекватно, поскольку стандартный альфа шейдер не способен принимать тени.

Для решения задачи был создан специальный двухпроходный шейдер. В первом проходе рисуются темные части изображения, во втором светлые методом смешивания Blend DstColor One. Я могу не совсем верно понимать метод отрисовки, поэтому воздержусь от подробных объяснений, но желаемого результата удалось достичь: в тени изображение утопает во мраке, а на свету проявляется и даже играет красками. На производительность двухпроходность шейдера влияния не оказала, поскольку Decal system изначально объединяет все декали в один большой меш. Наверно есть более оптимальные способы, но такой вариант мне вполне подошел.





Земля/трава
Еще один двухпроходный шейдер был создан для поверхности земли. Сама территория на локации сделана геометрией в 3d max. Но для отрисовки травы дополнительно был создан террейн. Пришлось вручную подгонять высоты террейна в нужных местах, чтобы они соответствовали рельефу местности. Затем отрисовка самого террейна была выключена а на поверхность кистью наносилось несколько вариантов заготовленных мешей травы. Родная трава на террейне жутко нагружает сцену в плане производительности, поскольку она не батчится и каждый элемент создает дополнительный вызов отрисовки ( возможно кто-то меня поправит, если я не прав), но подобный метод крайне удобен в плане работы. Густота травы не высока и в тех местах где ее нет, очень бросается в глаза простота поверхности. Как раз в связи с этим в качестве эксперимента я использовал двухпроходный шейдер, о котором говорилось выше. Первый проход обычная непрозрачная геометрия, второй немного приподнятый дубликат поверхности в режиме Alpha test. Грубо говоря, дополнительно рисуется копия поверхности земли/ травы с жесткой прозрачностью и смещенными вверх вертексами. Такой метод несколько дополняет плоскую траву и создает иллюзию дополнительной детализации и объема.



Свет за окном
За окнами главного здания в некоторых местах были размещены односторонние полупрозрачные спрайты для имитации засветов. Также, в постэффекте Global Fog имеется параметр яркого белого тумана, который активируется по мере движения героя вглубь здания. То есть, при нахождении персонажа в темных коридорах корпуса, окружение за пределами плавно приобретает белесое свечение. Это создает достаточно красивый художественный эффект и позволяет подчеркнуть ближний и дальний план.



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

История разработки The Light Remake. Часть 2

21.09.2020 12:22:00 | Автор: admin


Приветствую, читатель! В первой части статьи, посвященной разработке The Light Remake в общих чертах был рассмотрен процесс переноса игры на новую версию Unity. Я немного рассказал об используемых шейдерах и эффектах, о том, какие решения были реализованы в работе со светом, какой дополнительный контент был создан, какой контент из старой версии был переработан и т.д. Во второй части речь пойдет о других аспектах разработки, о постэффектах, структуре проекта, работе со звуком, оптимизации и прочих нюансах.

Часть 2


Постэффекты


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

Гамма

В оригинальной игре постэффектов было не много, но одним наиболее выделяющимся была коррекция цвета и яркости. Тогда мне удалось случайным образом добиться приятного белесого свечения и несколько прохладной цветовой гаммы. Картинка была контрастная и очень подчеркивала фантастическую атмосферу локации. Визуальный стиль несколько напоминал на тот момент цветовую палитру Battlefield 3, что мне казалось весьма удачным стечением обстоятельств.

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





Volumetric light

Очень важным элементом в данном ремейке стали постэффекты, связанные со светом. В старых версиях Юнити был очень симпатичный эффект объемных солнечных лучей Sun Shaft. Однако его функционал был ограничен, ведь он мог быть привязан лишь к одному источнику света в сцене.

В какой то момент мне на глаза попался очень занимательный эффект объемного освещения Volumetric lights Он позволяет создавать очень приятное, плотное свечение и лучи, которые могут исходить из разных источников. Атмосфера в сцене становится более объемной, объекты начинают утопать в легкой воздушной дымке, что меня крайне впечатлило. Единственным минусом стала высокая ресурсоемкость эффекта. Тем не менее, я решил его применить и благодаря Volumetric light удалось создать приятный свет от солнца во всей сцене, а также мистические направленные пучки света в отдельных местах: в коридоре на старте игры, в помещении с воробушком. Также эффект использовался для подчеркивания акцентов на светящихся ночью фонарных столбах, в эпизоде с проектором, в бойлерной для усиления света из окон, в финальной сцене вознесения персонажа к свету.



SSAO и SSR

Другими наиболее тяжелыми эффектами стал SSAO (Ambient Occlusion ), создающий мягкие затенения по углам и под поверхностями, без него сейчас никуда. Также был добавлен SSR (Screen Space Reflections ), с которым правда было много проблем еще на этапе разработки. SSR создает имитацию отражений на глянцевых поверхностях, в результате чего можно получить симпатичные рефлексы на кафельной плитке, металле и т.д. Проблема в том, что эффект оказался очень тяжелым и понижал FPS на моем железе почти в два раза. Путем некоторых манипуляций в коде постэффекта мне удалось немного снизить качество рассчета и несколько улучшить производительность. В целом частота кадров стала приемлемой, но в некоторых условиях (например при включенном Vsync) SSR вызывал периодические фризы и рывки при движении персонажа.



Другие

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



Кстати говоря, сейчас при работе с графикой часто вспоминается мой личный опыт обучения в художественной школе, где нас учили подчеркивать контрастные тени под объектами, отображать рефлексы на поверхностях предметов и накладывать в работе с пейзажами дымку на горизонте для подчеркивания воздушной перспективы. Теперь-то я знаю, что это все было SSAO, SSR и Global Fog!

Как это работает?


Первоначальный проект был собран буквально на коленке. Никаких навыков программирования на тот момент я не имел и весь функционал игры был построен на нескольких простых скриптах. Основным был Activate trigger скрипт, который я обнаружил в стандартных ассетах Unity. Все функции были построены на том, что персонаж попадает в триггер, включает или выключает определенные объекты и вызывает необходимые действия. О сохранениях или внутриигровых настройках не шло даже речи.

Система

Разумеется, для реализации ремейка предстояло с нуля написать всю систему игры: систему управления, сохранений, настроек и внутриигровых механик. Вообще, грамотное построение системы проекта для меня до сих пор представляет сложную задачу. Как правило я создаю базовый системный объект с несколькими дочерними объектами. Каждый из них выполняет свою группу функций и все они связаны между собой.

Поскольку игра состоит по большому счету из одной локации, я решил не заморачиваться с подгрузками сцен, префабов и компонентов, разместил все необходимое в одной главной сцене. Были созданы контроллеры, которые управляют всем необходимым, переключают настройки, сохраняют игру и считывают сохраненные данные, считывают тексты для субтитров и записок из специального файла в корне игры и т.д. Для простоты реализации как и в прошлых играх было решено использовать систему сохранения в чекпоинтах. На локации находится несколько десятков интерактивных объектов вроде дверей, геймплейных предметов, керосиновых ламп и т.д. Для каждого сохранения необходимо записать идентификаторы состояния объекта, представляющие из себя обычно Int переменную. Например, дверь закрыта и заперта на ключ: DoorOpen o, DoorLocked 1. Не буду вдаваться в подробности относительно программерской темы, поскольку мои навыки в этой области несколько специфичны и поверхностны, однако, для реализации собственных проектов вполне достаточны.

Два финала

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



Далее скрипт учитывает наличие поблизости дополнительных источников света, например ламп, света из окон, включенного фонаря или зажигалки в руках. Наличие одного из условий добавляет определенные значения к уже подсчитанным показателям освещенности. И наконец, самое мощное воздействие имеет попадание на персонажа прямых солнечных лучей. Из источника солнечного света в персонажа выстреливает рейкаст, который сообщает системе, находится ли игрок на открытом солнце.

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

Субтитры и локализация

Для ремейка был проработан новый для меня подход к локализации и вообще отображению текстов и сообщений. Точнее, подход этот ранее уже был использован в проекте 7th Sector, однако там объемы контента были значительно меньше. За основу взят метод сохранения данных в хмл документ. Вся текстовая информация для локализации изначально хранится в xml файле в корне игры. Сообщения поделены на группы и имеют индивидуальную метку, принадлежат определенным категориям и определенным языкам. Для переноса строки я решил использовать символ ( * ), а для начала нового сообщения ( # ).



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

Звук


Некоторая часть базовых звуков была перенесена из оригинальных исходников. Амбиент, звуки пения птиц, звук кинопроектора и т.д. было решено оставить для сохранения узнаваемости проекта. Однако, требовалось большое количество и нового контента. Были добавлены звуки самого персонажа, шагов, активируемых предметов, дыхания героя в определенных моментах, эффекта сердцебиения и т.д. Большее разнообразие было внесено в набор звуков окружения, добавлен стрекот кузнечиков, рандомные звуки падения предметов и хлопков дверей. Кстати говоря, на добавление шума цикад или кузнечиков меня вдохновили пейзажи новой Half Life Alyx, в которой очень здорово передана атмосфера жаркого летнего дня. Некоторое время наслаждался, прослушивая и просматривая на Youtube записи с амбиентами из City17.

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

Целым пластом ответственной и кропотливой работы стало создание саундтрека. Композиции в оригинальном проекте не были лицензионными, это был набор из треков Ludovico Einaudi, композитора Thomas Newman и OST из игры Afraid of monsters. Тем не менее, обычно нам западает в душу то, что мы услышали впервые, например оригиналы треков чаще всего кажутся более приятными, чем их ремиксы, по крайней мере для меня. В данном случае при создании новых композиций очень хотелось сохранить стиль и атмосферу оригинальных треков. С данной задачей, как мне кажется, отлично справился композитор Дмитрий Николаев, с которым мы уже работали ранее над игрой 35MM. Особое впечатление на меня произвел новый взгляд на динамичную композицию, звучащую во время показа кинофильма в лекционном зале. Трек сохранил оригинальный энергичный и немного психоделичный стиль, стал звучать свежо, но узнаваемо. Кстати, сам кинофильм тоже был сильно переработан и включал в себя много нового материала. Контент для видео более тщательно выбирался во избежании нарушения авторства, а часть фрагментов была создана самостоятельно.

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



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

Оптимизация


Одной из наиболее болезненных тем и задач в работе над ремейком стала оптимизация. Так вышло, что почти все проекты, созданные мной ранее на старой версии Unity ( 4.6) были достаточно просты в плане нагрузки на железо. Игра 35ММ на моей карте GTX 970 местами выдавала 200- 300 fps в достаточно сложных и загруженных сценах. Оригинальный Свет, собранный на еще более ранней версии движка показывал FPS еще выше. Но при переходе на Unity 2017 частота кадров просела в 2-3 раза. Понятно, что сцена стала намного сложнее, добавились просчеты света и отражений, дополнительные постэффекты и т.д. но настолько разительного снижения производительности я не ожидал. Загвоздка в том, что даже убрав из сцены практически все наполнение, показатель FPS у меня не поднимался выше 200-300. Это полупустая сцена, Карл! Была проведена большая работа по упрощению некоторой геометрии, созданию Lod групп, настройке Occlusion culling и т.д.



Также камерам с помощью скрипта назначалась дистанция отсечения определенных слоев. Для реализации использовался пример из мануалов Unity. Объекты разных размеров были присвоены отдельным слоям, которые перестают рендериться, если камера находится слишком далеко. Мелкие объекты, вроде банок, мусора, деревянных обломков, книг отсекаются после 20-30 метров. Более крупные после 60-80. Все перечисленные меры позволили значительно снизить число Drawcalls. В среднем количество вызовов отрисовки в сцене колеблется в диапазоне 800 2000 DC. Количество полигонов в кадре не превышает 1 миллиона.



Отсечение светильников

В рамках оптимизации был создан специальный скрипт, который раз в 2-3 секунды определял дистанцию до мелких динамических источников света, вроде керосиновых ламп. При удалении камеры от светильников, скрипт их выключал, чтобы не нагружать систему лишними процессами. Также, при спуске персонажа в подвальные помещения выключался источник солнечного света. Это значительно снижало

Немного деталей


  • В подвальных коридорах стоя под бетонными канализационными шахтами с капающей водой можно заметить как мутные капли появляются на экране ( глазах/очках персонажа). Сделано просто при условии попадания героя в триггер и при направлении камеры вверх включается префаб системы частиц с мутным Grabpass материалом. Также в этот момент капли можно наблюдать на поверхности карты, если взять ее в руки.



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



  • Во время нахождения персонажа в подвалах здания в меню можно заметить темные фигуры людей, стоящие напротив главного входа в корпус.

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



  • В катакомбах в одном из мест используется прием зацикленного коридора или петли. Похожая задумка была реализована в игре STALKER Зов Припяти на локации, где располагался Оазис. Когда мы попадаем в определенный триггер, скрипт переносит нас в почти такой же коридор, но уже зеркально и в обратном направлении. В результате, блуждая по туннелям мы несколько раз выходим в один и тот же зал. Телепортация не всегда срабатывает корректно, поэтому можно заметить резкий скачок и изменение дымки/ тумана перед собой.

  • Специально для проекта был снят видеоматериал с видами реальной территории, взятой в качестве референса 3 корпус Московского Гуманитарного университета. Из материалов был смонтирован ролик, который стал секретным бонусом, доступным после прохождения игры. Для проникновения на территорию пришлось перелезать через старый бетонный забор. При переброске бойцов в целевой сектор ни одни штаны порваны не были.



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



  • В финальной сцене, где персонаж летит к свету, был использован интересный прием инстанцирования геометрии. В кадре очень много темных человеческих фигур, парящих в воздухе. Изначально был вариант сделать их с помощью системы частиц, но для эксперимента было решено попробовать метод Graphics.DrawMeshInstancedIndirect по примеру.
  • В скрипте можно задать достаточно большое количество экземпляров и железо с такой нагрузкой справляется весьма успешно. Сам шейдер фигур анимирован для имитации полета, а в точке нахождения нашего героя и в радиусе нескольких метров геометрия уходит в альфу, для того чтобы камера случайно не попала во внутрь человеческой фигуры.



Парочка мыслей


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

Желаю всем удачи, творческого вдохновения и высокого FPS!
Подробнее..

Играем в DOOM на тесте на беременность. Что? Да

07.09.2020 18:22:46 | Автор: admin
А также на микроволновке, валидаторе билетов и многом другом.



Ко флэшмобу по переносу DOOM на любые устройства, у которых только есть дисплей, теперь добавился еще и тест на беременность.

Программист-энтузиаст и адепт реверс-инжиниринга Foone сумел запустить полнофункциональную игру DOOM на электронном тесте на беременность, а до этого то же самое проделал с The Elder Scrolls: Skyrim. Первое видео классического шутера на миниатюрном экране устройства было показано им на выходных в личном микроблоге в Twitter. Тогда он признался, что на самом деле просто проигрывал видео, но с тех пор поднял ставки и нашел способ действительно сыграть в DOOM на электронном тесте при помощи беспроводной клавиатуры.


Как такое возможно, спросите вы? Конечно, не обошлось без читерства.

В своем треде Foone ссылался на исследования другого юзера Twitter, Xtoff, в прошлом месяце решившем проверить, что кроется за корпусом теста на беременность компании ClearBlue. Foone использовал другой бренд, Equate, но обнаружил в нем похожую начинку: бумажную полоску, батарею CR1616 3V, восьмибитный микроконтроллер Holtek HT48C06 (64 байта RAM, работает на частоте 4 МГц либо 8 МГц) и оперативная память.

image

По словам Foone, этот наполнение этого теста на беременность быстрее в вычислении базовых математических операций и I/O, чем IBM PC.

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

Чтобы полноценно запустить игру, ему пришлось заменить дисплей и микроконтроллер. Оригинальный дисплей мог показывать только четыре возможных экрана, а центральный процессор нельзя было перепрограммировать, поэтому пришлось пойти на дополнительные ухищрения. Кроме того, тест был подключен к ПК в качестве источника питания, при помощи него же запускалась игра. Конечно, даже после замены дисплей остался монохромным с разрешением 128х32 пикселя, так что играть на нем оказалось не очень-то удобно. Но само по себе отображение DOOM на дисплее теста на беременность уже забавно и достойно восхищения.

image

Началом истории Will it run DOOM? можно считать 2016 год, когда DOOM был запущен на миниатюрном Game Boy, помещенном в брелке для ключей. Времени с тех пор прошло прилично, и теперь уже кажется, что легендарный шутер можно запустить практически на чем угодно. Чтобы доказать это, ниже приведем список (не полный!), на каких устройствах это удалось.




Принтер Canon Proxima


Система безопасности линейки принтеров Canon оказалась не самой стойкой, и через софт для обновлений удалось добиться того, чтобы игра запускалась на дисплее устройства.



Осциллограф


Сам по себе осциллограф использует операционную систему Windows 95, поэтому, по словам пользователя Zekinn, впервые запустившего DOOM на таковом, дальше дело оставалось за малых отбросить лишние предубеждения во имя улучшения человеческого опыта.



Банкомат


Обычно банкомат используют для снятия или внесения денег, но энтузиасты решили вместо этого запустить на нем DOOM. С предустановленной на таких автоматах операционной системой Windows XP дальнейшие действия по установке игры упирались лишь в вопрос желания.



DOOM в DOOM


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



Пианино


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



Minecraft


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



Валидатор билетов




Микроволновка


image


iPod Mini


image

Больше примеров в треде на Reddit.
Подробнее..

Бесплатный онлайн-Лекционный вечер по геймдизайну

08.09.2020 16:22:26 | Автор: admin
16 сентября (Среда), в 18:00, состоится бесплатный Лекционный вечер по геймдизайну, в онлайн-формате.



Вас ждут три интересные лекции:

1) Карьера геймдизайнера: как понять чего вы хотите на самом деле и достичь этого.
Спикер: Константин Сахнов совладелец издательства JustForward, игровой продюсер.

2) Геймдизайн: что делает игру интересной.
Спикер: Владимир Агарев Креативный продюсер в компании Gaming Point.

3) Работа геймдизайнером в игровой компании.
Спикер: Георгий Миронов Game Designer в 1C Game Studios.

Мы приглашаем инди-разработчиков, сотрудников игровых компаний, студентов, всех, кто хочет делать собственные игры или играет в игры.

Участие в мероприятии абсолютно бесплатно.

Все подробности и регистрация доступны по ссылке >>>
Подробнее..

Перевод Поиск инвестиций. Часть 1 если ищете средства на разработку игры

18.09.2020 16:04:09 | Автор: admin

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


Изучая материалы по этой теме им попалась серия статей, создаваемая при поддержке Epic Games, Майком Фаттером. Оценив качество материала и потенциальную пользу для русскоязычного коммьюнити, команда Rummy Games решила перевести статью и выложить ее на нашем ресурсе.


Создание видеоигры это совокупность непростых творческих задач.



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


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


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



Русский инвестор питчит стартаперов и геймдевелоперов


Венчурный капитал, бизнес-ангелы, привлечение займа, частные инвестиции Что все это значит?


Частные инвестиции (ЧИ)


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


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


Providence Equity Partners, Insight Venture Partners и Lightspeed Venture Partners объединения частных инвесторов, которые поддерживают индустрию видеоигр в том числе. Существуют также фонды, которые специализируются исключительно на видеоиграх, например: Makers Fund, Kowloon Nights и Hiro Capital. Такие фонды создаются для поддержки студий и новаторов геймдева.


Венчурный капитал (ВК)


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


К венчурным компаниям, инвестирующим в игры, относятся Sequoia Capital, Kleiner Perkins Caufield & Byers и Andreessen Horowitz.


Бизнес-ангелы


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


Термин Бизнес-ангел, возможно, звучит альтруистично, но на деле они также ждут окупаемости своих вложений. В качестве бонуса нередко предлагают экспертное руководство, менторство, знакомство с другими инвесторами, с которыми сложно связаться самостоятельно.


Краудфандинг или народное финансирование


Double Fine Adventure 2012 года (позже переименованная в Broken Age) это не первый краудфандинговый проект для видеоигр, но именно благодаря ему платформа Kickstarter стала популярной среди разработчиков. Идея проста презентуйте свою игру аудитории и предложите профинансировать ее заранее, например, за несколько лет до даты выхода. На практике краудфандинг тяжелый и напряженный способ финансирования. Этот пузырь давно лопнул. В 2019 видеоигры получили всего 16,9 млн долларов на Kickstarter против 50 млн в 2012 году. На сегодняшний день на платформе размещено более 50 тысяч проектов в категории игры, из которых успешно финансируются только 20 тысяч (стоит отметить, что в это число входят кампании по созданию настольных игр, которые составляют основную часть в этой категории). Краудфандинг не стал панацеей в вопросах финансирования проектов по созданию видеоигр, но остается одним из возможных вариантов для некоторых разработчиков.


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


Краудинвестинг или акционерный краудфандинг


Модель краудинвестинга сочетает в себе краудфандинг и классический подход к финансированию проектов по созданию видеоигр. Fig платформа для краудфандинга и микроинвестиций, созданная в 2015 году, модель которой оказалась довольно успешной 25 из 39 кампаний привлекли финансирование (64%). Благодаря краудинвестингу, свет увидил такие проекты, как Psychonauts 2 от Double Fine и Soundfall от Drastic Games. Платформа также принесла инвесторам прибыль в пяти из одиннадцати выпущенных игр. Fig продолжает развивать свою бизнес-модель, недавно расширившись до концепции Открытый доступ (Open Access). Open Access это возможность финансирования игровых проектов на стадии раннего доступа.


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


На самом деле, результативность гораздо выше, когда мы стараемся усилить то, что уже есть у разработчиков, поясняет Бейли. Все хотят быть сопричастными к истории успеха. И всем нравится, когда проект уже набрал обороты. Ведь сложнее всего это сдвинуть дело с мертвой точки. Поэтому, приходя к нам, разработчику следует думать так: Через эту платформу я смогу привлечь, до половины трафика и финансирования, которые нужны для успеха моей кампании.


Мы обсудим краудфандинг и краудинвестинг более подробно в следующих статьях.


Издатели


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


Гранты и налоговые льготы


В зависимости от географического положения вашей студии, вы можете иметь право на получение определенных видов финансирования. Канадский фонд СМИ (Canadian Media Fund) и Игровой фонд Великобритании (UK Games Fund) два примера государственной финансовой поддержки местных компаний, работающих с цифровыми интерактивными медиа. Штат Джорджия в США и канадская провинция Квебек являются примерами региональных правительств, предлагающих налоговые льготы.


Точно так же некоторые компании предлагают премиальные выплаты или возможности финансирования для использования определенных инструментов. Выделив 100 млн. долларов, Epic Games создали инициативу под названием Epic Mega Grants, в рамках которой предоставляется финансирование для широкого круга проектов и авторов. Включая разработчиков игр и инструментов для них, студентов, преподавателей, работающих в академических учреждениях, создателей средств массовой информации, медиа-развлечений и прочего. Если вы работаете с Unreal Engine или предоставляете инструменты и ресурсы с открытым исходным кодом, подача заявки на Epic Mega Grants может стать перспективным шагом.


Финансы не проблема, а возможность


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


Я наблюдаю это каждый день, обычно это выглядит так: Слушай, мне просто нужны деньги. Мне неважно, будут они от венчурных капиталистов, издателей или банков. Мне просто нужен мешок денег, говорит Джейсон Делла Рокка, соучредитель Execution Labs. Но так не пойдет.
Делла Рокка утверждает, что инвесторы ищут возможности по заработку, а не кому бы помочь. Позиционирование финансовой потребности вашей студии или игры как проблемы вряд ли привлечет инвестора.


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


В конечном итоге это приводит к серьезной дилемме: привлекать займы или использовать собственные средства. Об этом мы поговорим в следующей статье.


Ваши принципы путеводная звезда при поиске финансирования


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


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


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


Студии-разработчику Polyarc (Moss) потребовалось несколько лет на поиск правильного инвестора. Генеральный директор и соучредитель Тэм Армстронг рассказал, что он и его команда потратили годы, выбирая между продажей доли компании и займом.


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


Армстронг отметил, что существуют соглашения о привлечении займов, которые позволяют разработчикам сохранять контроль над своей интеллектуальной собственностью. Лучше обратиться к юристу за проверкой условий договоров и консультацией по вопросам собственности, контроля и использования ИС в каждом конкретном случае. В конечном итоге Polyarc привлекла частные инвестиции, сохранив контроль над своей интеллектуальной собственностью.


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



Что дальше


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


P.S. Статья переведена на русский язык и отредактирована командой разработки игры Saturated Outer Space, выпускниками образовательной программы Менеджмент игровых проектов. В настоящее время ребята ищут финансирование для своего проекта. Увидев эту статью на портале www.unrealengine.com, они решили поделиться ей с русскоязычным комьюнити. Если вы захотите отблагодарить их за перевод, то добавляйте игру в свой вишлист в Steam.


Майк Фаттер журналист-фрилансер и консультант в игровой индустрии. Автор книги Gamedev Business Handbook (Руководство по ведению бизнеса в сфере разработки видеоигр), а также соавтор подкаста Virtual Economy.

Подробнее..

Перевод Поиск инвестиций. Часть 2 в нужном месте в нужное время

21.09.2020 18:14:10 | Автор: admin

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


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



Инвестиции в акционерный капитал или финансирование проекта


Начать стоит с того, чтобы установить, что же вы ищете: финансирование игры или инвестиции в вашу компанию?


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


Однако если вы заключите договор о сотрудничестве с издателем или вашу студию купит игровая платформа, шансы получить финансирование на разработку игры (или нескольких) заметно возрастут.


Сложность как раз в том, чтобы определить, что именно вам подходит.


Практика показывает: если вы делаете игру премиум-класса или что-либо линейное с акцентом на повествование выбирайте финансирование проекта, рассказывает сооснователь Execution Labs Джейсон Дела Рокка, Но если у вас в планах free-to-play проект или игра-сервис, которые подразумевают большую базу пользователей и многоразовое масштабирование проекта, конечно, лучше идти по пути продажи акций.



The Darwin Project от Scavengers Studios


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


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


Большая часть диалогов с разработчиками начинается с вопросов:


Что конкретно вы делаете? Какими возможностями располагаете? Какую игру или студию вы создаете?


Лиха беда начало


Методы поиска источников финансирования эволюционируют вместе с вашей компанией.


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


Большое количество новых студий существуют за счет привлечения на ранней стадии так называемых love money инвестиции от друзей и родственников. Это не мешает им зачастую просить взамен акции или часть прибыли. Love money могут стать мощной поддержкой на старте проекта, особенно если учесть, что получить финансирование под прототип довольно трудно.
Другой способ преодолеть трудности на старте проекта собственные сбережения и прочие источники дохода.


Вам предстоит занимать, тратить сбережения и просить помощи до тех пор, пока вы не доведете свой прототип до нужного уровня. До того, который подойдет для презентации издателю или инвестору. А это задача не из легких, отвечает Каллум Андервуд, сооснователь Robot Teddy и менеджер портфеля проектов Kowloon Nights, Представьте, как ограничены возможности людей из бедных слоев населения или резидентов стран с малым доступом к технологиям и к индустрии в целом. У них нет денег и связей с таким количеством разработчиков, как, например, у жителей Лос Анджелеса.


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


Мы знали, что не найдем финансирования в самом начале, но решили, что нам помогут связи, Тэм Армстронг, генеральный директор Polyarc.


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


Как надо и как не надо презентовать проект


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


Составлять такое письмо это как ходить по минному полю,- говорит Андервуд.


Например, зачем в письме спрашивать меня: Хотите ли вы посмотреть наш билд? Просто пришлите его. Если не захочу, то и открывать не стану. Решению о заключении договора это никак не препятствует.


Если слишком сложно решить, что написать, предлагаю краткое содержание:


  • Вот билд;
  • Вот видео;
  • Вот что я хочу;
  • А вот сроки разработки.

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


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


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



Godfall от Counterplay Games and Gearbox Publishing


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


Будет отлично, если в ходе презентации покажете короткое видео последней версии играбельного билда.


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


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


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


Я нахожусь в уникальной позиции: сотрудничаю с Kowloon Nights в сферах бизнеса и поиска стартапов, работаю с Superhot подготавливаю Superhot Presents, консультирую и ищу стартапы для Oculus. Но нет смысла идти с одинаковой презентацией проекта во все эти компании.


Осознание этого факта является камнем преткновения для многих разработчиков.


Если вы просто зайдете на сайт Kowloon Nights, вы не найдете там слов VR и AR. Так зачем идти с VR игрой к Kowloon? То же самое мы увидим на сайте Superhot там нет ни VR, ни AR. Остается очевидный выбор Oculus, а не предыдущие 2 компании. Удивительно, как часто люди упускают это. Ведь можно потратить 10 минут на изучение потенциального партнера, а не биться о стену


Во время презентации проекта с издателем или инвестором важно знать, какими финансовыми возможностями они располагают.


Приходя на встречу с несоответствием желаемого и возможного, можно уйти ни с чем.


Если вы хотите 3 миллиона долларов на не-VR игру, то узнайте, кто из издателей сможет позволить себе такие траты или измените запрашиваемую сумму. В Superhot Presents мы получаем удивительное количество подобных заявок, что печалит, так как разработчики впустую тратят на их подготовку свое время.


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



Moss от Polyarc


Мы всегда заранее знали, с кем мы говорим, рассказывает Армстронг.


Мы подгоняли презентацию к тому, что от нас хотят услышать. Ребята, делающие инвестиции в компанию, хотят узнать о ее глобальной стратегии. Они хотят представлять, что случится с компанией через 2,3,4 года, через 5 лет, и какой вообще план действий.


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


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


Также необходимо понимать метрики, на которые обращают внимание инвесторы. С тех пор, как индустрия игр начала двигаться в направлении игр сервисов (free-to-play и premium), акционеры фокусируют внимание на них. Для увеличения шансов при поисках инвестора на такие проекты Дела Рокка советует уйти от производственных показателей.


Если вы презентуете достижение производственных целей, то вы совершаете ошибку, объясняет Дела Рокка. Когда я презентую достижения по увеличению коммьюнити игры, это звучит как У нас был ЗБТ пришла 1000 человек. Затем у нас был еще один ЗБТ, но уже с 5000. А еще мы отслеживали сессии, и все, кто играл приходили на следующий день и играли еще по 20 сессий.


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


Согласно утверждениям Дела Рокка, гораздо ценнее сообщить потенциальному акционеру об увеличении пользователей проекта и ретеншена, чем говорить с ним о разнообразии видов оружия в вашей игре.


Это показывает, что вы видите тенденции, знаете важные показатели. Нужно ли делать еще один вид оружия в игре? Это не имеет значения!


Пример графика долевого финансирования


В 2018 году на презентации во время Game Developers Conference, Дела Рокка предоставил график для поиска долевого финансирования на разных стадиях проекта. Все начинается с самофинансирования и love money (если они доступны).



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


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


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


Дела Рокка также показал визуализацию пути проектного финансирования. При просмотре этого графика необходимо учесть:


  1. издатели, о которых мы поговорим более детально в следующей статье, охотнее финансируют уже существующие команды с прототипом и рассчитанным бюджетом;
  2. курс краудфандинга сместился с 2012 г, когда Double Fine прекратили золотую лихорадку Kickstarter. За последние 8 лет краудфандинг перешел со стадии концепта до довольно поздних стадий цикла производства.


Реальность и отказы


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


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


Я не ожидал, что получу столько звонков от людей, которым до индустрии нет никакого дела, рассказывает Армстронг.


Не в обиду сказано, просто некоторые люди звонят ради интересного разговора и из чистого любопытства. Даже будь у вас четкая цель добиться от этих людей каких-то вложений, у них о вложениях нет ни единой мысли. Опять же, не злонамеренно, но бывает: О, привет, ребята! Я слышал вы очень стараетесь выпустить свой проект о котором я ничего не знаю. Хочу чтобы вы мне подробно все рассказали!


Иногда потенциальный инвестор не готов вложиться полностью, но и однозначно отказывать тоже не хочет. Нет такого рода звучит скорее как не сейчас.


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


Это способ отложить разговор на неопределенный срок. О таком чаще всего говорят как о мягком нет, и таких звонков было больше всего.


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


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


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



Disc Jam by High Horse Entertainment


Как вариант, вы можете решить пойти дальше вообще без инвестиций. Независимость достается дорогой ценой. Особенно тем, кто находится в ААА сегменте. High Horse Entertainment, студия из двух людей, сочетающая работу по контракту и собственные разработки. Дуо Типа Раппа и Джея Маттиса работали в Activision, у обоих есть опыт в лайв-сервисах и сетевом программировании. Этот опыт стал решающим для успеха их первой игры Disc Jam.


Сегодня High Horse остается независимой, стойко игнорируя предложения по приобретению доли в компании и по инвестициям.


Когда к тебе приходят, чтобы вложить деньги в компанию, это обычно происходит по одному-двум сценариям. К примеру: Эй, мы компания, у которой не получается делать сетевые проекты правильно. У нас была пара попыток, но их хватило, чтобы понять, как это сложно. Нам нужны профессионалы, которые сделают это и создадут нужные технологии. Это один случай. Бывало и так: Disc Jam крутая игра. У вас, ребята, много пользователей. Вы сделали кросс-платформенный сетевой тайтл. Мы хотим запрыгнуть к вам на этот проект и на другие тоже. Как насчет того, что мы вас купим и уже сообща заработаем?


Были еще версии: Нам нравится то, что вы сделали. Как вам идея завершить этот проект и привнести свою экспертизу в другую команду, которая уже готова или только набирается?


Эти 3 сценария выглядят разными, но суть всегда одна и та же. Причина, по которой мы покинули Activision изначально была в том, что мы хотим делать что-то свое. Мы хотели стать независимыми разработчиками.


Найдите время для поиска средств


Довольно просто забыть, что поиск инвестиций это процесс, проходящий вне традиционных собраний и мероприятий, подобных Game Developer Conference и другим конференциям.


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


Разработка питча, создание играбельного билда, исследование рынка, звонки, отправка электронных писем все это может отнимать много вашего времени. Пока студия еще маленькая и находится в начале своего пути, поиск инвестиций должен стать одной из главных целей.
Я могу сказать, что это занимает существенное количество времени, объясняет Армстронг. Это работа. Количество времени, которое я тратил на поиск инвесторов, было не абсолютным лишь потому, что еще я занимался программированием и дизайном самой игры. В среднем это занимало более одного рабочего дня в неделю. Так как больше дня, то точно больше 20% всего рабочего времени. А должно бы было занимать все 50%.


И даже при этом Армстронг не уверен, что в Polyarc потратили достаточно времени на поиск инвестиций. У студии тогда не было специалиста по данным вопросам.


А если бы и был, пошло ли дело быстрее и эффективнее? Скорее всего. Верхнего порога по времени на поиски нет. Я не видел никого, кто потратил на это слишком много времени.


Майк Фаттер журналист-фрилансер и консультант в игровой индустрии. Автор книги Gamedev Business Handbook (Руководство по ведению бизнеса в сфере разработки видеоигр), а также соавтор подкаста Virtual Economy.


Статья переведена на русский язык и отредактирована командой разработки игры Saturated Outer Space, выпускниками образовательной программы Менеджмент игровых проектов. В настоящее время ребята ищут финансирование для своего проекта. Увидев эту статью на портале www.unrealengine.com, они решили поделиться ей с русскоязычным комьюнити. Если вы захотите отблагодарить их за перевод, то добавляйте игру в свой вишлист в Steam или пишите им в группу в VK.

Подробнее..

Разработчики Amnesia опубликовали исходный код игры

24.09.2020 08:13:47 | Автор: admin
Amnesia: The Dark Descent и Amnesia: A Machine For Pigs с этого дня распространяются по лицензии GPL3.



Моддинг был важной частью Amnesia. За годы существования The Dark Descent на ModDB накопилось более тысячи модов и дополнений. Разработчики чрезвычайно благодарны всему сообществу. Пришло время отплатить фанатам тем же, и Frictional Games выложили исходный код игр под лицензией GPL v3:

Помимо кода самих игр присутствует и код редактора.

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

Как пишут разработчики, они могли бы уже давно опубликовать код игры, но всегда были более важные дела. Но теперь, когда Amnesia: The Dark Descent исполнилось 10 лет, а до Amnesia: Rebirth осталось меньше месяца, они решили всё-таки порадовать сообщество.

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

Wargaming выдвинула новое обвинение добавив технодемо 2017 года

16.09.2020 12:14:05 | Автор: admin

Wargaming неожиданно усилил на нас атаку в США. В конце Августа 2020 мы узнали из калифорнийского суда, что после нашего недавнего переименования (которое, как нам казалось, должно было снять все прошлые надуманные претензии), к искуWargaming было добавлено обвинение теперь уже и в незаконном копировании исходного кодаNetworkTestGame. Так называется тестовый клиент, использовавшийся для отладки сетевого кодаOpenSourceдвижкаdava.framework, размещенного наgithub.comс 2012 года. Чтобы формально обосновать эту атаку, кипрская компания в Июне 2020 зарегистрировалаNetworkTestGameвU.S.Copyright Officeпод видом отдельного продукта и приложила эту регистрацию к иску, дополнив экспертным сравнением скриншотов с нашей игрой BattlePrime:

Один из лидеров на рынкеfree-to-playMMO (источникhttps://wargaming.com/ru/about/) обосновал заимствование следующими признаками:

1) игровой прицел внешне похож;

2) идентичный виртуальный джойстик, расположенный в нижнем левом углу;

3) пустынная местность с пальмами, которые даже отбрасывают тень;

4) управляемый игроком стрелок изображен в виде от третьего лица верхней половиной тела;

5) 3Dизображение противника в отдалении.

Оставим оценку такой аргументации и подходов WG вдумчивым читателям, следящим за развитием травли нашей компании. Со своей стороны заверяем, что ООО Пресс Фире Гамес продолжает развитие свой игры Battle Prime, полностью разработанной на 100% своём движке ни в коей части не основанном на старом движке Wargaming. А что касается юридических процессов в Беларуси, Кипре и США, то мы будем держать вас в курсе.

FYI: Мы приняли решение переименовать ООО БлицТим в конце Мая 2020, чтобы отделить спор по торговым маркам от развития игры.

Подробнее..

Из песочницы Alt City Online. Как я в одиночку создавал Gta Online для мобильных устройств. Часть 1

23.09.2020 02:13:19 | Автор: admin
Возможно ли в здравом уме замахнуться на подобный проект в одного, и надо ли оно вообще? Спойлер: да (длинный пост с картинками и видео).



Предыстория


Разработкой программных продуктов я занимаюсь уже 6 лет, начинал с разработки приложений для iOS. После выпуска нескольких приложений, которые в общей сумме принесли около $500, решил попробовать себя в разработке сайтов и настройке рекламы. В этой сфере я проработал 3 года, и понял, что создавать сайты не то, чем мне хотелось бы заниматься в жизни.

Так как с детства я очень любил игры, решил рассмотреть геймдев как будущую нишу, где хотел бы себя попробовать. Сделал бесплатную игру на SpriteKit (фреймворк Apple для создания 2D игр), начал знакомиться с инструментами для разработки игр. Решил подробно изучать Unity, так как он мне показался оптимальным вариантом для разработки именно мобильных игр. Выпустил в AppStore и в Google Play простенькую игру на Unity, и естественно поиграли в нее условно 3 с половиной человека. Это меня не особо остановило, так как цель разработки этой игры была в основном в том, чтобы познакомиться с процессом разработки в Unity и запуском игры именно в Google Play. Эти цели были выполнены, можно было двигаться дальше. Я начал уже более тщательно изучать Unity: 3-4 часа в день стабильно проходил Advanced курсы по разработке. Думаю, что мне повезло попался действительно подробный и толковый курс по созданию RPG в Unity, и многое, особенно различные best-practices, я узнал именно из него.

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

В общем, после ухода с работы, пришло понимание, что хочу попробовать создать действительно интересную и уникальную игру (наверное термин уникальная игра не совсем вяжется с концепцией игры-аналога GTA Online, но об этом дальше). С детства я обожал серию GTA играл десятками часов напролет в GTA Vice City и GTA San Andreas, ставил моды, крутил параметры машин. Потом после выхода GTA IV все то же самое делал с ней. Потом GTA IV: Episodes from Liberty City, GTA V. И естественно GTA Liberty City Stories, GTA Vice City Stories, GTA Chinatown Wars для PSP. Также было потрачено куча часов в других подобных играх Saints Row, Godfather 2 и т. д. Эх, хорошие были времена

Так вот, потом я познакомился уже с различными Role Play проектами, основанными на GTA. Но знакомство с ними, к сожалению, уже происходило через YouTube, так как работа стала занимать практически все время, а найти 30 минут в день на ролик не проблема. Считаю, что RP проекты создали очень интересную нишу, и вообще переосмыслили GTA.

RP проекты создали очень интересную нишу, и вообще переосмыслили GTA

Как я уже сказал выше, свободного времени становилось все меньше, и я захотел поиграть во что-то по типу GTA Online / GTA RP на телефоне благо сегодня телефоны действительно мощные, и по идее что-то подобное можно реализовать (например тот же PUBG, который отлично работает на практических любых устройствах). Каково было мое удивление, когда в AppStore я нашел буквально 3 игры, которые хоть как-то можно было отнести к аналогам GTA Online, да и те ужасные. Вот так и появилась идея создать первый аналог GTA Online для устройств на базе iOS и Android.

ALT: City Online




Геймплей в ALT: City Online это смесь классической GTA Online и ее Role Play модификаций. В самом начале игры ты появляешься в стартовой точке (предполагается, что это будет либо вокзал, либо аэропорт). Твоя задача найти работу и начать зарабатывать деньги и опыт. По мере получения опыта, тебе будут открываться новые профессии. Список профессий будет широкий, и я сейчас работаю над тем, чтобы сделать геймплей каждой профессии максимально интересным насколько это возможно. Далее ты сможешь купить себе квартиру, мотоцикл, потом дом, машину, машину получше в общем все, кто играли в GTA RP, знакомы с этим. Фишка игры заключается в том, что в сессии, в отличии от классической GTA Online, будут сотни человек, ты сможешь взаимодействовать со всеми разговаривать, наносить урон, кооперировать, обмениваться вещами, продавать вещи.



В игре будет возможность покупать оружие, менять его скины. Также в игре будет доступен тюнинг транспортных средств.

Но главное отличие от RP проектов в ALT: City Online нет классической для таких проектов консоли, нет администраторов, не нужно отыгрывать РП. Ты можешь, но не обязан.

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



Важно то, что игра будет работать не только на последних топовых девайсах. Например, если говорить об iOS, то минимально поддерживаемое устройство iPhone 7. Вообще, основным боттлнеком оптимизации игры стала непрозрачность потребления памяти на iOS платформе (это известный недостаток Unity, с которым на данный момент мало что можно сделать), из-за чего было достаточно сложно оптимизировать огромный открытый мир для работы на маленьком устройстве с 2 гб оперативной памяти.


Какие вопросы предстояло решить


В первую очередь надо было вообще понять, возможно ли реализовать мобильную онлайн игру в большом открытом мире на Unity? Соответственно, был куплен простенький генератор города для Unity, контроллер персонажа, скачаны бесплатные модели оружия и автомобиля и кое-как настроен клиент сервер (вопрос реализации мультиплеера решался достаточно долго, так как официального решения от Unity нет, а фреймворков много, и они очень отличаются, расскажу о сетевой архитектуре подробнее в одной из будущих технических статей). Спустя две недели был готов достаточно играбельный прототип. То есть был запущен тестовый сервер игры на виртуальном сервере, установлены клиенты на телефоны друзей. Мы подключились, поиграли все отлично работает для прототипа, все довольны. Провел стресс тест, получилось, что минимум 100 игроков сервер точно держит. Для игры это достаточный минимум, но вообще, цель 600-1000 игроков на сервере (то есть столько игроков будет одновременно в сессии). В общем, пришло понимание, что Unity очень даже подходит под этот проект.


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

Далее нужно было понять, как быть с 3D моделями? А их нужно было действительно много здания, автомобили, различные пропсы, одежда, оружие. И самое главное как смоделировать сам мир: ландшафт (террейн), дорожную сеть, город? Написал подробный список всех нужных объектов. Потратил неделю на поиск нужных моделей по списку, и понял, что в принципе все что нужно есть в магазинах 3D моделей.

Сделаю отступление надо понимать, что я занимаюсь проектом с декабря на фул-тайме (по 10-12 часов в день, без выходных), а практически все средства, отложенные за прошлые годы, я тратил на покупку различных инструментов и 3D моделей. Думаю, это снимет многие вопросы по поводу того, почему на некоторые этапы потрачено мало времени.

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

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

Текущее состояние проекта


На данный момент я работаю над игрой уже девятый месяц. Проект готов к альфа релизу на 85%. Уже сейчас полностью смоделирована карта, настроена работа открытого мира на телефонах, проведена работа над рендерингом (LODы, различные настройки графики, оптимизация текстур, динамический батчинг и т.д.), настроена архитектура клиент-сервера, оптимизировано потребление памяти.

Для альфа релиза осталось доработать базовые механики, добавить работы, добавить контент (автомобили, одежду), добавить UI и все это хорошенько протестировать.


Скриншоты










Заключение


Спасибо, что дочитали данный пост до конца. Это мой первый опыт написания статьи, так что буду рад фидбеку. Это первая статья из цикла, посвященного скорому релиз игры ALT: City Online. Последняя статья этого цикла будет новостью о релизе игры. Так что, пожалуйста, пишите, про что хотели бы почитать в будущих постах.

Огромная просьба ко всем, кого заинтересовал проект: скоро мы начинаем открытые альфа тесты в онлайне на Android и iOS, поэтому вступайте в группу ВКонтакте, чтобы узнать, когда начнется альфа тестирование. Тем самым вы очень поможете развитию проекта. Всем тестировщикам естественно полагаются жирные игровые призы сразу после релиза проекта.

Также подписывайтесь на Twitter, там будут выкладываться новости, а также актуальные фото и видео о разработке: @AltCityOnline

На сайте ALT: City Online вы также можете оставить свой email. На него придет оповещение, когда игра будет доступна для загрузки. Всем, кто оставил свой email на сайте, так же положен жирный бонус при запуске игры!
Подробнее..

Из песочницы Индюшатина, как ее готовят

19.09.2020 22:22:49 | Автор: admin

GoooGooo
Привет! Сегодня мы поговорим с ребятами из Literal Team, которые делают игру Sovereign's Will об их страданиях, мучениях и победах при разработке индюшатины. Есть много замечательных игр, от больших и богатых студий играй и переигрывай. Но тем не менее люди берутся создавать свои, в гараже, на коленке, тратя сотни часов личного времени и кучу денег, без каких либо гарантий успеха. Мы живём в удивительное время, когда каждый, у кого хватит упорства, может на коленке собрать игру своей мечты или почти каждый или мало кто. Большинство таких проектов не доживают даже до стадии дизайн концепта. Ребятам из LT есть чем похвастаться, они обошли многие подводные камни инди разработки, проделали огромную работу на чистом энтузиазме, создали альфу, отказались от нее, создали другую альфу и продолжают упорно работать дальше. Видеть, как люди в режиме реального времени превращают свою мечту в реальность очень прикольно и вдохновляет, об этом мы с ними и поговорим.


Lesich
Привет! Я Алексей, один из участников LT, занимаюсь геймдизайном, кодингом и другими вещами по мере их поступления.


graphomant
Привет! Меня зовут Артём, я отвечаю за сценарий и текстовое наполнение игры


GoooGooo
Расскажите пожалуйста, почему вы решили этим заняться?


graphomant
Я всегда интересовался двумя вещами: играми и литературой. Совместить эти два занятия идея, лежащая на поверхности, но, к сожалению, в моё время (мне 31) информации о геймдеве было куда меньше, чем теперь. И казалось, что игры делают какие-то небожители, причём преимущественно англоговорящие. Но само желание никуда не девалось. Несколько лет назад я стал пробовать себя в качестве писателя, и вот чуть больше года назад мы с Literal Team нашли друг друга)


Lesich
Давно, еще с 2007 года, я читал бумажный журнал (такое нынче редкость) "Игромания". Помимо статей с обзорами игр и новостями индустрии там также был раздел "Игрострой", в котором рассказывалось о разработке игр, предлагались руководства и приложения для этого. Тогда я начал понимать, что в современном мире есть возможность создавать игры самостоятельно, и это захватило мое воображение.


Но тогда особо ничего не выгорело (кроме того, что я случайно удалил кучу нужных файлов с родительского компьютера, пытаясь установить какую-то программу для создания 3д-моделей), и вернулся я к идее создать игру уже через десяток лет, когда мы с товарищами решили попытаться создать что-то свое. Так началась история Sovereign's Will точнее, тогда еще King's Ambitions.


GoooGooo
Сколько вас человек в команде?


Lesich
После трех изменений состава команды, сейчас у нас двое разработчиков (я и Артем) и переводчик @Arsonium. Недавно мы также договорились о сотрудничестве с Gloomy тоже в плане перевода.
Но ветераны команды остаются с нами и периодически дают ценные идеи!


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


Lesich
Мы руководствовались целью сделать то, что будет интересным и будет выделяться в огромном море инди в Стиме. Да, мы предполагали, что сразу начинать со стратегии будет сложно, но всё равно остановились на это выборе по двум критериям. Во-первых, мы решили, что таким образом избегаем кучу других сложностей скажем, анимирования или разработки ИИ противников. Именно стратегия лучше всего покрывалась умениями участников команды. А во-вторых, проект изначально задумывался как небольшая бесплатная игра, проба сил и оценка отклика. Но мы не смогли вовремя определить круг работ и остановиться..


graphomant
О выборе жанра вам расскажет Алексей я-то присоединился к разработке, когда проекту было уже несколько лет. Но что до меня, то я вообще не понимал, в какую сложную историю ввязываюсь ;) Я знал, что ребята делают стратегию, и поначалу мне казалось, что я буду, скажем, делать какие-нибудь описания зданий и юнитов. Очень боялся, что придётся рыться в исторических книгах %) Потом слово за слово выяснилось, что сеттинг около-фентезийный, что писать нужно будет гораздо больше, чем я думал (вдобавок переписывать всё, что было написано до меня). Но, если бы даже я всё это сразу знал, то согласился бы, даже с большей охотой. Такой формат даёт огромный простор для "литературщины" здесь тебе и сценарий, и биографии, и описания локаций, и ветвистые квесты о большем и желать не приходится! Это настоящая проверка для меня как для автора.


Lesich
Не несколько лет, а всего полтора года, это важно!


graphomant
(смеётся)


GoooGooo
Для своей игры вы создали целый мир, расскажите пожалуйста о нем вкратце.


Lesich
О, я расскажу, с чего все начиналось, а Артем расскажет, к чему пришло.


graphomant
Валяй


Lesich
Сейчас будет инсайд: изначально сеттингом игры должна была быть Русь. Примерно с Рюрика и до 18 века. Мы хотели сделать игру, раскрывающую детали российской истории и позволяющую проверить "а что, если". Скажем, а что, если центром объединения русских земель стал бы Новгород? А что, если бы в России установилась аристократическая республика?


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


Поэтому на общем совете было решено делать свой средневековый мир. Точнее, даже не совсем делать. Один из участников команды, Солар ведущий в форумных стратегических играх (это что-то вроде ДнД с мастером, но в виде стратегии). Он взял за основу придуманный им мир Владения империи, подчинившей весь мир. И дальше была долгая дорога с доработками и адаптацией.
Солар примерно через 6-7 месяцев покинул команду, какое-то время мы по инерции работали с его материалом, а дальше к команде присоединился Графомант. Тогда мы и начали прорабатывать то, к чему пришли сейчас.


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


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


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


GoooGooo
О_о, т.е. вы изначально замахивались на что то типа стратегий Парадоксов?


Lesich
Сейчас это выглядит смешно и самонадеянно, но да, замахивались. Я сам, бывает, посмеиваюсь над людьми, которые собираются на форумах, чтобы сделать "убийцу WoW" или ААА-шутер с открытым миром и грабежом корованов. Но мы сами действительно хотели сделать что-то вроде Europa Universalis, но с упором не на "песочницу", а на историю одного конкретного региона. Наивно было полагать, что если в ЕУ дать возможность играть только за одну страну, то она станет гораздо проще в разработке, но мы это поняли не сразу.
Поделюсь, что один из первых диздоков-игры иронично был сохранен как "Клон ЕУ.docx"


GoooGooo
Когда мечта начинает воплощаться, сталкиваешься с серьезными ограничениями. Понимаешь что все, о чем так мечталось сделать не удастся, потому что свободное время, которое можно посвятить разработке, не резиновое и есть ограничения в финансах и человеческих ресурсах которые можно привлечь. У вас есть классная статья на ДТФ, где вы рассказываете о "Суверене". Там вы пишите о том что с самого начала в разработке вы задали себе четкие рамки. Расскажите пожалуйста об этом подробнее.


Lesich
Конечно, говорить о том, что эти рамки были установлены "с самого начала" это несколько лукавить) Мы прошли долгий путь перед тем, как поняли, какую именно игру будем делать. Если бы мы делали платформер или, скажем, шутер, то было бы достаточно просто определить рамки того, что нам нужно создать. Скажем, в шутере это механика стрельбы, враги, уровни. Но в Суверене мы и сами не могли очертить жанр игры, поэтому простор был действительно безграничный. И это было большим минусом.
В итоге мы много обсуждали, пытаясь выявить суть игры, то, что мы можем дать игрокам на хорошем уровне, и что может выделить нас из тысяч других игр. Так и остановились на четырех критериях: нелинейности, феодализме, последствиях решений и политической борьбе. Обсуждая добавление какого-то элемента, мы оцениваем его соответствие этим критериям: поможет ли он раскрыть их? Не будет ли он чуждым в системе остальных элементов? Это здорово помогает понять, стоит ли овчинка выделки в каждом конкретном случае.


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


Lesich
вот так выглядит начало уже четвертого диздока по игре
imagehttps://i.imgur.com/uWU8eBF.png


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


Lesich
В основном исходя из наших возможностей, хах. У нас в команде нет художников, но, к счастью, и целевая аудитория нашей игры достаточно благосклонно относится в том числе к играм без мощной графической части. Если бы такой аудитории не было, нам не было бы и смысла что-то делать...


graphomant
О, мы это до сих пор решаем! В опросе, который прикреплён к демо-версии, мы отдельно спрашиваем, не показалось ли игроку, что текста слишком мало или слишком много. На самом деле, очень тяжело определить ту тонкую грань между твитом и простынёй текста. Люди читают очень по-разному, с разной скоростью, с разной, скажем так, толерантностью к словесным изыскам. Очень простой текст одному покажется приятным и понятным, а другого может разочаровать как слишком примитивный. Здесь мы опираемся только на собственное чутьё, потому что объективного критерия нет.


Lesich
В любом случае, мы постепенно стараемся сократить объем текстов.
Изначальные тексты ивентов, написанные еще Соларом, могли занимать две страницы текста...


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


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


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


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


Lesich
graphomant, ну ты морально готовься на всякий случай


GoooGooo
Lesich мы знаем что Вы по профессии профессор и Ваша работа заключается в том, что Вы гоняете на входе в ВУЗ студентов без масок.
graphomant а чем занимаетесь Вы?


graphomant
У меня скучнейшая офисная работа возможно, поэтому меня так и тянет к творчеству ;) Сейчас я работаю в интернет-рекламе.


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


Lesich
В основном совещаниями. Мы используем доску в Trello для отслеживания задач и их приоритетности. Это выглядит вот так:
imagehttps://i.imgur.com/c3h7hS6.png
Задачи сортируются по сферам, чем выше задача в списке тем выше ее приоритет.
Одно время я пользовался приложением Todoist, но через какое-то время оповещения "15 задач на сегодня, 72 просроченных" начали меня угнетать. Пришлось удалить.


graphomant
Это, конечно, отдельная боль. Когда я утром составляю план на день (да, я правда так делаю), у меня в списке вперемешку оказываются задачи по основной работе, задачи по разработке игры, бытовые задачи, покупки, какие-то личные дела. Как расставлять приоритеты вопрос вечный и нерешаемый. Иногда я больше отдаюсь разработке, иногда личным делам. Когда в одной сфере наступает перенапряжение, я просто переключаюсь на другую.


GoooGooo
Сколько часов в неделю вы посвящяете разработке?


Lesich
О, тут всегда по-разному. Иногда наступает такая апатия, что выходит всего 2-3 часа в неделю. А, например, перед выходом демо в Steam я сидел над ним по 4-6 часов в день. Последние два дня перед выходом по 10.
И в такие моменты ты кажешься себе просто властелином продуктивности, но не рекомендую таким злоупотреблять. Уже через несколько дней ты чувствуешь себя настолько опустошенным, что не будешь ничего делать совсем. И всё преимущество от этих переработок нивелируется.


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


GoooGooo
И как на такое массивное использование свободного времени реагируют жены/подруги?


Lesich
Нет жён нет проблем


graphomant
Мне в этом смысле повезло семья, друзья и подруги всецело одобряют мои художества.


Lesich
Между прочим, жена разработчика Stardew Valley несколько лет работала за двоих, пока он клепал свой инди-шедевр!


GoooGooo
Как вы можете оценить свою работу, что уже сделано, за что вы себя определенно можете похвалить, над чем работаете и что предстоит сделать?


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


graphomant
Мне кажется, что для столь маленькой команды, которая работает на идею и не имеет опыта разработки игр, мы держимся очень даже неплохо)


Lesich
Самое наглядное наше достижение, это то, как мы перешли от этого:
imagehttps://i.imgur.com/5c6jgwG.png
через это:
imagehttps://i.imgur.com/7sFT4np.png
к этому:
imagehttps://i.imgur.com/b5jKQcu.png


graphomant
К сожалению, прогресс текстовой части так наглядно не покажешь, но там тоже стало слегка получше, чем было


GoooGooo
graphomant Вы говорили что постоянно работаете над сокращением текстов, чтоб они лучше воспринимались игроком. Не жалко резать? Как Вы решаете, что и насколько сократить?


graphomant
Нет, не жалко. Помогает то, что я вожусь с текстами уже много лет и успел привыкнуть к тому, что резать и переписывать необходимо. Если человеку жаль редактировать свои тексты, то он либо не успел набраться опыта, либо просто графоман. Тут двух мнений быть не может редактировать и сокращать тексты так же естественно, как, скажем, заниматься монтажом отснятого материала для кино.
Сколько сокращать и что переделывать. Здесь ты либо видишь избыточность можно выразить ту же мысль более кратко, либо замечаешь какую-нибудь "занозу". Занозы бывают очень разные: неудачный ритм фразы, прорыв другого стиля в текст (например, канцеляризма), и так далее. По сути приходится многократно перечитывать текст, чтобы выровнять их все.


GoooGooo
Насколько я понимаю вы пытаетесь добиться того чтоб каждый персонаж, с которым будет взаимодействовать игрок был объемным, живым. Как вы это делаете?


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


И, конечно, мы поломали много копий на этом этапе!
imagehttps://i.imgur.com/qqsyRx7.png
Зато каждый персонаж теперь как родной.


graphomant
Любой автор к этому стремится. Благо, посредством текста можно очень подробно очертить образ происхождение, характер, реакции на события. Сейчас у нас 15 основных (геймплейных) персонажей, не считая закадровых. Мои предшественники собрали хороший набор архетипов: суровый военачальник, бюрократ, разбойник, рыжуха-пиратка Мне оставалось только создать им новые биографии в соответствии с архетипом и особенностями их вотчин. Некоторые нужно было только слегка подправить (творчество других нарративщиков иногда конфликтовало между собой, и тогда я выбирал вектор). Для некоторых написались целые небольшие рассказы об их детстве, юношестве и зрелости.


GoooGooo
О, я вижу в биографии много деталей, они играют какую то роль в прохождении игры?


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


Есть также расширенная биография в Кодексе со множеством деталей, которые как могут подсказать тактику поведения с этим персонажем (например, использовать его происхождение), так и служат для общего наполнения лора игрового мира.


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


graphomant
Мне кажется, что сделать сюжет нелинейным это такой священный грааль любого нарративщика. В случае дорогостоящих ААА-игр сделать это очень трудно, слишком уж велики затраты на контент, который отдельно взятый игрок может никогда не увидеть (хотя и тут есть редкие удачи например, Ведьмак 2 с двумя принципиально разными игровыми актами в зависимости от сделанного игроком выбора). В нашем же случае нелинейность всего лишь означает, что сценаристу нужно продумать и написать не один вариант развития событий, а, скажем, пять. нервно хихикает
Если говорить о конкретном воплощении, то здесь мы проделали некий путь. Поначалу мы пытались чертить огромные блок-схемы, но скоро стало понятно, что это очень неэффективно и трудозатратно само по себе. По сути ты становишься заложником инструмента, каким бы хорошим он не был. Разные студии решают эту проблему по-своему некоторые даже пишут собственные софты, чтобы держать и сопоставлять куски истории.


Lesich
Да, поддерживаю. У нас есть большое преимущество в этом плане. Сценарист Даинг Лайт и Мафии 3 как-то сказал: "Игры, над которыми я работаю, не состоят исключительно из текста. Как только в дело вступают арт, анимация и так далее, возможность постоянно разветвлять историю в духе книжек Выбери своё приключение у тебя немедленно пропадает".


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


graphomant
Если говорить ещё конкретнее, то нам пришло в голову адаптировать систему из игр Failbetter Fallen London, Sunless Sea и Sunless Skies. Там по мере прохождения персонаж получает условные "метки" побывал там-то, познакомился с тем-то, взял такое-то задание. Сейчас мы взяли курс на то, чтобы строить нелинейность на основе таких вот меток. К примеру, если игрок познакомился на балу с определенным персонажем, то он получает метку, и затем факт наличия (или отсутствия) этой метки выступает как триггер для события или эффекта. Это избавляет нас от необходимости создавать эти сумасшедшие блок-схемы. По сути вся нелинейность может укладываться в ассортимент таких вот меток.


Lesich
Вот, на этом скриншоте в правом окне часть меток события "Коронация":
image


GoooGooo
Но нелинейность в таком случае предполагает, что многие сюжетные линии пройдут мимо игрока. Вам не жалко затраченного труда?


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


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


Lesich
"хитро его имитировать"
Я так Волкинг Дэд от ТеллТейлов бросил...


GoooGooo
Хм т.е. у вас мир квази живёт своей жизнью?


Lesich
Жизнь мира строится фактически из двух элементов: это длань сюжета, которая перетасовывает персонажей и события, и некоторая полуавтоматическая системность: мы постараемся, чтобы персонажи взаимодействовали друг с другом, фракции боролись с игроком, а мировая обстановка влияла на состояние дел внутри маркграфства. Но пока эта система сыровата, и мы не выкатили ее тестерам. Кто знает, может, еще придется ее вырезать!


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


GoooGooo
Скажите, над чем вы работаете прямо сейчас и какие у вас виды на разработку игры в обозримом будущем?


Lesich
О, мы планируем сделать пост в Стим с описанием наших планов на ближайшие месяцы разработки, но могу выдать инсайды!


Сейчас мы работаем над глобальным событием "Заговор", в котором игрок будет противостоять, собственно, заговорщикам, а заодно посетит разные уголки своей страны. Это будет весьма нелинейно, потому что персонажей целых 15, а посетить можно успеть 4-5 максимум, и от того, кого игрок выберет, будет зависеть многое.


Также в планах доработка системы взаимодействия с персонажами (сейчас оно ограничивается только взятками. Это весьма реалистично для игры про огромную бюрократическую империю, но скучновато для игрока), создание полноценного древа указов (что-то вроде национальных фокусов в ХоИ) и системы взаимодействия игрового мира, которую я раньше упомянул. И это не говоря о туче менее важных, но запланированных доработок. Как можно видеть, с того момента, как мы начали делать игру, амбиции не сильно уменьшились!


graphomant
Прямо сейчас мы, наконец, закончили с проработкой сеттинга и дописали кодекс. Теперь предстоит снова начать с начала мы хотим сильно расширить и углубить первые игровые ивенты. В первую очередь они должны рассказать игроку о мире игры и персонажах и желательно не нудными выкладками-лекциями, а геймплеем. Для этого мы отправляем главного героя в поездку по его владениям. В связи с некими обстоятельствами ему будет нужно посетить нескольких персонажей, с каждым из которых будет связан отдельный нелинейный ивент. А все эти ивенты послужат решением одной глобальной проблемы. Об этом трудно говорить без спойлеров я надеюсь, скоро сами всё увидите.


Lesich
Глобальная цель выпустить таки эту игру! Потом, возможно, перейдем к следующей, потому что я втянулся в это. Инди-разработка это, всё-таки, больше не про цель, а про процесс, на мой взгляд. С каждой неделей я узнаю что-то новое о программировании, геймдизайне, графике. Я многому научился за это время. Собственно, это и путь постоянно развиваться и улучшать то, что мы делаем. А там посмотрим, к чему придем.
Очень вдохновляют отзывы и мнения об игре. Некоторые игроки не поленились и написали несколько абзацев с замечаниями и предложениями в форму для отзыва или в нашем чате. Каждый такой отзыв мотивирует продолжать работать.


graphomant
Поддерживаю. Я ничего не узнал о программировании и графике, но кое-что узнал о геймдизайне, а также, возможно, стал чуть лучше писать.


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


graphomant
Потому что искренне любим игры и хотим их делать) Наверное, вот так просто. Да и заработать вроде как можно, за книги-то вообще не платят!


Lesich
Ну, есть подозрение, что мы не совсем нормальные


А так да, я уже очертил причины: мне нравится делать игру, в которую я бы сам играл; меня очень поддерживают отзывы и мнения; я чувствую, как учусь на этой разработке и становлюсь лучше. Наверное, без разработки я уже не буду чувствовать себя целостным. Знаю, звучит как зависимость, но это не хуже зависимости от ММОРПГ!


Да, финансовый аспект тоже есть, было бы нечестно его отрицать. Но для меня это не "заработать миллионы", а "зарабатывать на разработке игр достаточно, чтобы заниматься этим как основной деятельностью".


graphomant
И да, мы определённо не совсем нормальные. И это нормально.


Интервью происходило в дискорде Literal Team https://discord.gg/gEjr7WB
Стим: https://store.steampowered.com/app/1047560/Sovereigns_Will/
Рассказ об игре: https://dtf.ru/indie/125620-on-eto-zapomnit-milord-nelineynost-i-intrigi-v-indi-proekte

Подробнее..

Из песочницы База данных на ScriptableObject c системой сейвазагрузки

14.09.2020 16:14:47 | Автор: admin

Введение


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


Для хранения таких данных существует много способов кто-то хранит их в таблицах, в xml или json файлах, которые редактируют собственными инструментами. Unity предоставляет свой способ Scriptable Objects (SO), которые мне нравится тем, что для их визуального представления не нужно писать свой редактор, легко делать ссылки на ассеты игры и друг на друга, а с появлением Addressables эти данные можно легко и удобно хранить вне игры и обновлять отдельно.


В этой статье я хотел бы рассказать о своей библиотеке SODatabase, с помощью которой можно удобно создавать, редактировать и использовать в игре (редактировать и сериализовать) scriptable objects.


Создание и редактирование SO


Создание и редактирование SOшек я веду в отдельном окне, которое чем-то похоже на окна проекта с инспектором слева находится дерево папок (папка, в которой находятся все SOшки группа в addressables), а справа инспектор выделенной SOшки.


Интерфейс


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


Создание SO


Создание новых SO происходит через нажатие кнопки в этом окне там нужно выбрать тип нужной сошки, и она создаётся в папке. Для того, чтобы тип SO появился в этом окне в качестве варианта, SO должен наследоваться от DataNode, который имеет лишь одно дополнительное поле к ScriptableObject


public string FullPath { get; }

Это путь к данной SO, с помощью которого к ней можно будет обратиться в рантайме.


Доступ к SO в игре


В рантайме обычно нужно либо получить какую-то конкретную модель, например, SO со списком настроек какого-либо окна, либо набор моделей из папки например, список айтемов, где модель каждого айтема представляет собой отдельный SO.
Для этого в static классе SODatabase есть два основных метода, которые возвращают либо весь список моделей из нужной папки, либо конкретную модель из папки с определённым именем.


public static T GetModel<T>(string path) where T : DataNode   public static List<T> GetModels<T>(string path, bool includeSubFolders = false) where T : DataNode

Уточню, что один раз в начале игры перед запросом моделей SODatabase нужно проинициализировать, чтобы обновились и прогрузились данные из Addressables.


Загрузка и сохранение


Один из недостатков ScriptableObject по сравнению с хранением данных с сериализацией в собственном формате является то, что в них нельзя записывать данные из игры в рантайме. То есть по сути ScriptableObject предназначены для хранения статичных данных. Но любой игре нужна загрузка и сохранение, и я реализую это через те же самые SO из базы данных.


Возможно это не идиоматичный способ совмещать базу статичных моделей игры с загрузкой и сохранением динамических данных, но в моём опыте ещё ни разу не было случая, когда это создало бы какие-то неудобства, но при этом есть ряд ощутимых плюсов. Например, с помощью тех же инспекторов SOшек можно смотреть игровые данные в эдиторе и менять их. Можно удобно загружать сейвы игроков, смотреть их содержимое и редактировать в unity, не используя никаких внешних утилит и собственных редакторов для визуализации xml или других форматов.


Я достигаю этого, сериализуя динамические поля в ScriptableObject с помощью JSON.


Класс DataNode родительский класс всех SO, хранящихся в SODatabase, помечен как


[JsonObject(MemberSerialization.OptIn, IsReference = true)]

и все его JsonProperty сериализуются в файл save.txt при сохранении игры. Соответственно при инициализации SODatabase кроме запроса данных об изменении addressables происходит JsonConvert.PopulateObject для каждой динамической модели из SODatabase, используя данные из этого файла.


Для того, чтобы это работало гладко, я сериализую ссылки на SO (которые могут являтся динамическими полями, помеченными как JsonProperty) в строку-путь, и потом десериализую обратно в ссылки на SO при загрузке. Есть ограничение данные на игровые ассеты динамическими быть не могут. Но это не фундаментальное ограничение, просто у меня ещё не было случая, когда такие динамические данные потребовались бы, поэтому я не реализовывал специальную сериализацию для таких данных.


Примеры


В классе-стартере игры инициализация и загрузка данных


async void Awake(){    await SODatabase.InitAsync(null, null);    await SODatabase.LoadAsync();}

и сохранение стейта при выходе


private void OnApplicationPause(bool pauseStatus){    if (pauseStatus)        SODatabase.Save();}private void OnApplicationQuit(){    SODatabase.Save();}        

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


public class PlayerSO : DataNode{    public static string Path => "PlayerInfo/Player";    [JsonProperty]    public string Title = string.Empty;    [JsonProperty]    public int Experience;}

Точно также для инвентаря игрока я создаю PlayerInventorySO, где храню список ссылок на айтемы игрока (каждый айтем представляет собой ссылку на статичный SO из SODatabase).


 public class PlayerInventorySO : DataNode {     public static string Path => "PlayerInfo/PlayerInventory";     [JsonProperty]     public List<ItemSO> Items = new List<ItemSO>(); }

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


public class QuestNode : BaseNode{    public static string Path = "QuestNodes";    //Editor    public virtual string Title { get; } = string.Empty;    public virtual string Description { get; } = string.Empty;    public int TargetCount;    //Runtime    [JsonProperty]    private bool finished;    public bool Finished    {        get => finished;        set => finished = value;    }}

Вообще, лучше поля с JsonProperty делать приватными, чтобы SO их не сериализовал.
Доступ к этим данным выглядит следующим образом


var playerSO = SODatabase.GetModel<PlayerSO>(PlayerSO.Path);var playerInventorySO = SODatabase.GetModel<PlayerInventorySO>(PlayerInventorySO.Path);var questNodes = SODatabase.GetModels<QuestNode>(QuestNode.Path, true);

Текущее состояние библиотеки


В продакшене несколько лет использовался прообраз этой библиотеки в ней аналогичное окно-проводник для создания/редактирования моделей, которые содержали статичные и динамические данные, но все эти модели не использовали SO, а были целиком в json. Из-за этого для каждой модели приходилось писать свой эдитор вручную, ссылки моделей друг на друга и игровые ассеты(спрайты и т.д.) делались довольно неудобными способами. Переход на SO совершён в прошлом году, и пока всего одна игра с SODatabase ушла в релиз, но в ней не использовались Addressables.


На addressables я перешёл совсем недавно для использования в текущем проекте (на разработку которой я ищу в команду второго программиста в партнёры). В данный момент идёт активное допиливание этой библиотеки под нужды этой игры.


Библиотека лежит в открытом доступе на github. Написана с использованием Nullable из c# 8, соответственно требует Unity 2020.1.4 в качестве минимальной версии.

Подробнее..

Перевод Остров читеров в Fall Guys

16.09.2020 10:17:24 | Автор: admin

Позвольте мне рассказать безумную историю. Она называется

Расцвет и упадок Острова читеров!

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

Убедившись, что всё работает, мы втайне запустили Остров читеров Fall Guys. Это было сказочное место, в котором читеры могли беззаботно сражаться друг с другом за Корону читера (вообще, это обычная корона, только подпорченная глубоким чувством вины и сожаления).

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

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

Поэтому если вас пометили как читера, то вы сможете закончить текущую игру. Но когда вы в следующий раз попытаетесь найти противников то сможете соревноваться ТОЛЬКО с другими читерами (не зная об этом). Поздравляю, вы этого добились. Вы попали в очередь на Остров читеров!

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

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


В процессе ожидания противников воспроизводится анимация падения персонажа игрока.

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

Долгое время в игре не было достаточно читеров, чтобы собирать из них целые игровые матчи. Мы постепенно понижали порог распознавания, и ловили всё большее количество читеров. Но всё равно матчей на Острове читеров пока не было. Читеры продолжали бесконечно падать и утверждали, что игра неисправна.

В этот момент читеры начали понимать, что могут объединяться в команды со своими друзьями, которые не пользовались читами, и участвовать в играх с ними Они сбегали с Острова читеров. О нет! Мы как можно быстрее пропатчили эту проблему, прежде чем все они успели это осознать.

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

Всё это время мы продолжали повышать порог распознавания читов. Мы были уверены на 100%, что если кого-то пометили как читера, то он ТОЧНО жульничает. Это было для нас чрезвычайно важно, мы не хотели ошибочно забанить ни одного игрока.

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

Один пользователь загрузил это видео на Reddit с подписью: Играли когда-нибудь в раунд Fall Mountain, в котором все игроки читерят? Проблема заключалась в том, что это было ПОХОЖЕ на Остров читеров, но мы не могли убедиться в этом на 100%.


Мы ЗНАЛИ, что матчи на Острове читеров проходят. Мы НЕ знали, там ли сняты эти видео. Поэтому мы закрыли Остров читеров. Теперь читеры просто не могут войти в игру. Мы постоянно работаем над совершенствованием порога и системы распознавания читов.

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

Наше следующее обновление называется BIG YEETUS AND ANTI-CHEATUS. Про Yeetus мы расскажем позже, а что касается Anti-Cheatus мы добавляем в игру Anti-Cheat компании Epic, который станет огромным шагом вперёд в развитии игры! Мы понимаем, что статья получилась очень длинной, но такова история Острова читеров. Именно поэтому нам приходилось сохранять секретность и умалчивать о подробностях!
Подробнее..

Из песочницы Анимация в KAPIA. Лучше один день потерять, чтобы потом за пять минут долететь

20.09.2020 18:23:47 | Автор: admin

Анимация в KAPIA


image

Добрый день! Меня зовут Павел. Я являюсь программистом и аниматором маленькой семейной команды по разработке игр. Я и моя жена делаем 3Д адвенчуру в постапокалиптическом мире. За время разработки мы изобрели свои технические велосипеды с которыми я хотел бы с вами поделится. Хочу начать с анимации и рассказать как мы ее делаем.

Уже много лет мы работаем в 3D пакете Softimage XSI. Хоть проект уже давно не поддерживается и закрыт, но мы до сих пор им пользуемся. Я думаю, что примерно такой же пайплайн возможно воспроизвести и в других пакетах. Поехали!

Риггинг



Для риггинга я использую Gear. Очень удобный и простой инструмент для автоматического риггинга персонажей.

image

Иногда для не человекоподобных персонажей приходится собирать риггинг вручную или модифицировать собранный с помощью Gear.

Анимация


Анимацию мы снимаем с помощью мокап костюма Perception Neuron. Все пропорции персонажей перенесены в программу от Axis Neuron (родная программа от Perception Neuron). Сначала мы пробовали использовать Motion Builder для чистки анимации. Но это отнимало слишком много времени, так как нужно использовать 3 программы(Axis Neuron -> MotionBuilder ->Softimage). Но затем стали использовать Softimage. Для этого был написан драйвер для Perception Neuron чтобы можно было его напрямую использовать в Softimage в реально времени. Чистить мокапы в Softimage оказалось очень удобно. Единственный минус это конечно проскальзывания ног. Но и этим мы быстро справились написав соответствующий плагин.

Затем чистим анимацию.

И немного пайплайна, как делаем это мы.

Процесс анимации выглядит так.

1. Сначала идет озвучка текста.

image

Как видите. Весь текст разбит по цветам(персонажы), а также у каждой фразы есть свое имя-номер(G11,R12,I13 и т.д.).

2. После озвучивания персонажей мы получаем аудиофайл, который затем помечаем маркерами.
И каждому маркеру даем то самое имя-номер.

image

3. После этого этапа идет запись мокапа и его чистка.

image

Для того чтобы анимация точно совпадала с маркерами из Adobe Audition маркеры выгружаются в csv файл такого вида:

Name Start Duration Time Format Type Description
Eye29 00:00:00:00 00:00:02:14 30 fps Cue
Ren30 00:00:02:26 00:00:04:29 30 fps Cue

4. Затем этот файл загружается скриптом в Softimage.

image

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

5.И далее все улетает в Unity. Вместе с файлом fbx формируется файл *.agrd

_Ren67;5832.0;5950.0
_Ren65;5601.0;5656.0
в котором лежит разметка анимации. С помощью класса наследуемого от AssetPostprocessor Unity автоматически нарезает всю анимацию, которая идеально совпадает с озвученным голосом.

Вообщем весь процесс не сложный и не отнимает много времени. На 10 минут анимации сейчас уходит примерно 5-6 часов (съемка, чистка, экспорт).

Далее идет автоматизация загрузки аудио и анимации в Unity.

Articy


Все диалоги построены в Articy:

image

image

Если посмотреть внимательнее на поля, то видно все те же названия маркеров.

Был написан импорт из Articy в Unity. Он автоматически считывает весь текст и вилки диалога и сопоставляет им аудиофайлы и анимацию так как названия аудио файлов, липсинка, анимации совпадают с названиями маркеров, которые считываются из articy.

image

Lipsync


Для липсинка мы используем Softimage Face Robot. Здесь есть очень удобный инструмент риггинга лица.

image

После того как риггинг построен подгружаются фонемы. Этот этап автоматизирован.

Всего их 25: 9 на звуки речи, 6 на движение и моргание глаз и 10 на мимику.

image

После риггинга голова с костями приклеивается обратно к модели. И снова делаем небольшой риггинг глаз. Чтобы можно было записывать их движение отдельно вместе с веками.

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

Далее персонаж грузится в unity. И там уже все это подгружается в lypsinc.

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

Вот так вкратце устроен наш пайплайн анимации мимики и персонажей в игре. С помощью
небольших утилит и скриптов получилось идеально скрестить Unity, Articy, Axis Neuron и Softimage.

И конечный результат:

Подробнее..

Работа и карьера геймдизайнера. Часть 2. Основы игровой механики

24.09.2020 04:10:20 | Автор: admin

В первом статье "Работа и карьера геймдизайнера. Часть 1. Что за зверь и с чем его едят?" мы немного разобрались, как нас видят HR, как они делают выбор, что означает работать в крупной компании и как круто (и стрёмно) быть инди. И ещё много о чём.


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



Налево пойдёшь Направо пойдёшь...


Первое направление это профессиональное. Я лучше рисую, более крутые арты, я быстрее делаю код, в котором меньше ошибок, я знаю больше языков.


Я теперь не только на javascript пишу, но и на java. Это любимая ненависть всех программистов, когда LinkedIn, ты пишешь себе в профиле программист java, приходит HR и говорит: Не пойдете ли вы к нам работать на javascript. Все программисты испытывают святой гнев, потому что это два разных языка с абсолютно разной логикой, применяющихся в разных целях. На java вы можете писать сервер. На javascript вы делаете интерфейс вебсайтов. Ничего общего. Вот это профессиональный рост.


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


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


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


Особенности подхода к карьерному и профессиональному развитию


Какие особенности подхода к карьерному и профессиональному развитию?


Типы руководителей. И, конечно, навыки, софт и хард скиллы. Личные навыки, профессиональные навыки для человека, который хочет быть геймдизайнером и заниматься геймдизайном.


Я вам посоветую быть мега упертыми. Упорство, настойчивость и целеустремленность это для геймдейва чуть ли не самое главное. Я смотрю на отличия такие социальные между своим поколением, нашим с Вячеславом Уточкиным и молодых людей, кому сейчас 16-20. У нас всегда была ценность этого карьерного развития, упорства вкалывать, работать больше-больше, делать, добиваться результатов. Почему подход настолько разный? Я не буду, конечно, в социологические аспекты углубляться, нам интересен результат.


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


Далее Константин Сахнов передаёт слово преподавателю игровых механик Владимиру Огарёву.


Основы игровой механики


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


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


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


Соответственно, как я уже и говорил, определений играм множество. Определения играм давали многие известные, как геймдизайнеры, так и в принципе люди, которые изучают игры в том, или ином виде. И в данном случае я могу озвучить некоторые определения.


Игра это деятельность, имеющая правила и включающая конфликт.


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


Довольно длинное описание игры это собственно от Грега Костикяна. Игра представляет собой форму искусства, кстати, впервые в определении используется слово искусство, применимо к играм, в котором участники, т.е. игроки, принимают решения о распоряжении ресурсами посредством игровых условностей для достижения цели. Ну да штука довольно громоздкая, поэтому заканчиваю я всегда цитатой Сида Мейера, я думаю, многие знают этого геймдизайнера, он недавно, кстати, книжку мемуаров выпустил. Человек, который придумал цивилизацию, Pirates и огромное количество игр. Собственно, игра это последовательность интересных выборов.


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


Ну, во-первых, игра это действительно деятельность, т.е. в игре ты что-то делаешь. В игре ты участник какого-то процесса, даже вот довольно популярные сейчас игры с нарративным фундаментом, где нужно просто ходить и слушать какие-то аудио дневники или смотреть какие-то вставки, типа какого-нибудь Firewatch, What Remains of Edith Finch и тому подобные вещи. Это все равно деятельность, мы все равно что-то делаем. Мы принимаем решения куда идти, мы принимаем решения куда смотреть, принимаем решения, как возможно реагировать на какие-то вещи.



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


Это, наверное, три основные вещи, которые можно вынести в определении игры, поскольку они используются и в других объяснениях того, что такое игра тоже, т.е. деятельность, решение. Почему решение игрока я выделил как конфликт? Потому что периодически, на самом деле, даже играя в какие-нибудь нарративные игры, типа The Wolf Among Us, Walking Dead и тому подобные вещи, принимая решения, игрок вступает сам с собой в конфликт, т.е. какое решение ему принять, помочь там какому-нибудь игровому персонажу, не помогать ему и бросить его на произвол судьбы.



Например, в том же The Wolf Among Us мне в свое время было очень сложно принимать негативные решения, когда я проходил игру второй раз для того, чтобы просто получить все достижения. Вначале я был хорошим копом, потом я проходил как плохой коп, и проходить как плохому копу мне было максимально некомфортно. Потому что я применял неприемлемые для себя решения, просто ачивки ради.



И собственно, возвращаясь к определению Сида Мейера, что это последовательность интересных выборов. В данном случае последовательность это деятельность какая-то, т.е. делая определенный выбор, мы движемся к следующему выбору.


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


Если выбор недостаточно интересный, то в нем нет конфликта. Грубо говоря, тот знаменитый скриншот из Life Is Strange, когда тебе нужно выбрать, что съесть в кафе, либо яичницу с беконом, либо, по-моему, панкейки или что-то такое. Понятно, что это ни на что не влияет. Наверное, я не уверен. Но мне кажется, данный выбор не особо интересен, поэтому не является конфликтным. Ну и выбор, поскольку у игрока нет бесконечного количества выборов, у него всегда есть выбор, который ему предоставляет геймдизайнер. Он выбирает из предложенных вариантов, поэтому они и продиктованы правилами игры. Т.е. мы не всегда можем принять какой-то выбор, который сами считаем правильным и выбираем из тех, которые нам предлагают.


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


Игра в первую это очередь система. Система подразумевает, что внутри игры существуют множество разных более простых сущностей, которые как-то взаимодействуют друг с другом. В данном случае можно привести аналогию из физики, когда все, что нас окружает, состоит из более мелких деталей, которые как-то с собой взаимодействуют.


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



Не всегда, конечно, но по большей части.


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


Они могут взаимодействовать через ресурсы, они могут взаимодействовать через принятие каких-то решений, они могут взаимодействовать через открытия какого-то нового контента, или новых механик и тому подобные вещи.


В данном случае мы рассматриваем игру как систему и у игры у нас существует несколько параметров этой системы.


Во-первых, это объект. Объект то, с чем происходит игра. Объект может быть, как физический, так и абстрактный. Самый простой вариант это мяч или шайба для игры в футбол или хоккей. Это могут быть собственно какое-то оружие и противники, которые взаимодействуют между собой. Это может быть машина и трасса, т.е. вариантов множество. У каждого из этих объектов существуют свои атрибуты, т.е. их характеристики и свойства. Т.е. у нас есть футбол и у него есть резиновый мяч, мы играем резиновым мячом. У нас есть футбол, и мы играем каучуковым мячом, который отлетает от различных поверхностей. У нас может быть чугунный мяч, мы можем играть машинами, как это сделано в Rocket League.



В данном случае атрибуты и объекты всегда обладают достаточно большим количеством вариативности, но по большей части они все-таки ограничены окружающим нас миром. Довольно странно делать, например, какую-нибудь игру, в которой взаимодействия между объектами сделаны не так, как в реальном мире, потому что это может отпугнуть игрока и сделать так, что он не поймет, в чем соль игры и зачем что-то делать.


Я плохо могу себе представить гоночную аркаду, где нужно ехать, как можно медленнее и как можно дальше отдалиться от соперника, в плане не обогнать его, а наоборот не доехать до него или оставить его перед собой это будет странно. Или тот же shooter, где тебе нужно бегать спиной, к примеру. Есть такие на самом деле представители жанра, но они скорее сделаны в виде каких-то артобъектов, т.е. это не игра в прямом смысле этого слова, это какая-то артинсталляция.


Далее, между объектами с определенными атрибутами есть отношения. Если говорить про мяч, то в данном случае мы можем бить по мячу с определенной силой, он куда-то отлетает, он может как-то взаимодействовать с окружающими объектами, например, с другими игроками, он может воздействовать на ворота, на какие-то стены, если мы играем внутри какого-то маленького помещения.


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


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


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


Объяснение было первое даже в том же Doom, потому что какой-то минимальный сюжет был добавлен в текстовый файлик, который находился на дискете вместе с игрой. Т.е. это не просто какой-то совершенно случайный чувак, который бегает с двуствольным ружьем и отстреливает демонов. Это было как-то объяснено, хотя бы в зачаточном состоянии. Понятно, что сейчас индустрия шагнула достаточно далеко вперед и нарратив уже является достаточно серьезным инструментом, с которым работают геймдизайнеры.



Но даже самые маленькие игры, типа того же Radical Fishing от Rami Ismail, где тебе нужно закинуть удочку в воду, пытаться уклоняться от рыб до тех пор, пока крючок не достигнет дна. После этого он начинает подниматься вверх и тебе нужно как можно больше рыбы поймать. И после того, как ты достигаешь поверхности воды, рыба взлетает вверх в воздух и тебе нужно её отстреливать из различного рода оружия. Даже в этой игре есть прекрасный контекст.


И в своё время на конференции в DevGAMM он рассказывал о том, что они сделали достаточно крупную книжку, в которой описывали, что происходит в игре. Они называли ее игровой библией. Там описывалось, что за персонажи, что за локации, что за рыба, как эта рыба взаимодействует друг с другом, почему есть некая преемственность рыб из одной локации в другую, иногда ты мог находить какие-то под водой объекты, типа сундуков и прочей ерунды, которые тоже объяснялись, откуда были взяты и как они там появились. Вообще сюжет крутится вокруг того, что главный герой мстит за отца.


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


Игровая механика. Как я уже говорил, их в игре обычно очень много. Они как-то взаимодействуют между собой. Даже если говорить про тот простейший вариант shooter. Даже в shooter количество игровых механик достаточно большое именно кор-геймплее. Т.е. в shooter есть механика перезарядки, есть механика прицеливания, есть механика перемещения. Мы можем убрать механику прицеливания и у нас будет автоприцел. Мы можем убрать механику перемещения и у нас будет тироподобная игра.



В Gears of War одной из фишек было добавление дополнительной степени свободы, связанной с перезарядкой. Т.е. мы перезаряжали оружие, и оно перезаряжалось какое-то время, пять секунд. Но если в процессе перезарядки вовремя нажать кнопку, то у нас перезарядка будет не пять секунд, а три. Это было довольно интересное геймдизайнерское решение. И тот же Клифф Блезински в свое время писал о том, что это очень сильно изменило динамику игры и повлияло на достаточно большое количество параметров внутриигровых, и вообще на динамику игры. Хотя всего лишь просто добавил дополнительный ритмгейм в процесс перезарядки. Но поскольку процесс перезарядки находится внутри других игровых механик, т.е. в тот момент, когда ты перезаряжаешься, на тебя прут монстры и стреляют в тебя, и тебе не до ритмгейма, ты там не Without You в оригинальном варианте от Badfingers на гитаре играешь в этот момент.



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


Дальше изменения. Понятное дело, что в игре все происходит в динамике. Мы не можем представить себе игру, где после нажатия на кнопку или после взаимодействия с каким-то игровым объектом ничего не происходит. Обычно что-то меняется. Разбили камень, собрали какую-нибудь травинку, или что-то в таком духе. И всегда, собственно это принцип обратной связи, так называемый. Когда игрок что-то делает в игре, что-то должно поменяться, это относится как к игровым механикам, так и вообще ко всем действиям в игре, будь то нажатие кнопки, будь что угодно на самом деле.


Третье это игровая среда. Игровая среда это то, что окружает все то, что мы делаем в игре. Т.е. это уровни в Doom, различные многоуровневые помещения, это может быть поле для игры в футбол, оно как-то ограничено, у него есть какие-то определенный элементы близкие к воротам, далекие от ворот, ворота могут стоять как-то по-другому или ворота могут быть развернуты спиной к игрокам и нужно как-то исхитриться, закинуть мяч рикошетом, что-то в таком духе. Опять же игровая среда это футбол в бассейне под водой.


И цикличность. Цикличность это повторение игровых механик. Если игровая механика запускает какие-то изменения в игровой среде, то важно понимать, что эти изменения цикличны. Т.е. мы бежим по локации, стреляем в монстров, убиваем их, бежим дальше, стреляем в монстров, они продолжают умирать, нас могут убить. То же самое с футболом. Мы ударили по мячу, попали в ворота, нам зачли гол, потом мы продолжаем играть, чтобы сделать больше голов, чтобы соответственно выиграть. И в данном случае я всегда провожу аналогию с механическими часами и тем, что в игре есть достаточно много механик, которые как-то между собой взаимодействуют, у них может быть разная динамика. Например, можно вспомнить механику стрельбы в том же Bioshock и механику взлома дверей. Это совершенно две разные механики, они совершенно различные по динамике и в принципе. Когда мы взламываем двери в Bioshock, нам нужно просто соединять трубы, по которым движется жидкость это совершено не то, что отстрел монстров и использование плазмидов.


То же самое в часах. В часах может быть много разных шестеренок, шестеренки могут быть абсолютно разные по размеру, шестеренки могут двигаться с разной динамикой, какие-то крутятся медленно, какие-то быстро, но в итоге все движение шестеренок и все их размеры и то, как они между собой связаны, направлены на одну единственную цель, чтобы часы показывали правильное время. Собственно, здесь игровые механики работают примерно так же, как шестеренки, и единственная их цель это приносить удовольствие от игры в том или ином виде. И то, как динамична та или иная механика, какой процент от геймплея она занимает. Например, в GTA навскидку тяжело сказать, какой процент в геймплее перестрелки, а какой процент погони, и вообще перемещение по городу. Так что в какие-то моменты может быть перекос в одно, в какие-то моменты перекос в другое, какие-то миссии больше направлены на перестрелки, в какие-то больше на погони. Соответственно, эти шестеренки игровые, их размеры тоже динамические и всегда меняются.


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


Там выбраться из подземелья, убить определенное количество монстров или прожить N время. Неважно.


Важную роль здесь играет сложность. В свое время было сформулировано такое понятие, как FU-фактор, которое говорит о том, какая сложность оптимальная для того, чтобы игрока вовлечь максимально в процесс и проще всего создать состояние потока, о котором многие говорят, про которое можно много где прочитать, но практических каких-то советов о том, как проще достигнуть этого состояния потока не так много.


FU-фактор это один из них. Это тот случай, когда мы создаем некоторые бугры, связанные с повышенной сложностью, после которых всегда есть какая-то релаксирующая часть. Но в итоге эти бугры с каждым разом все выше и выше, и в итоге игрок как-то прогрессирует через постоянные повторения одного и того же действия по сложности, по своим скиллам, по прогрессу и т.д. Одним из хороших примеров мне кажется серия игр Uncharted, в которой есть моменты с перестрелками, есть моменты с решением каких-то головоломок и перемещениями по каким-то сложным уровням.


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


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


Например, у меня есть снайперка, и я убиваю ей персонажей с дальнего расстояния, а потом я могу стрелять из снайперки по каким-нибудь тросам, для того, чтобы скинуть на противников булыжник, ящик, контейнер. Об этом мы поговорим чуть позже.


Это наслоение механик и наслоение игрового опыта друг на друга.


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


Это может быть бумажный прототип, это может быть какой-то собранный из квадратиков, треугольников какая-то штука в Юнити. В общем, в любом случае только прототип является конечным мерилом того, насколько игровая механика хороша или плоха в том или ином виде. Но есть какие-то вещи, которые можно понять ещё до создания прототипа. Во-первых, игровая механика должна быть простой и понятной. Игровую механику легко освоить за несколько секунд. И довольно важный критерий, который пришёл в геймдизайн из маркетинга это то, что механику можно интуитивно понять, глядя на скриншот игры. В данном случае это очень часто используется для создания каких-то промороликов, скриншотов и тому подобных вещей, которые относятся больше к маркетингу, чем к геймдизайну. Но при этом это очень хорошая лакмусовая бумажка, которая поможет понять, достаточно ли проста и легка механика, т.е. понятно ли по скриншоту, что здесь делать. В данном случае shooter, все понятно. Видно, что у нас есть какие-то животные, чтобы помогать нам в бою, есть самолеты, есть машины, т.е. у нас есть возможность пользоваться каким-то видом транспорта, есть разные виды оружия и все такое. И соответственно разные какие-то постройки на локации, есть деревья. Эффекты огня и прочего.


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


Я очень часто привожу в пример игру Water Stone, это довольно старая аркадная гонка. В ней игрок выбирает сначала какое-то транспортное средство, которое он будет гонять в том или ином заезде. Это может быть грузовик, это может быть мотоцикл, это может быть байк, это может быть все, что угодно. И соответственно, сами трассы построены таким образом, что там есть множество развилок и эти развилки имеют разный материал. То есть ты можешь ехать по песку, ты можешь ехать по какой-то непересеченной местности, можешь ехать по трассе, ещё как-то. И соответственно выбор твоего транспортного средства напрямую влияет на то, какой тебе нужно выбирать путь для того, чтобы максимально использовать плюсы ландшафта, для твоего конкретного транспортного средства. Там могут быть какие-то очень узкие проезды, для которых подойдет только мотоцикл, там может быть достаточно короткая береговая линия, которая позволит срезать достаточно нехилое расстояние, но по береговой линии лучше всего перемещается багги. Соответственно, выбирая транспорт, ты выбираешь стратегию поведения. И в данном случае это как раз та механика, которая вызывает яркие эмоции, в том плане, что в начале, когда ты начинаешь играть и возможно этого не ожидаешь, выбрал мотоцикл, начал заезд, и тут решил поехать по береговой линии, утонул в песке и мимо тебя со свистом пролетел какой-то багги другого игрока. И ты сразу начинаешь понимать, по песку лучше на багги ездить, а на мотоцикле не стоит.


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


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


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


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


Игровая механика должна создавать у игрока ощущение роста, ощущение прогресса. Позволяет более эффективно решать какие-то игровые цели и задачи, которые ставятся перед игроком. Ну и естественно, у механики должна быть какая-то глубина, для того чтобы игрок рос дальше в своих навыках. Вот это как раз сильная сторона компании Nintendo с их играми. Что Zelda, что Mario, я даже не знаю, любую на самом деле игру от Nintendo возьми, они довольно неплохо умеют работать как раз с этой частью, с этим критерием игровой механики.


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



Если говорит про Zelda, то во время прохождения игры, игрок получает разные инструменты типа магнитов, типа всяких шестов, ещё что-то, много разных инструментов и проходит различные подземелья, используя эти инструменты или какое-то сочетание этих инструментов между собой. Опять же, эти сочетания являются достаточно очевидными, игрок понимает, что если есть какая-то железная штуковина, то он может передвинуть её с помощью магнита. Не бывает так, что он деревянные вещи двигает с помощью магнита или воду перемещает с помощью магнита. Это как звуковая отвертка у Доктора Кто, на дерево не работает. Соответственно, в Mario то же самое. Мы можем как-то даже комбинировать различные игровые механики, чтобы достигать каких-то игровых целей.


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


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


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


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


Если говорить о механиках гениальных, то это 2048, FlappyBird и прочие истории и вообще в принципе гиперказуальные игры, которые сейчас довольно популярны. В них как раз для того, чтобы сделать популярной, как мне кажется, гиперказуальную игру, нужно создать как раз аддиктивную механику. И тут как раз работа основная геймдизайнера это придумать что-то залипательное, что-то такое, во что хочется возвращаться. Coin Master, она вроде совершенно простая и бесхитростная, но при этом довольно залипательная, да и вообще много таких вещей. Я периодически за собой замечаю, что залипаю в игру, в которой максимально спинно-мозговая деятельность, но при этом она удовлетворяет всем этим критериям. Намного больше и точнее вы узнаете на курсе "Менеджмент игровых проектов", преподаватели которого и делились премудростями в этой статье.


В следующей части статьи мы поговорим об архитектуре в геймдизайне. Не переключайте канал, пока идёт реклама, будет интересно. :)


P.S. Вспоминая о времени, сейчас не только 3:21 минут по Москве, но и уже 24 число. До начала онлайн-интенсива Kubernetes База осталось совсем ничего. Он пройдёт 2830 сентября. Хорошее начало для перспективной профессии или значительного апгрейда уже существующей.

Подробнее..

Перевод Генерация подземелий в Binding of Isaac

25.09.2020 10:10:53 | Автор: admin

Binding of Isaac и её ремейк Binding Of Isaac: Rebirth одни из самых любимых для меня игр. Они относятся к жанру roguelite twin stick shooter и очень похожи на Enter the Gungeon.

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

Хоть мне и пришлось провести декомпиляцию, а также освежить свои покрывшиеся пылью знания о Flash (когда-то я написал собственный декомпилятор Actionscript), мне ещё и очень повезло: разработчик Isaac Флориан Химсл и один из основных разработчиков Rebirth Саймон Парзер с радостью ответили на мои вопросы. На самом деле, Флориан даже недавно записал видео с описанием алгоритма. На его канале можно также узнать подробности разработки его новой игры Squid Invaders.

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

Базовый алгоритм


Разработчики Isaac активно вдохновлялись играми серии Zelda в 2D и генерировали карты, похожие на их подземелья.


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

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

Первую версию Isaac разработали менее, чем за 3 месяца, поэтому Химслу приходилось невероятно эффективно использовать своё время. Фундаментальный дизайн игры изящен и прост. Сначала генерируется план этажа (уровня). Затем некоторые комнаты выбираются в качестве особых. Потом из соответствующего пула выбирается интерьер каждой комнаты.

План этажа


Isaac генерируется на сетке размером 98. Для удобства ячейки обозначаются числами единицами обозначают позицию по X, десятками позицию по Y. Это означает, что можно двигаться вверх, вниз, влево и вправо, просто прибавляя +10, -10, +1 и -1. Ячейки с позицией по X, равной 0, не используются (всегда пусты), и это значит, что большей части кода не нужно беспокоиться о границах карты. То есть верхняя левая ячейка карты имеет обозначение 01, а нижняя правая 79.

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

Сначала по формуле random(2) + 5 + level * 2.6 определяется количество комнат. Т.е. уровни начинаются с 7 или 8 комнат, и каждый раз увеличиваются на 2 или 3 комнаты.

Затем игра помещает начальную комнату (ячейка 35) в очередь. Далее она циклически обходит очередь. Для каждой ячейки в очереди она циклически обходит 4 основных направления и делает следующее:

  • Определяет соседнюю ячейку, прибавляя к текущей ячейке +10/-10/+1/-1.
  • Если соседняя ячейка уже занята, то игра ничего не делает.
  • Если сама соседняя ячейка имеет более одного заполненного соседа, то игра ничего не делает.
  • Если на уровне уже достаточно комнат, игра ничего не делает.
  • Игра ничего не делает с вероятностью в 50%.
  • В противном случае игра помечает соседнюю ячейку как содержащую комнату, и добавляет её в очередь.

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

В случае, если на картах требуется больше 16 комнат, начальную комнату периодически снова помещают в очередь, чтобы стимулировать рост.

Так как описанный выше алгоритм начинается с единственной комнаты и многократно расширяется наружу, он, по сути, является исследованием в ширину (breadth first exploration). Ограничение, не позволяющее добавить комнату, если уже есть два соседа, разделяет комнаты на отдельные коридоры, которые никогда не объединяются в петли.

Затем план этажа проверяется на согласованность. В нём должно содержаться нужное количество комнат, а комната босса не должна находится рядом с начальной комнатой. В противном случае генерация начинается заново.

Особые комнаты


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

Затем указывается положение секретной комнаты. Эти комнаты добавляются в план этажа; они являются одними из немногих исключений из правила, запрещающего располагать комнаты рядом с несколькими уже существующими. На самом деле, алгоритм наоборот, предпочитает их размещать их так. Генератор случайным образом ищет пустую ячейку, находящуюся рядом с не менее чем тремя комнатами и не рядом с любой из конечных комнат. Если он не находит её спустя 300 попыток, то слегка ослабляет критерий поиска, а после 600 попыток ослабляет его ещё больше. Эта процедура гарантирует, что секретная комната всегда будет размещена на уровне, но обычно они зажаты рядом с перекрёстками, то есть рядом с ними всегда много комнат.

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

Обычные комнаты


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

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

Для обычных комнат существует три пула: лёгкий, средний и сложный. Первый этап главы выбирает из простых и средних комнат, а второй из средних и сложных. Первая глава (Basement) содержит в пулах 174 обычных комнат. Альтернативные главы, например, Cellar, которая случайным образом может заменить Basement, имеют немного отличающийся набор комнат.

Проклятие лабиринта


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

  • На 80% больше обычных комнат (максимум 45)
  • Для особых комнат используются только 6 дальних конечных комнат
  • Уровни выбирают комнаты из пулов простых, средних и сложных комнат.
  • В план этажа случайно добавляются дополнительные обычные комнаты с логикой размещения, как у секретных комнат.

Демо


Я создал на Javascript упрощённый пример генератора, чтобы можно было с ним поэкспериментировать. Полный код можно найти здесь, а работающий пример в оригинале статьи.


Rebirth



Binding of Isaac: Rebirth это ремейк оригинального Binding of Isaac, созданный компанией Nicalis, которая в то время была известна своими портами VVVVV и Cave Story. Игру портировали на C++ и переделали все звуки и графику. За годы существования игра получила множество DLC, добавивших новых предметов и врагов к и так уже достаточно впечатляющему списку оригинала.

Хотя в Rebirth есть куча интересных нововведений, основным вкладом в генерацию уровней стало добавление более крупных комнат неправильной формы.


С полным набором DLC (на момент написания статьи это Afterbirth+) в игре есть 11 больших комнат: 22, 21, L-образные и узкие коридоры в разных вариантах поворота.


Типичная L-образная комната, в три раза больше обычной комнаты. Она была реализована Саймоном Парзером благодаря аккуратной модификации исходного кода Химсла.

Вместо обхода в цикле всех направлений алгоритм обходит все выходы из комнаты. В комнате размером 22 их может быть до восьми.

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

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

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

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


В Isaac пропасти обычно пересекать невозможно

Вывод


Генератор уровней Isaac не самый сложный из тех, которые я видел, но несмотря на такой малый объём кода, работает он невероятно хорошо. Вероятно, именно поэтому его так часто пытаются воссоздать. Его простота позволяет вносить изменения и расширения, и мы видим это на примере Rebirth. Невероятный результат.

Также можно заметить, что эта игра продолжает тенденцию, в соответствии с которой план этажа генерируется отдельно от деталей комнаты. В своих статьях о Diablo 1 [перевод на Хабре] и Enter the Gungeon [перевод на Хабре] я говорил, почему такой подход может быть очень мощным.

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

Далее вы можете посмотреть серию видео Химсла о внутреннем устройстве игры или даже сыграть в Isaac и увидеть все уровни вживую. Я слышал, что новый DLC Repentance выйдет в этом году. Также я рекомендую сыграть в другие игры главного дизайнера Эдмунда Макмиллена (особенно в Super Meat Boy).
Подробнее..

Категории

Последние комментарии

© 2006-2020, personeltest.ru