
Привет! В предыдущем посте я рассказал вам о движке Armory, теперь создадим свой первый уровень в нем. На самом деле, создание уровней в Armory ничем практически не отличается от работы в том же Unreal или Unity - вы также можете импортировать ассеты, создавать свои тут же (это же Blender!), накладывать текстуры и прочие штуки. Как я уже упоминал, для работы с движком вам нужны умение работы с Blender, поскольку Armory тесно связан с ним. Я не стану подробно разбирать как добавить plane, cube или lamp в сцену - только работа с движком. Готовы? Тогда начнем.
В первую очередь, вы можете скачать готовый template файл, на его основе мы разберем что и как работает. В архиве нам потребуется playground.blend. Щелкаем и запускаем. Теперь посмотрим какие у нас опции запуска.
Панель Armory Player:
Runtime:
Krom запускает автономный плеер.
Browser для проигрывания в браузере.
Camera:
Scene чтобы начать игру с точки активной камеры сцены.
Viewport, чтобы начать игру из области просмотра. Это полезно для предварительного просмотра сцены, так как позволяет управлять камерой.
Вы можете поэкспериментировать с этими параметрами или приступить к созданию сцены по готовому шаблону.
В сцене присутствует Cube, Cylinder, Ground, Lamp, Wall, Lamp (spot) и анимированная модель человека и цилиндра. Вам понадобятся для сцены еще и две текстуры (grid_base, grid _rough), но вы можете взять любые другие или сгенерировать свои. Сделали? Приступим.
Анимация
Наша сцена, в отличии от неподвижного рендера, интерактивна - куб в центре вращается, цилиндр катается, человек бежит, кубы у стены можно перетаскивать, а кнопкой F можно заспавнить новые кубы. И вот тут самое интересное - все сделано силами Blender. Начнем с анимации вращения куба в центре.
Выберете куб, в панели Timeline перейдите к кадру 1, выберите куб и нажмите, I - Rotation чтобы вставить ключевые кадры для поворота. Затем перейдите к 60 кадру на временной шкале. Выбрав куб, нажмите R чтобы повернуть его на желаемую величину и нажмите I - Rotation еще раз. Наш куб анимирован.
Физика объектов
Выбрав объект (Cube), перейдите на Physics вкладку и нажмите Rigid Body. Задать форму для объекта можно на панели Collision.

В панели Rigid Body задаем массу и тип объекта:
Active для объектов, на которые влияет физика.
Passive для статических или анимированных объектов на шкале времени.
Освещение и мир

Свет настраивается в панели Light, установлен тип лампы Spot. Но вы можете поэкспериментировать с другими источниками света и посмотреть как оно будет выглядеть у вас.
Мир настраивается в Shader Editor - World. Можно сделать процедурное небо или добавить какой-нибудь HDR файл.
Логические узлы
Выше я рассказал как устроена наша сцена, теперь добавим в нее интерактивность. За это у нас отвечают Logic Node - аналог blueprints в Unreal Engine 4. Созданные в редакторе ноды компилируются в скрипты. Их можно писать на Haxe и/или комбинировать оба этих способа.
Система скриптов состоит из 5 основных категорий:
Events - ноды которые запускают необходимое событие.
Actions - при запуске этой ноды (events) начинается действие.
Logic - ноды для управления потоком, используя ветвления, циклы, вентили…
Variables - ноды используемые для хранения данных в логическом дереве.
Values - ноды для извлечения данных из других объектов.
Процедурную анимацию цилиндра сделаем с помощью нодов. Открываем Logic Editor - New для создания нового дерева узлов. Все доступные ноды находятся в диалоговом меню Shift - A.

Найдите On Update узел и вставьте его. Он будет активировать свой вывод каждый кадр.
Подключите его к входу In Set Object Location узла. Если оставить Object поле пустым, будет использоваться объект, выполняющий это логическое дерево. В нашем случае это цилиндр.
Красные входы узлов (часто называемые In и Out) используются для потока сигналов дерева узлов. Красные входные гнезда активируют узлы, а красные выходные гнезда обычно активируются при выполнении узла (если узел не указывает иное). Другие типы входов (другие цвета) используются для передачи данных между узлами.
Добавьте нод Vector и подключите его выход Location к входу Set Object Location узла. Теперь положение цилиндра будет установлено на этот векторе в каждом кадре.
Для местоположения вектора X, добавьте нод Math и установите его на Sine.
Вставьте Get Application Time для управления синусоидальным узлом - он перемещает цилиндр вперед и назад.
Добавьте еще один Math для масштабирования синусоидального выхода (это позволит цилиндру двигаться дальше с каждой стороны).
Положение Y и Z сохраняем без изменений, поэтому нам нужно установить его в соответствии с текущим положением цилиндра. Добавьте Get Object Location, подключите его к Separate XYZ узлу, чтобы разбить вектор на его значения XYZ, а затем подключите значения Y и Z к входам Y / Z Vector узла, который мы добавили ранее.
Консоль отладки можно включить в Armory Project > Flags > Debug Console.
Haxe
Спавн кубов кнопкой “F” мы напишем на Haxe. Создадим в сцене еще один куб, добавим в сцену, настроим ему физику (Rigid Body и Active). Добавим Empty, расположим на некоторой высоте от сцены - это точка спавна наших кубов. Создайте новый Haxe traits в Properties - Object - Armory Traits. Нажмите кнопку New Script. Откроем Kode Studio - создадим и сохраним новый скрипт. Назовем ее SpawnBox.
SpawnBox
package arm; import iron.object.Object; import iron.system.Input; import iron.Scene; import armory.trait.physics.RigidBody; class SpawnBox extends iron.Trait { public function new() { super(); // We want to get notified every frame notifyOnUpdate(update); } function update() { // f key was pressed if (Input.getKeyboard().started("f")) { // Spawn Box object Scene.active.spawnObject("Box", null, boxSpawned); } } // Box just got spawned function boxSpawned(o:Object) { // Translate cube to the location of empty object var traitOwner = object; o.transform.loc.setFrom(traitOwner.transform.loc); // Box object has a rigid body trait // Notify physics system to take new location into effect! o.getTrait(RigidBody).syncTransform(); } }
Физику кубов которые можно перетаскивать мышью, мы сделаем другой traits - PhysicDrag.
PhysicDrag
package arm; import iron.Trait; import iron.system.Input; import iron.math.Vec4; import iron.math.Mat4; import iron.math.RayCaster; import armory.trait.physics.RigidBody; import armory.trait.physics.PhysicsWorld; class PhysicsDrag extends Trait { #if (!arm_bullet) public function new() { super(); } #else var pickConstraint: bullet.Bt.Generic6DofConstraint = null; var pickDist: Float; var pickedBody: RigidBody = null; var rayFrom: bullet.Bt.Vector3; var rayTo: bullet.Bt.Vector3; static var v = new Vec4(); static var m = Mat4.identity(); static var first = true; public function new() { super(); if (first) { first = false; notifyOnUpdate(update); } } function update() { var physics = PhysicsWorld.active; if (pickedBody != null) pickedBody.activate(); var mouse = Input.getMouse(); if (mouse.started()) { var b = physics.pickClosest(mouse.x, mouse.y); if (b != null && b.mass > 0 && !b.body.isKinematicObject() && b.object.getTrait(PhysicsDrag) != null) { setRays(); pickedBody = b; m.getInverse(b.object.transform.world); var hit = physics.hitPointWorld; v.setFrom(hit); v.applymat4(m); var localPivot = new bullet.Bt.Vector3(v.x, v.y, v.z); var tr = new bullet.Bt.Transform(); tr.setIdentity(); tr.setOrigin(localPivot); pickConstraint = new bullet.Bt.Generic6DofConstraint(b.body, tr, false); pickConstraint.setLinearLowerLimit(new bullet.Bt.Vector3(0, 0, 0)); pickConstraint.setLinearUpperLimit(new bullet.Bt.Vector3(0, 0, 0)); pickConstraint.setAngularLowerLimit(new bullet.Bt.Vector3(-10, -10, -10)); pickConstraint.setAngularUpperLimit(new bullet.Bt.Vector3(10, 10, 10)); physics.world.addConstraint(pickConstraint, false); /*pickConstraint.setParam(4, 0.8, 0); pickConstraint.setParam(4, 0.8, 1); pickConstraint.setParam(4, 0.8, 2); pickConstraint.setParam(4, 0.8, 3); pickConstraint.setParam(4, 0.8, 4); pickConstraint.setParam(4, 0.8, 5); pickConstraint.setParam(1, 0.1, 0); pickConstraint.setParam(1, 0.1, 1); pickConstraint.setParam(1, 0.1, 2); pickConstraint.setParam(1, 0.1, 3); pickConstraint.setParam(1, 0.1, 4); pickConstraint.setParam(1, 0.1, 5);*/ pickDist = v.set(hit.x - rayFrom.x(), hit.y - rayFrom.y(), hit.z - rayFrom.z()).length(); Input.occupied = true; } } else if (mouse.released()) { if (pickConstraint != null) { physics.world.removeConstraint(pickConstraint); pickConstraint = null; pickedBody = null; } Input.occupied = false; } else if (mouse.down()) { if (pickConstraint != null) { setRays(); // Keep it at the same picking distance var dir = new bullet.Bt.Vector3(rayTo.x() - rayFrom.x(), rayTo.y() - rayFrom.y(), rayTo.z() - rayFrom.z()); dir.normalize(); dir.setX(dir.x() * pickDist); dir.setY(dir.y() * pickDist); dir.setZ(dir.z() * pickDist); var newPivotB = new bullet.Bt.Vector3(rayFrom.x() + dir.x(), rayFrom.y() + dir.y(), rayFrom.z() + dir.z()); #if (js || hl) pickConstraint.getFrameOffsetA().setOrigin(newPivotB); #elseif cpp pickConstraint.setFrameOffsetAOrigin(newPivotB); #end } } } static var start = new Vec4(); static var end = new Vec4(); inline function setRays() { var mouse = Input.getMouse(); var camera = iron.Scene.active.camera; var v = camera.transform.world.getLoc(); rayFrom = new bullet.Bt.Vector3(v.x, v.y, v.z); RayCaster.getDirection(start, end, mouse.x, mouse.y, camera); rayTo = new bullet.Bt.Vector3(end.x, end.y, end.z); } #end }
Armory UI

Игровой интерфейс создается в специальном инструменте - Armory UI. чтобы создать UI, переключаемся на вкладку Scene, добавляем traits UI в панели Armory Traits. Нажимаем New Canvas и Edit Canvas - запустится редактор интерфейса. Далее в Armor UI нажав кнопку Text создаем текстовый объект. Отрегулировать можно на панели Properties. Сохраняем.
Путь рендеринга

В Аrmory есть настраиваемая система рендеринга, в панели Render - Armory Render Path можно выбрать одно или несколько целевых устройств для сборки. Там же можно настроить качество графики.
Экспорт проекта

Сборка и публикация нашего проекта находится в Properties - Render - Armory Exporter. Можно сделать несколько предустановок экспорта, причем в каждой можно указать платформу, API, путь и сцену для запуска. Выберете необходимую платформу (например Windows) и нажмите Publish. Экспортированные и собранные файлы можно посмотреть нажав треугольник (выделен красным) - Open Folder.
