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

Игровой движок

Перевод Обучение технологии ray-casting. Часть 2

04.09.2020 00:21:28 | Автор: admin

Приветствую тебя, читатель. Это вторая часть серии переводов работы "Ray-Casting Tutorial For Game Development And Other Purposes". Второй части предшествует первая, которую так же можно просмотреть.

Начнем! Продолжим!

Ограничения ray-casting

Технология ray casting быстрая потому, что она использует некоторые геометрические ограничения. В большинстве случаев стены всегда перпендикулярны полу (заметьте, что это не касается углов между самими стенами).

Отсюда мы получаем еще одно ограничение: точка обзора не может вращаться вдоль оси Z (которую мы все знаем, как ось аппликата-прим.пер.). Почему? Представим обратное: вращение разрешено. В таком случае стены могут наклоняться, и соответственно теряется преимущество отрисовки вертикальных срезов этих самых стен. Ограничение во вращении является одной из причин, почему технологию ray casting нельзя считать полноценным 3D.

Технология ray-casting позволяет игроку двигаться вперед/назад и поворачиваться вправо/влево, но не вращаться вокруг оси Z (такой тип вращения называется креном (если ось Y вертикальная, то вращение по ней-тангаж, а по оси X - рыскание, - прим.пер.)).

Шаг 1: Создание окружения

Для то, чтобы отобразить работу ray casting, мы создадим сцену в виде лабиринта, которая будет основываться на следующих геометрических ограничениях:

  • Стены всегда перпендикулярны полу

  • Стены представляютсобойодинакового размера кубы

  • Пол всегда лава плоский

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

Перед тем как продолжить, мы должны определить систему координат (далее-СК), чтобы далее не было каких-либо недопониманий. Наша СК изображена на следующем рисунке.

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

Шаг 2. Определение атрибутов проекции

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

  • Высота игрока, поле зрения игрока (FOV - Field Of View), и координаты игрока

  • Размеры плоскости проекции

  • Как соотносятся игрок и плоскость проекции

Игрок должен иметь возможность видеть то, что находится напротив него. Для этого мы объявим FOV, который определит, насколько широкая область сцены доступна для наблюдения игроком. Люди имеют FOV в 90 и больше. Тем не менее, такой угол не совсем подходит для экранов ПК. Мы выберем 60, которые были получены путем экспериментов и проб (насколько хорошо выглядит картинка с таким-то углом FOV). Высоту игрока определим, как 32 единицы. Это вдвое меньше, чем стены, что является разумным допущением. (если взять реальное соотношение средней высоты помещения к среднему росту человека-самца, то получим: 2.4/ 1.7 = 1.41 - прим.пер.).

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

Чтобы поместить игрока в наш мир, нужно для этого игрока определить X-координату, Y-координату и угол поворота. Эти 3 атрибута формируют POV (point of view) игрока.

Если предположить, что игрок где-то по центру координатной сетки (1, 2), и его угол поворота равен 45 относительно начала оси, то FOV будет выглядеть примерно, как на картинке ниже. (Каждый элемент сетки имеет размеры 64x64 единиц).

На картинке слева мы видим POV, которая представляет собой координаты игрока и его угол поворота. На картинке справа - FOV - область видимости игрока. - прим.пер.

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

Когда POV игрока спроецирован на плоскость, сцена будет выглядеть, как на картинке ниже.

Как только нам стали известны FOV и размеры плоскости проекции мы можем посчитать угол между последующими лучами и расстояние между игроком и плоскостью проекции.

Что мы имеем:

На картинке слева мы видим плоскость проекции того, что видит игрок (это же экран нашего монитора): она имеет размеры 320х200 пикселей и центр в точке (160, 100). На картинке справа представлен вид "из глаз игрока". - прим.пер.

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

FOV равен 60. При этом мы видим плоскость проекции шириной в 320 пикселей, то есть 60 соответствуют 320 столбцам и, следовательно, 1 столбец равен 60 / 320 градусам. - прим.пер.

FOV и плоскость проекции образуют равнобедренный треугольник. Если к плоскости проекции провести высоту, то мы получим два прямоугольных треугольника с одной стороной в 320 / 2 = 160 единиц и углами: 90, 60 / 2 = 30 и 90 - 30 = 60 соответственно. Используя формулы тригонометрии мы находимдлинувысоты (277), которая и является расстоянием между игроком и плоскостью проекции. - прим.пер.

По итогу нам известно:

  • Размеры плоскости проекции (экрана) = 320х200 единиц

  • Центр плоскости проекции (160, 100)

  • Расстояние до плоскости проекции = 277 единиц

  • Угол между последующими лучами = 60 / 320 градусов

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

Конец второй части

Подробнее..

Перевод Базовые концепции Unity для программистов

24.11.2020 16:19:42 | Автор: admin
Привет, Хабр! При проработке темы Unity мы нашли интересный блог, возможно, заслуживающий вашего более пристального внимания. Предлагаем вам перевод статьи о базовых концепциях Unity, также опубликованный на портале Medium


Если вы обладаете опытом программирования и пытаетесь вкатиться в разработку игр, порой бывает непросто найти такие учебные материалы, в которых в достаточной степени объясняется необходимый контекст. Вероятно, придется выбирать между материалами, в одних из которых описана парадигма ООП, в других язык C# и концепции Unity, либо сразу начинать с продвинутых руководств; в последнем случае придется самостоятельно дедуктивно выводить базовые концепции.

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

Я начал путь в программировании около 17 лет назад, открыв для себя Game Maker. Многие часы потратил на самостоятельное программирование маленьких игр и инструментов, в процессе всерьез увлекшись программированием.

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

Сцена


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

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



Редактор сцен Unity, в котором загружена задаваемая по умолчанию пустая сцена в режиме 3D. В пустых сценах Unity3D по умолчанию содержатся объекты Main Camera (Главная Камера) и Directional light (Направленный свет).



Пример сцены в редакторе Unity; здесь выделено несколько объектов. Такое представление сцены можно использовать для редактирования уровней в игре.

Каждый игровой объект в Unity должен находиться в сцене.

Игровые объекты


Игровой Объект (в коде GameObject) один из базовых кирпичиков, из которых строится игра.

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

У каждого игрового объекта есть значения положения и поворота. Для метафизических объектов они не имеют значения.

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



Группа объектов, совместно вложенных в сцене и объединенных в пустом объекте Interior_Props, сделано в целях структурирования

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



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

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

Компоненты (и моноповедения)




Объект Warrior с предыдущего скриншота показан над окном Инспектор в интерфейсе Unity. Каждый из проиллюстрированных разделов (напр., Animator, Rigidbody, Collider) это компоненты, слагающие этот объект

Каждый игровой объект состоит из компонентов.

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

  • У единственного видимого элемента машины будет компонент Renderer, который отрисовывает машину и, вероятно, компонент Collider, задающий для нее границы столкновений.
  • Если машина представляет персонажа, то у самого объекта car может быть Player Input Controller (Контроллер ввода от персонажа), принимающий все события, связанные с нажатиями клавиш, и транслирующий их в код, отвечающий за движение машины.

Притом, что можно писать большие и сложные компоненты, где компонент 1 в 1 равен кодируемому объекту (напр., компонент player содержит код, полностью описывающий персонажа, а компонент enemy, в свою очередь, полностью кодирует противника) обычно принято извлекать логику, дробя ее на небольшие обтекаемые кусочки, соответствующие конкретным признакам. Например:

В коде есть MonoBehavior, вездесущий родительский класс для представления компонентов. Большинство невстроенных компонентов будут наследовать от MonoBehavior, который, в свою очередь, наследует от Behavior и Component, соответственно.

  • Все объекты, обладающие здоровьем, будь то Player (Игрок) или Enemy (Враг) могут иметь компонент LivingObject, задающий исходное значение здоровья, принимающий урон и приводящий в исполнение смерть, когда объект умирает.
  • Кроме того, у игрока может быть компонент ввода, контролирующий сообщаемые ему движения, а у врага может быть аналогичный компонент, реализованный при помощи искусственного интеллекта.

На протяжении жизненного цикла компоненты получают различные обратные вызовы, которые в среде Unity именуются Сообщениями. К Сообщениям относятся, в частности, OnEnable/OnDisable, Start, OnDestroy, Update и другие. Если объект реализует метод Update(), то этот метод будет как по волшебству вызываться Unity в каждом кадре игрового цикла, пока объект активен, а заданный компонент действует. Эти методы могут быть помечены private; в таком случае движок Unity все равно будет их вызывать.

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

Ресурсы


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

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



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

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

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

Шаблонные экземпляры


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

Вложенные шаблоны


Начиная с Unity 2018.3, поддерживается вложение шаблонов, чего и следовало ожидать:

  1. Родительский объект с дочерними объектами, представленными в виде шаблонов, сам может быть представлен в виде шаблона. Внутри родительского шаблона дочерний шаблон допускает собственные модификации. В сцене инстанцируется сразу вся иерархия шаблонов, а поверх нее также могут надстраиваться модификации, специфичные для конкретной сцены.
  2. Шаблонный экземпляр, находящийся в сцене и снабженный собственными локальными модификациями, может быть сохранен как самостоятельный ресурс Prefab Variant. Этот вариант представляет собой шаблонный ресурс, наследующий от другого шаблона, поверх которого применены дополнительные модификации.

Эти концепции компонуются: возможен шаблонный вариант вложенного шаблона или, например, шаблонный вариант шаблонного варианта.

Сериализация и десериализация


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

Поток сериализации/десериализации, действующий в движке Unity, загружает в память ресурсы, расположенные на диске (в вашем проекте: для редактирования или тестового прогона игры, либо в самой игре, при загрузке сцены) и отвечает за сохранение состояния отредактированных вами объектов и компонентов обратно в соответствующие сцены и шаблонные экземпляры.
Следовательно, система сериализации также является ключевым элементом работы с редактором Unity. Чтобы MonoBehavior мог принять ввод при конструировании сцены в ходе ее инициализации, эти поля должны быть сериализованы.

Большинство базовых типов Unity, в частности, GameObject, MonoBehavior и ресурсы поддаются сериализации и могут получать исходные значения при создании прямо из редактора Unity. Публичные поля в вашем MonoBehavior сериализуются по умолчанию (если относятся к сериализуемому типу), а приватные поля для этого сначала нужно пометить атрибутом Unity [SerializeField], и тогда они тоже могут быть сериализованы.


Скриншот игры Chaos Reborn производства Snapshot Games, 2015 год. BY-CC-SA 3.0

Заключение


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

Перевод От int main() до BeginPlay как происходит инициализация Unreal Engine под капотом

24.03.2021 14:22:50 | Автор: admin

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

Но когда вы пишете игровой код на Unreal Engine, вы не имеете дело с игровым циклом напрямую. Вы не начинаете работать сразу с основной функцией сначала вы определяете подкласс GameMode и переопределяете функцию под названием InitGame. Или пишете одноразовые классы Actor и Component и переопределяете их функции BeginPlay или Tick для добавления собственной логики. Это самый минимум того, что вам нужно сделать: обо всем остальном движок позаботится за вас.

Unreal Engine также предлагает вам как программисту большую мощность и гибкость: конечно, он имеет открытый исходный код, но также возможно и расширение несколькими другими способами. Даже если вы только начинаете работать с этим движком, было бы не лишним получить представление о его GameFramework: о таких классах, как GameMode, GameState, PlayerController, Pawn и PlayerState.

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

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

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

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

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

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

Все начинается в модуле Launch, где вы найдете различные основные функции, определенные для разных платформ. В конце концов, все они находят свой путь к функции GuardedMain в Launch.cpp. Где-то здесь как раз можно увидеть базовый игровой цикл.

Основной цикл движка реализован в классе FEngineLoop:

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

PreInit это место, где загружается большинство модулей. Когда вы создаете игровой проект или плагин с исходным кодом на C++, вы определяете один или несколько исходных модулей в файле .uproject или .uplugin и можете указать в LoadingPhase, когда нужно загрузить этот модуль.

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

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

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

Так что же происходит после загрузки вашего модуля?

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

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

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

Итак, цикл Engine загрузил все необходимые модули движка, проекта и подключаемых модулей, зарегистрировал классы из этих модулей и инициализировал все необходимые низкоуровневые системы. На этом этап PreInit завершается, и мы можем перейти к функции Init. Если мы немного упростим ее, то увидим, что она передает данные в класс под названием UEngine:

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

Движок это программный продукт, содержащий исходный модуль под названием Engine. В этом модуле находится заголовок под названием Engine.h, а в этом заголовке определен класс UEngine, реализованный как в UEditorEngine, так и в UGameEngine.

На этапе инициализации игры FEngineLoop проверяет файл конфигурации движка, чтобы определить, какой класс GameEngine нужно использовать. Затем он создает экземпляр этого класса и закрепляет его как глобальный экземпляр UEngine, доступный через глобальную переменную GEngine, объявленную в Engine/Engine.h.

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

Так что же делает класс Engine? На него возложено множество функций, но основная заключается в этом наборе больших массивных функций, включающем Browse и LoadMap. Мы рассмотрели загрузку процесса и инициализацию всех систем движка, но для того, чтобы зайти в игру, нам нужно загрузить карту, и делается это при помощи класса UEngine.

Engine может просматривать URL-адрес, который представляет собой либо адрес сервера для подключения в качестве клиента, либо имя карты для локальной загрузки. В URL-адресах есть и свои добавляемые аргументы. Когда вы устанавливаете карту по умолчанию в файле DefaultEngine.ini вашего проекта, вы говорите движку автоматически переходить к этой карте при загрузке. Конечно, в своих сборках вы также можете переопределить карту по умолчанию, указав новый URL-адрес в командной строке, или можете использовать команду open для перехода на другой сервер или карту во время игры.

Итак, давайте посмотрим на инициализацию класса Engine. Она происходит перед загрузкой карты и делает это посредством создания нескольких важных объектов: GameInstance, GameViewportClient и LocalPlayer. Можно утрированно представить, что LocalPlayer это представление пользователя, сидящего перед экраном, а GameViewportClient это и есть сам экран: по сути, это высокоуровневый интерфейс для систем рендеринга, звука и ввода.

Класс UGameInstance был добавлен в Unreal 4.4 и выделен из класса UGameEngine для обработки некоторых функций, более специфичных для проекта, которые ранее обрабатывались в Engine.

Итак, после инициализации класса Engine у нас появляются GameInstance, GameViewportClient и LocalPlayer. Теперь игра готова к запуску: именно здесь происходит первоначальный вызов LoadMap. К концу вызова LoadMap у нас будет UWorld, содержащий всех актеров, сохраненных на нашей карте, а также несколько новых актеров, формирующих ядро GameFramework, включающее игровой режим, игровую сессию, состояние игры, диспетчер игровой сети, контроллер игрока, состояние игрока и пешку.

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

Все, что происходит до LoadMap, привязано ко времени жизни процесса. Все остальное например, GameMode, GameState и PlayerController, создается уже после загрузки карты и остается там до тех пор, пока вы играете на этой карте. Движок действительно поддерживает так называемый seamless travel, когда вы можете переходить на другую карту, сохраняя при этом некоторых актеров со старой. Но если вы сразу перейдете к новой карте, или подключитесь к другому серверу, или вернетесь в главное меню, тогда все актеры будут уничтожены, мир очищен, и эти классы не будут отображаться, пока вы не загрузите другую карту.

Итак, давайте посмотрим на то, что происходит в LoadMap. Это сложная функция, но если мы вернемся ее к основам, то выясним, ее не так уж и сложно понять.

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

Короче говоря, к этому моменту World уже не будет. Однако у нас есть Контекст Мира (World Context). Этот объект создается экземпляром игры во время инициализации Engine, и по сути, это постоянный объект, который отслеживает, какой мир загружен в данный момент. Перед загрузкой чего-либо еще GameInstance может предварительно загрузить любые ассеты, которые ему могут понадобиться, но по умолчанию ничего не делает.

Далее нам нужно получить UWorld.

Если вы работаете с картой в редакторе, редактор загружает в память UWorld вместе с одним или несколькими ULevels, которые содержат размещенных вами Актеров. Когда вы сохраняете свой постоянный уровень, этот Мир, его Уровень и все его Актеры сериализуются в пакет карты, который записывается на диск в виде файла .umap. Во время LoadMap движок находит этот пакет и загружает его. На этом этапе мир, его постоянный уровень и актеры на этом уровне, а также WorldSettings загружаются обратно в память.

Теперь у нас есть World, и мы должны его инициализировать.

Движок дает миру ссылку на GameInstance, а затем инициализирует глобальную переменную GWorld. Затем мир устанавливается в WorldContext, ему присваивается тип в данном случае Game, и он добавляется в корневой каталог. InitWorld позволяет миру настраивать такие системы, как физика, навигация, искусственный интеллект и аудио.

Когда мы вызываем SetGameMode, мир просит GameInstance создать актера GameMode. Как только это происходит, движок полностью загружает карту то есть, загружаются все подуровни вместе с ассетами.

Далее мы переходим к InitializeActorsForPlay. Это то, что Engine называет подведением мира к игре. Здесь World перебирает всех актеров в нескольких разных циклах. Первый цикл регистрирует все компоненты актеров в мире.

Происходит регистрация каждого компонента ActorComponent в каждом Actor, что делает для компонента три важных вещи:

  • Во-первых, мы получаем ссылку на мир, в который он был загружен;

  • Затем рпроисходит вызов функции компонента OnRegister, дающий ему возможность выполнить любую раннюю инициализацию;

  • В случае PrimitiveComponent после регистрации компонент будет иметь FPrimitiveSceneProxy, созданный и добавленный в FScene, являющийся версией потока рендеринга UWorld.

После регистрации компонентов World вызывает функцию InitGame GameMode. Это заставляет GameMode породить актера GameSession. После этого у нас есть еще один цикл, в котором мир проходит от уровня к уровню, и каждый уровень инициализирует всех своих актеров. Это происходит за два прохода. В первом проходе Уровень вызывает функцию PreInitializeComponents для каждого Актера. Это дает участникам возможность инициализироваться довольно рано после регистрации компонентов, но до их инициализации.

GameMode это такой же актер, как и любой другой, поэтому здесь также вызывается функция PreInitializeComponents. После этого GameMode порождает объект GameState и связывает его с миром, а также порождает GameNetworkManager, прежде чем, наконец, вызвать функцию InitGameState.

Наконец, мы повторяем цикл по всем актерам, на этот раз вызывая InitializeComponents, а затем PostInitializeComponents. InitializeComponents перебирает все компоненты актеров и проверяет две вещи:

  • Если в компоненте включена функция bAutoActivate, необходимо активировать компонент;

  • Если в компоненте включен bWantsInitializeComponent, произойдет вызов функции InitializeComponent.

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

На этом этапе наш вызов LoadMap почти завершен: все актеры загружены и инициализированы, мир готов для запуска в игре, и теперь у нас есть набор актеров, используемых для управления общим состоянием игры: GameMode определяет правила игры, он же порождает большинство актеров кор-геймплея. Это высший авторитет того, что происходит во время игры, и он существует только на сервере. GameSession и GameNetworkManager также работают только на сервере. Сетевой менеджер используется для настройки таких вещей, как обнаружение читов и предсказание движения. А для онлайн-игр GameSession одобряет запросы на вход и служит интерфейсом для онлайн-сервисов (например, Steam или PSN).

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

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

В этот момент LoadMap перебирает все LocalPlayers, присутствующие в нашем GameInstance: обычно таковой существует только один. Для конкретного LocalPlayer он вызывает функцию SpawnPlayActor. Обратите внимание, что PlayActor здесь взаимозаменяем с PlayerController: эта функция порождает PlayerController. LocalPlayer, как мы уже убедились, является представлением игрока в движке, а PlayerController представлением игрока в игровом мире.

LocalPlayer на самом деле является специализацией базового класса Player. Есть еще один класс Player под названием NetConnection, который представляет игрока, подключенного из удаленно.

Чтобы любой игрок мог присоединиться к игре независимо от того, локальная она или удаленная, он должен пройти процесс входа в систему. Этот процесс обрабатывается GameMode. Функция PreLogin в GameMode вызывается только для попыток удаленного подключения: она отвечает за утверждение или отклонение запроса на вход. Как только мы получаем добро на добавление игрока в игру, происходит вызов Login. Функция Login порождает актера PlayerController и возвращает его в мир.

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

PlayerController и PlayerState похожи на GameMode и GameState в том, что один из них является официальным представлением игры (или игрока) на сервере, а второй содержит данные, которые каждый должен знать об игре (или игроке).

После создания PlayerController World полностью инициализирует его для работы в сети и связывает с объектом Player. После этого вызывается функция PostLogin игрового режима, которая дает игре возможность выполнить любую настройку, которая должна произойти в результате присоединения этого игрока. По умолчанию игровой режим будет пытаться создать Pawn для нового PlayerController в PostLogin. Pawn это особый тип актера, которым может владеть Controller. PlayerController это специализация базового класса Controller. Есть еще один подкласс под названием AIController, использующийся для неигровых персонажей.

Это давнее соглашение в Unreal: если у вас есть актер, который перемещается по миру, руководствуясь собственным автономным процессом принятия решений будь то игрок-человек, принимающий решения и переводящий их в данные ввода, или ИИ, принимающий решения более высокого уровня о том, куда идти и что делать, обычно у вас есть два актера. Controller это тот, кто управляет актером, а Pawn представление актера в мире. Поэтому, когда к игре присоединяется новый игрок, GameMode по умолчанию порождает Pawn для нового PlayerController.

Ваш PlayerState может быть настроен так, чтобы игрок был простым наблюдателем без возможности взаимодействия с миром, или вы можете настроить GameMode, чтобы изначально все игроки были только зрителями. В этом случае GameMode не будет порождать Pawn, а вместо этого PlayerController создаст свой собственный SpectatorPawn, позволяющий следить за происходящим в игре, не взаимодействуя с игровым миром.

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

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

В любом случае, после создания Pawn будет связана с PlayerController, которому она принадлежит. Теперь, когда мы вернемся в LoadMap, у нас будет все готово для фактического запуска игры. Все, что осталось сделать это маршрутизировать событие BeginPlay. От Engine к World, от World к GameMode, от GameMode к WorldSettings, а WorldSettings, в свою очередь, перебирает всех актеров.

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

Давайте в качестве повторения быстро пробежимся по всему еще раз.

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

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

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

На этапе инициализации мы начинаем настройку самого движка. Короче говоря, мы создаем объект Engine, инициализируем его, а затем запускаем игру. Чтобы инициализировать движок, мы создаем GameInstance и GameViewportClient, а затем создаем LocalPlayer и связываем его с GameInstance. После этого мы можем начать загрузку игры.

Мы выясняем, какую карту будем использовать, переходим к этой карте и сообщаем GameInstance о том, что сделали.

Остальная часть нашего процесса запуска происходит в вызове LoadMap. Сначала мы находим наш пакет карты, затем загружаем его: он переносит в память всех актеров, помещенных на постоянный уровень, а также дает нам объекты World и Level. Мы находим наш мир, даем ему ссылку на GameInstance, инициализируем некоторые системы, а затем создаем актера GameMode. После этого мы полностью загружаем карту, добавляя все необходимые подуровни и ассеты. Когда все оказывается полностью загружено, мы начинаем подходить мир к игре. Сначала мы регистрируем все компоненты для каждого актера на каждом уровне, а затем инициализируем GameMode, который, в свою очередь, порождает актера GameSession. После этого мы инициализируем всех актеров в мире.

Сначала мы вызываем PreInitializeComponents для каждого актера на каждом уровне: когда это происходит для GameMode, он порождает GameState и GameNetworkManager, а затем инициализирует GameState. Затем в другом цикле мы инициализируем каждого актера на каждом уровне: происходит вызов InitializeComponent (и, возможно, Activate) для всех компонентов, которым это необходимо, после чего актеры уже формируются окончательно.

Как только мир будет готов к запуску в игре, мы можем зарегистрировать LocalPlayer внутри игры. Здесь мы создаем PlayerController, который, в свою очередь, порождает для себя PlayerState и добавляет это PlayerState в GameState.

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

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

Мы рассмотрели здесь много вещей, поэтому выделим самое важное:

  • Мы рассматривали классы GameModeBase и GameStateBase, а не GameMode и GameState. Эти базовые классы были добавлены в Unreal 4.14, чтобы исключить некоторые функциональные возможности Unreal-Tournament из игрового режима. В то время как GameModeBase содержит все основные функции игрового режима, класс GameMode добавляет концепцию матча с изменениями состояния матча, которые происходят после BeginPlay. Это позволяет следить за потоком игры например, за готовностью всех игроков, за временем начала и окончания игры и переходом к новой карте для следующего матча.

  • Мы также рассмотрели класс Pawn, но помимо него GameFramework определяет класс Character, который является специализированным типом Pawn, включающим сразу несколько полезных функций. У класса Character есть капсула столкновения, которая используется в основном для движений, а также скелетная сетка, в связи с чем предполагается, что он является анимированным персонажем. Еще у него есть компонент CharacterMovementComponent, тесно связанный с классом Character и выполняющий несколько полезных вещей. Самым важным является то, что движение персонажа воспроизводится из коробки с предсказанием движения на стороне клиента. CharacterMovement реализует полный набор опций передвижения для ходьбы, прыжков, падений, плавания и полета.

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

Итак, вот все те классы, которые мы рассмотрели (за исключением UWorld и ULevel):

Мы убедились, что у Unreal есть зрелая Game Framework, которая имеет устоявшийся дизайн для обработки таких вещей, как онлайн-интеграция, запросы на вход и репликация сети. Это означает, что вы можете довольно легко разрабатывать многопользовательские игры из коробки, и движок позволяет добавлять пользовательские функции практически на любом уровне. Если вас больше всего интересует создание простых однопользовательских игр, то столь комплексная Game Framework может оказаться для вас довольно бесполезной.

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

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

Подробнее..

Armory Engine. Введение

25.11.2020 14:11:26 | Автор: admin


Итак, я расскажу вам о таком замечательном игровом движке как Armory3D. Написал его и развивает Lubos Lenco, словацкий программист. Он же и написал Armory Paint, программу для рисования PBR текстур на 3D моделях. Расскажу чем он примечателен:

  • Открытый (лицензия zlib)
  • Кроссплатформенный (Windows, Android, IOS, Linux, macOS)
  • Совершенно бесплатный (вы можете пожертвовать автору на развитие).

Движок написан на Haxe, С/С++ и WebAssembly, поставляется в виде аддона для Blender, но спокойно интегрируется и другие пакеты 3D моделирования и анимации. Движок оптимизирован на производительность, переносимость и легковесность кода в отличии от таких монстров как Unity, Unreal, CryEngine etc.

Компоненты движка


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

  • Рендер. У движка своя собственная система рендеринга Iron. Она основана основана шейдерах EEVEE/Cycles Blender, но только частично. Вот список тех которые можно использовать.
  • Скриптовая система. Скрипты в Armory используют traits (особенность, черта), часть логики прикрепленная к объекту (о ней будет подробнее чуть ниже)
  • Мультиплатформенность. Движок поддерживает из коробки все доступные популярные платформы -ПК (Windows, Linux, macOS), консоли (Xbox One Series X, PS4, Nintendo Switch, и мобильные OS(Android, IOS).


Теперь о скриптах, в движке их 4 типа:

  1. LogicNodeTraits: это аналог blueprints Unreal Engine 4.
  2. Haxe Traits: traits написанные на Haxe.
  3. Canvas Traits: UI, меню и прочее.
  4. WASM Traits: traits написанные на WebAssembly, Rust, C/C++.


Технологии движка


Движок использует Iron, KHA и Haxe:

  • Iron. Внутренний рендер движка написан на Iron, KHA и Haxe. Легко масштабируется, поскольку является модульным. Сам iron обрабатывает контейнеры визуализации, поэтому на его основе можно писать свой собственный движок
  • Haxe. Открытый кроссплатформенный инструмент основанный основанный на современном, высокоуровневом, строго типизированном, многопарадигмальном языке программирования и кросс-компиляторе. Который может компилировать код для целевого исходного кода платформы или двоичного кода. В своей сущности конвертер кода. Написав код для python вы сможете перенести его на lua, c++ и так далее. Очень удобно для переноса
  • KHA. SDK и мультимедийный фреймворк в одном лице. Компактный, производительный для создания кроссплатформенных приложений. KHA предоставляет API для сети, аудио. ввода, графики (Metal, Vulkan, DirectX, WebGL и OpenGL). Генерирует высокоскоростной компактный код для всех платформ.


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

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

Домино на Unity

05.01.2021 18:18:32 | Автор: admin

Учебные материалы для школы программирования. Часть 2

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

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

Spoiler

Хочется напомнить, что занятия разрабатывались для детей 10-16 лет, поэтому многие этапы упрощены.

Первое занятие вы можете найти по ссылке http://personeltest.ru/aways/habr.com/ru/post/535916/

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

Домино

Цель данного занятия - научиться создавать и редактировать игровые объекты, компоненты и материалы. Итак, начнем!

Импортируем приложенный ассет. Скачать его можно по ссылке

Создаём новую сцену. На сцене создаём плоскость с помощью quad или plane, выставляем размер 200х200.

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

Spoiler

Unity - отлично интегрируется с уроками по математике и физике. Смело вплетайте сложные или "скучные" темы по этим предметам в ваше занятие по Unity. Такой прием поможет не только преподнести тему интересно, но и ответит на частый вопрос "а зачем мне это учить, если в жизни не пригодится?" - пригодится, отвечаем мы, и рассказываем на проекте Домино, например, о понятии линейного размера.


Далее, на наше домино надо закинуть Rigidbody, чтобы домино могли падать.

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

using System.Collections;using System.Collections.Generic;using UnityEngine;public class Reset : MonoBehaviour {  Rigidbody rig;  Vector3 startPos;  Quaternion startRot;    // Use this for initialization  void Start() {    rig = GetComponent<Rigidbody>();    startPos = transform.position;    startRot = transform.rotation;  }    // Update is called once per frame  void  Update() {    if(Input.GetKeyDown(KeyCode.Space)) {      if(rig) {        transform.position = startPos;        transform.rotation = startRot;        rig.velocity = Vector3.zero;        rig.angularVelocity = Vector3.zero;      }     }  }}

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

Этот этап урока отлично подойдет, чтобы рассказать о глобальной и локальной системе координат, о центре объекта и его позиции (center / pivot).

Несмотря на малый, с первого взгляда, объём занятия, у ребят уходит 1,5-2 часа. Время затрачивается не только на выставление домино в желаемой последовательности (рекомендуем каждому воплотить свою задумку по "узору" расположения костей), но и на разбор важных, для дальнейшей работы, тем: система координат, точки отсчёта, понимание углов Эйлера (pitch, yaw, roll), и масштабирования объектов.

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

Этот скрипт по нажатию на пробел выставляет шарик в изначальное положение и включает-выключает объект-препятствие.

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

Подробнее..

Godot, 1000 мелочей

01.07.2020 20:13:32 | Автор: admin
Недавно открыл для себя Godot engine, опенсурсный игровой движок. Делюсь некоторыми приёмами и заметками, в основном из области 3д, кода или общих моментов.


У Godot в целом репутация скорее 2д движка, которое проработано довольно хорошо, но и не так давно появившиеся 3д возможности позволяют делать трёхмерные игры. Особенно если вы в состоянии оптимизировать какие-то вещи самостоятельно, не делаете слишком уж тяжёлую и комплексную игру, а также вас устраивают текущие варианты рендера. Ну или можете ждать будущих оптимизаций и появления vulkan.

Из коробки в движке есть некоторая физика, в том числе джоинты и колёсный транспорт. Нет встроенного редактора terrain, но можно воспользоваться плагином, импортировать из специализированных программ или просто как меш из 3д пакета. В последнем случае ради производительности придётся самостоятельно резать ландшафт на фрагменты-чанки, и скорее всего делать им отдельные меши под коллизии. Что касается мешей под форму коллизии ландшафта чтобы не было визуальных нестыковок, нужно триангулировать модель во время экспорта из 3д пакета. Например, один из простейших способов это сделать в Blender экспортировать меш в формате collada (.dae), там по дефолту стоит галочка triangulate.

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


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

Что касается языков, то если не рассматривать низкоуровневый способ, то наиболее ходовые варианты это скриптовые языки GDScript и C#, а также визуальный скриптинг для чего-то простого. GDScript лучше интегрирован в движок, имеет больше примеров, не требует запуска внешней среды, а в плане общей структуры тут будет происходить всё то же, что в варианте C# объявление переменных, вызов функции инициализации, цикл процессов, цикл физических процессов и так далее. Так что выбор GDScript в качестве основного скриптового языка разработки имеет смысл. Пересаживаясь на C# получим разве что большую аккуратность и подробность записи, теряя в лаконичности, но усиливая разборчивость и контроль над ситуацией фигурные скобочки вместо использования табуляции, отметки конца строки, более формальную типизацию.
Что касается глобальных переменных, то для их использования что в GDScript, что в C# потребуется добавить в параметрах проекта глобальный скрипт/скрипты в автозагрузку, к переменным которого можно будет обращаться глобально.
Также в Godot действует ограничение -на каждом объекте не может быть больше одного скрипта. Но этот момент можно обойти, например, повесив второй скрипт на дочерний объект.

Сигналы


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


Заводим сигнал

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

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


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

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


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

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



CGS-объекты



Один из полезных 3д инструментов в Godot примитивы constructive solid geometry. Проще говоря это объекты, поддерживающие булевы операции пересечение, исключение, объединение. Кроме набора примитивов есть универсальный CGS Mesh, в качестве формы для которого можно установить уже произвольный меш.



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

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

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




Далее у нас идут CGS движущиеся. Через код, либо записанную анимацию. В целом через этот способ можно реализовывать какие-то эффекты, но очень желательно чтобы подобные анимации не крутились на сцене в цикле. Несколько анимированных CGS могут заметно посадить производительность, к тому же в Godot оптимизирует не все вещи, и если вы не вырубите анимированные CGS самостоятельно, то они будут продолжать расходовать производительность вне видимости камеры.
Тем не менее эффекты анимированных CGS уже сложно заменить решениями 3д пакета, поэтому вы можете быть заинтересованы именно в их использовании (по крайней мере, если не собираетесь рисовать продвинутое 3д через код). Главное найти им правильное применение, лучше всего их использовать точечно, в определённых местах, включая анимацию по триггеру. Как эффект открытия прохода или прочий разовый спецэффект. Естественно, чем проще форма CGS объектов тем лучше. А основную вычислительную нагрузку они оказывают именно в процессе соприкосновения в движении, притом нет особой разницы, какой именно объект двигать относительно другого.


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

Демо-версии Невангеров для Unigine и Godot

19.10.2020 22:04:09 | Автор: admin
Альтернативные прототипы с биомашинками (и не только био-), которые собрал за время знакомства с игровыми движками Unigine 2 и Godot 3.




Unigine engine



Начнём с версии для Unigine. Используется версия 2.11, вышедшая этой весной, начиная с которой в движке появилась бесплатная лицензия. На данный момент вышла 2.12 и скоро ожидается 2.13.

Что в общем стоит знать про Unigine это томский игровой движок, часто используемый для бенчмарков и симуляций. На нём в разные годы вышли такие игры как Oil Rush, Cradle, и вот, например, относительно недавняя ммо Dual Universe.
Внутри применяется довольно много интересных и перспективных решений, рендерит достаточно красивую картинку и может довольно сильно приглянуться художникам, особенно если те моделят в отдельном 3д-пакете, а не средствами самого движка.

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

В качестве инструмента для игрового разработчика здесь в принципе применим опыт использования C# в Unity, хотя в Unigine нет такого же многообразия готовых компонентных решений. Тем не менее, какие-то базовые вещи реализованы, а документация поможет написать остальное. С++ тоже никуда не делся.

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

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

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

Что касается геймплея, в Unigine, в отличие от Unity и Godot, например, оказалось довольно просто переключать физику машинки на лету, не только визуал, но и расположение/размер колёс. Без придумывания дополнительных трюков для того, чтобы не провалиться сквозь пол, и без пересоздания модели. Хотя, существует некоторый шанс провалиться в текстуры, но совершенно в иных ситуациях, а не в момент смены машинки.

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

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

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

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



Архив для Win64 можно скачать здесь (вес 687Мб): DROPBOX
или на страничке itch.io: NEWANGERS
в распакованном виде занимает 3Gb

Что тут есть:

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

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

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

* Машинка в основном управляется кнопками WASD. Также можно стрейфиться по Q и E. Прыгать на пробел, или, наоборот, сильнее устремляться к земле нажимая R. По Tab машинку можно крутить, чтобы, например, перевернуться и встать на колёса.
Стрейфам и прыжкам не установлены лимиты, то есть во время прыжка можно прыгать дальше и так далее.

* Кнопки 1,2,3,4,5,6,7 переключают разные машинки. Колесом мыши можно слегка зумить камеру.

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

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

* P выход из игры, L перезагрузка с откатом на стартовый уровень

Более ранние исходники этого прототипа знакомый выложил на своей страничке вместе с некоторыми правками физики колёс первоначальной версии: GITLAB

Godot engine



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

У Годо больше репутация 2д-движка, благодаря многообразию проработанных инструментов именно для 2д, но они являются дополнительным плюсом для 3д куда проще делать игровой UI. Ещё проще чем в Unity, как по мне. На текущий момент Годо в своём развитии дошёл до стабильной 3.2.3 версии (но все ждут 4 из за вулкана, оптимизаций и так далее. шаткие сборки четвёрки, кстати, уже можно пробовать хотя бы оценить картинку).

Движок не требует мощного железа для 3д-графики и выдаёт вполне приличную картинку. Готовых трёхмерных инструментов не огромное количество, но реализованы как раз одни из самых нужных, полезных и универсальных. Примерно то же касается и оптимизаций. Например, в движке реализован обычный frustrum culling, отсекающий геометрию вне зоны видимости камеры. Occlusion culling (чтобы не считать закрытые стенами объекты) реализацию придётся придумывать самостоятельно (что не так уж сложно, особенно в каких-то точечных местах, да и не в каждой игре нужно). Также из коробки в движке нет батчинга геометрии (правда для gles2 частично есть) и террейна, но это не такая уж проблема, просто потребуется что-то оптимизировать вручную сшивать какие-то меши вместе, бить геометрию на мелкие части или использовать чанки и так далее. Можно подыскать какую-то реализацию в местном небольшом сторе, например, добавить в свой проект готовое решение для террейна.

Интерфейс движка, кстати, довольно продуманный и кастомизируемый (хотя есть некоторые негибкие элементы). Пользоваться им в целом удобно. Поддерживаемых языков достаточно, для разного уровня погружения. Тут и C++ и C# и довольно удобный внутренний GDScript, который запускается прямо внутри редактора, не требуя запуска отдельной среды. Визуальный скриптинг тоже присутствует, так что без знания программирования в Годо тоже вполне можно жить какую-то минимальную логику сконструировать, что-то заанимировать (в Godot есть простой и классный инструмент для записи анимаций).

Малый вес приложения, мультиплатформенность, быстрота разработки, простота имплементации различных сторонних решений тоже немаловажные плюсы движка. Есть два варианта рендера gles2 и gles3, оба поддерживают 3д, но в первом оно попроще и в целом он больше подходит для 2д и мобилок. Gles3 даёт более продвинутый уровень графики, какая-то часть мобильных устройств его тоже поддерживает.

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

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

В Годо есть интересные инструменты, вроде CGS-объектов и мультимеша. Подробнее про особенности их использования я писал в статье: Godot, 1000 мелочей

Прототип Невангеров на этом движке получил отдельное название Wild Engines. В целом у меня получается как бы семейство сходных проектов, объединённых концепцией странных машинок путешествующих по странным мирам. И в качестве рабочего собирательного названия я их привык именовать Невангерами, пока не придумается более конкретное наименование. У Godot прототипа теперь появилось своё название, у приостановленной на версии 0.9 Unity версии (с которой всё и началось) тоже появилось другое название, но до этого дойдёт дело потом, если появится время к ней вернуться.

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

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

В итоге в демо версии Wild Engines есть 4 машинки, одну из которых нужно выбрать для старта, и два небольших уровня (Level A и Level B). Пара ранних карт тоже осталась (Levels 0 и 1), но они ещё более тестовые и ландшафт там неоптимизирован.



В меню можно включить/выключить полноэкранный режим и тени.
Кнопки 1, 2 и 3 меняют позицию камеры. Мышь нацеливает и поворачивает камеру, тем сильнее, чем дальше курсор от центра.
WASD перемещение. PgDown прыжок. Q случайный импульс.
Левая кнопка мыши выстрел.
По Enter появляется подсказка об управлении и кнопка возврата в основное меню, где можно поменять машинку/уровень.

Win64 версия (42 Mb): DROPBOX wildengines_x64
Linux версия (44 Mb): DROPBOX wildengines_linux

Бонус



А вот один из недавно появившихся новых мехосов, Некромант:

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

Ещё тут завалялся один старый картридж как по вашему, клоны каких восьмибитных игр могли скрываться под таким названием на китайском картридже, будь Вангеры всемирно известной франшизой в те далёкие времена?

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru