Назначение движка

advertisement
Animator
Назначение движка
Движок дает предметно-ориентированное API для создания высокопроизводительных графических
приложений. Основным его применением, вероятно, станут игры.
Движок позволяет описать параметрическую модель игрового мира, посредством
специализированных классов. Позволяет отобразить модель в кадре анимации. А также реализует
часто встречающиеся элементы управления игрой (контроллеры).
С другой стороны, движок не реализует (и не накладывает ограничений) на реализацию конкретной
бизнес-логики приложения (для игры – это игровой AI).
Общий подход к построению движка
Следует выработать единый стиль создания компонент (code conventions). Можно поискать готовый,
благо их много, в т.ч. и игровых. Следует стараться избегать свойств (особенно, геттеров). Но не
следует так же заменять геттеры на функции. Нужно искать компромисс. Т.к. рассматривается
интеграция с LibCanvas, можно попробовать придерживаться стиля, принятого в данной библиотеке.
Так же имеет смысл плотно использовать AtomJS.
В любой игре есть три взаимосвязанных сущности:
 Параметрическая объектная модель игрового мира.
 Алгоритм управления игрой - AI.
 Алгоритм отрисовки.
Следует стремиться реализовать логику работы этих сущностей так, чтобы они делали только свою
работу, а с остальными сущностями взаимодействовали через соответствующие интерфейсы.
Например, параметрическая модель не должна обеспечивать рендер, а AI не должен рассчитывать
коллизии (может только проверять их наличие). Выглядеть это может примерно так:

С помощью объектной модели описывается игровой мир: помещения, декорации, персонажи,
спецэффекты и т.д.

С помощью Viewport-ов этот мир можно правильно отобразить в кадре анимации, согласно
положению и состоянию всех его объектов (в т.ч. и самого Viewport-а).

С помощью AI миром можно управлять. Для этого AI может менять параметры объектов,
добавлять новые, удалять имеющиеся и т.д. Для облегчения задачи типового управления,
часть алгоритмов AI можно оформить в виде контроллеров и применять их к объектам
стандартным образом. Например, контроллер патрулирования территории, контроллер
преследования, контроллер столкновений и т.д.
Декларативное описание
Общая базовая функциональность за счет унификации всех компонент. Основой являются
графические примитивы и контейнеры – составные графические примитивы, объединяющие
примитивы и другие контейнеры. Контейнеры могут добавлять специфическую функциональность к
группе примитивов. Примитивы могут быть текстурированы с использованием растровых карт. К
контейнерам и примитивам можно применять эффекты (например, прозрачность, масштабирование,
освещение и т.п.). На свойства эффектов, контейнеров и примитивов можно навешивать
контроллеры и триггеры. Контроллеры управляют изменением свойства по заданной программе, а
триггеры срабатывают при достижении свойством установленного порога. Любой контейнер имеет
собственную трехосную систему координат (не обязательно ортогональную) и любой примитив (в т.ч.
вложенный контейнер) описывается положением верхнего левого угла в этой системе, а также
наклонами относительно осей родительского контейнера вокруг произвольной точки (pivot). Размер
контейнера определяется размерами его примитивов (т.е. явно не задается). Для вывода
изображения используются специализированные объекты – Viewport-ы. Характер отображения будет
зависеть от типа Viewport-а (планиметрия, перспектива), и от его настроек (угол обзора, фокусное
расстояние и т.п.).
Поясняющий пример:
VP1 и VP2 - Viewport-ы с перспективной проекцией, VP3 – с планиметрической. Схему на
приведенном рисунке можно сделать из 2-х простых контейнеров: один включает 4 прямоугольных
полигона для стен, на которые наложены простые цветовые текстуры (solidColor), второй включается
в состав первого и содержит 4 полигона центральной колонны.
Состав движка
Теоретически движок может работать с любым API рендеринга. Для начала следует реализовать
поддержку 2D Canvas API (склоняемся в сторону LibCanvas и AtomJS). Движок создает главный
контейнер, с бесконечными шириной, длинной и глубиной, к которому можно добавлять другие
контейнеры верхнего уровня. Движок поддерживает несколько мультикастовых событий:
- от клавиатуры
- от мыши.
Любой объект можно подписать на одно или несколько из этих событий. Пока на событие никто не
подписан, оно не активно и не отслеживается в системе. Как правило на события клавиатуры и мыши
подписывается AI.
Библиотека растровых карт
- текстуры (в т.ч. текстурные карты)
- виртуальные текстуры - по сути кэш растеризации линий, фигур, текстов, заполнителей (solidColor,
linearGradient, ...) и т.п.
Статическая растровая карта может иметь только 1 элемент. Динамическая – несколько (в переделе –
бесконечное число элементов для процедурных карт). Если динамическая структура накладывается
на какой-либо полигон, можно задействовать его свойство frame для выбора части текстуры из
предопределенного набора.
Библиотека графических примитивов
Все примитивы по умолчанию прозрачны и заполняются исключительно растровыми картами
- Полигоны - треугольники, прямоугольники, многоугольники, овалы и т.п. Позволяют применить
любую текстуру из библиотеки растровых карт либо как есть, либо с замощением, либо с
растягиванием и т.п.
Библиотека специализированных контейнеров
Контейнеры позволяют объединять объекты и согласованно изменять их параметры групповым
образом. А так же, добавляют специализированную функциональность (например, кнопка).
Контейнеры имеют собственные оси координат и все объекты внутри контейнера описываются
относительно них. Контейнеры могут включать другие контейнеры неограниченно. Т.о. образуется
граф объектов.
Примеры контейнеров:
- простой контейнер – обладает только базовой функциональностью,
- кнопка - может состоять из нескольких любых примитивов - фон, рамка, текст, позволяет
регистрировать обработчик нажатия,
- слайдер,
- скроллбокс
- и т.п.
Библиотека Viewport-ов. Задача Viewport-а - отрисовка объектов в кадре анимации, согласно их
координатам и трансформациям (повороты, эффекты и т.п.).
Для этого алгоритм:
- обходит граф всех объектов,
- определяет какие объекты попадают в поле зрения (например, трассировкой)
- строит граф отрисовки (скорее всего это будет простой список - надо еще подумать)
- вызывает событие readyDrawing на которое можно подписать контроллеры и AI.
- рисует объекты.
Пока планируется ограничиться одним типом Viewport-а с планиметрической проекцией.
Библиотека хелперов
- куки (хранение/восстановление JSON сериализации),
- случайные числа (различные распределения, целые / дробные, вероятности, условно случайные),
- звук.
Библиотека эффектов (например, эффект прозрачности, осветления/затемнения и т.п.). Эффекты
рассчитываются исключительно для тех объектов, которые попали в граф отрисовки Viewport-а.
Библиотека контроллеров и триггеров.
Контроллеры и триггеры навешиваются на любой объект или эффект или на отдельный параметр
объекта или эффекта. Задача контроллера - изменение одного или нескольких параметров по
определенному алгоритму.
Контроллер можно связать с любым событием. Например, контроллер патрулирования можно
связать с событием таймера движка. Тогда с определенной периодичностью этот контроллер будет
обновляться и изменять контролируемый параметр или объект.
Задача триггера - вызов обработчика при срабатывании указанного условия для контролируемого
параметра или объекта.
Общая стратегия использования примерно такая:
Для примера возьмем предыдущий рисунок и нарисуем пол и колонну
- вначале создается движок на одной из поддерживаемых технологий.
//создаем движок типа CanvasDriver, работающий с Canvas объектом
var am = new Animachine.CanvasDriver('#my_canvas')
- объявляются используемые текстуры:
var bi = new Bitmap.Initializer();
var bg = bi.regTexture (“background.jpg”); //простая картинка, размером 200х500
var enemy = bi.regTextureMap(“sprite01.png”, [50, 80]); //текстурная карта с элементами 50x80
var faceColor = bi.regSolidColor(“#face”); //текстура для заливки цветом
var waterFall = bi.regFunctional(functor); //функциональная текстура, управляемая функтором
bi.init(onProgress); //здесь инициализируются все созданные тесктуры и отображается прогресс
- затем структурами можно заполнить полигоны:
var floor = new Primitive.Poligon(bg); //пол, размером с текстуру
floor.y = -100;
//добавляем эффект поворота вокруг верхнего левого угла на угол 90 градусов относительно оси x
floor.effects.add(new Effect.Rotate(floor.pos, [Math.PI/2, 0, 0]));
//4 прямоугольных стены для колонны
var walls = [new Primitive.Polygon(faceColor,
new Primitive.Polygon(waterFall,
new Primitive.Polygon(faceColor,
new Primitive.Polygon(waterFall,
Primitive.Filling.Stretch,
Primitive.Filling.Stretch,
Primitive.Filling.Stretch,
Primitive.Filling.Stretch,
[50,
[200,
[50,
[200,
100]),
100]),
100]),
100])];
/* можно было бы задавать координаты стен так
walls[0].x = 50; walls[0].y = -100; walls[0].z = 350;
walls[2].pos = [50, -100, 20]; //аналогично walls[2].x = 50; walls[2].y = -100; walls[2].z = 20;
walls[1].pos = [100, -100, 350];
walls[3].pos = [50, -100, 20];
но это слабо переносимо, т.к. такую колонну ни передвинуть, ни повернуть нормально не получится.
Самое время применить контейнеры */
- Делаем два контейнера для комнаты и колонны
var column = new Container.Simple(); //простой контейнер с базовой функциональностью
column.add(walls); //можно сделать и после размещения всех эелемнтов, но для примера сделаем тут
column.beginUpdate(); //чтобы не персчитывались размеры при всяком изменении составляющих
walls[0].pos = [0, 0, 200];
walls[1].pos = [50,0]; //x=50, y=z=0
walls[2].pos = [0]; //x=y=z=0
walls[3].pos = [0];
//нужно повернуть две боковые стены относительно оси y на 90 градусов по часовой
var a = [0, Math.PI/2, 0];
walls[1].effects.add(new Effect.Rotate(walls[1].pos, a));
walls[3].effects.add(new Effect.Rotate(walls[3].pos, a));
column.endUpdate(); //закончили размещать стены, можно и пересчитать
//помещаем пол и колонну в общий контйенер
var room = new Container.Simple();
room.add([floor, column]);
am.rootContainer.add(room);
//размещаем колонну по центру пола
сolumn.x = (room.width - column.width)/2;
column.z = (room.depth – column.depth)/2; //контейнеры имеют свойство «глубина», а примитивы – нет
Следует пояснить, что все координаты и размеры носят относительный характер. Если контейнер
добавляется непосредственно в главный контейнер движка (как, например, для room), то все
считается относительно экранной системы координат (оси x и y расположены как обычно, ось z –
ортогональна плоскости монитора и направлена в сторону оператора). Иначе – все считается
относительно координатных осей контейнера. Так в нашем примере, даже если мы повернем
колонну по одной из осей, все координаты и углы поворота ее стен останутся такими же, как были.
Поэтому можно договориться, что шириной контейнера считается проекция описывающего его
прямоугольного параллелепипеда на ось X, высотой – аналогичная проекция на ось Y, глубиной – на
ось Z.
- теперь можно попробовать вывести картинку на экран
//добавим камеру в координатном пространстве экрана
var camera = new Viewport.Perspective(am); //Perspective projection viewport
camera.focus = 0; //камера с нулевым гиперфокальным расстоянием и бесконечной глубиной резкости
camera.a = 2 * Math.PI / 3; //Угол изображения объектива – 120 градусов
camera.pos = [50, 50, 700]; //координаты относительно экрана, т.к. камера не включена в контейнер
Helper.Interaction = new Helper.Interaction.Default(); //Задаем хэлпер интерактивности по умолчанию
Helper.Interaction.fps = 30;
Helper.Interaction.resume(); //теперь камера будет выводить изображение с частотой 30 fps
Что дальше?
После реализации минимальной базовой функциональности движка, можно предпринять попытку
создания на его основе WYSIWYG редактор, который позволит в графическом виде конструировать
простейшие карты. На первых порах, выхлопом этого редактора должен стать валидный JS код уровня
(т.н. параметрическое описание карты) – весьма полезно для отладки, а в дальнейшем можно
изменить это поведение
Download