![](https://habrastorage.org/getpro/habr/post_images/617/3f7/d0c/6173f7d0c7b03aed02fcf856f58cb526.png)
Сегодня я хочу рассказать об одном малоизвестном игровом движке, который мы используем уже год для кроссплатформенной разработки мобильных игр. Для 2д он нас полностью устраивает, а единственным конкурентом может быть только Unity3d из-за своего редактора. Отсутствие должного внимания к MOAI SDK, очевидно, связано с высоким порогом входа — сами разработчики (Zipline Games) позиционируют свой продукт как «The mobile platform for pro game developers», хотя разобравшись с установкой и настройкой окружения можно очень быстро и просто клепать игры на Lua.
Чем же мне так понравился MOAI:
- Движок написан на C++, игровая логика пишется на Lua. Есть поддержка luajit (на iOS только в режиме интерпретатора)
- Открытый исходный код. Лицензия CPAL — необходимо указывать логотип, название, авторов и ссылку на MOAI на загрузочном экране или в титрах
- Кроссплатформенный: Windows, Mac OS X, Linux, iOS, Android, OUYA, а также есть эксперименты с html5 через emscripten. Разработка поддерживается на Windows, Mac OS X и Linux. Для сборки под iOS нужен Mac. Теоретически, добавить новую платформу не сложно — надо написать «хост», который создаст OpenGL контекст и будет вызывать методы обработки ввода
- Lua API очень низкоуровневый. Это и плюс, и минус. Для эффективной разработки просто необходим Lua фреймворк более высокого уровня
- Внутри используются крутые алгоритмы и технологии. Рендерер с автоматическим батчингом (cocos2d вроде тоже этому научился). Action tree — все что периодически обновляется (анимации, физика) представлено в виде дерева, родительские узлы передают прошедшее время (дельту) своим детям; это позволяет выстраивать иерархию анимаций, останавливать и продолжать их разом, менять скорость воспроизведения
- Нет привычного графа сцены как в других 2д движках. Для обработки зависимостей используется dependency graph. У объектов есть множество атрибутов (координата, цвет, шейдер, ...) — можно задавать зависимости между ними, например, привязать поворот одного спрайта к координате Х другого. Движок производит рассчет только изменившихся атрибутов, что по идее уменьшает затраты на обновление. По факту это похоже на нодовую архитектуру Maya, Nuke, материалов из UE — привязывай что хочешь к чему хочешь, лишь бы тип совпадал. Есть специальный ScriptNode которому можно добавлять свои атрибуты и задавать коллбек на их обработку
- Несколько классов для работы с тайлмапами. Hex, diamond, rectangular сетки, поиск пути
- Есть поддержка 3д — то есть все трансформы и объекты по сути своей 3д
Минусы и недоработки:
- Пробелы в документации, примеры часто нерабочие. Немногочисленное сообщество
- Для сборки используется Cmake. Здесь нет одной кнопки «собрать под ...» как в Юнити или Короне, часто приходится бороться с ошибками компилятора и линкера
- Lua api не полный. Например, часто нет баланса между сеттерами и геттерами (сеттеров больше). Поэтому полезно разбираться в написании биндингов и расширять их при необходимости
- Нет редактора
- Встроенный звуковой движок UNTZ очень примитивный. Есть биндинги к fmod, но скорее всего они сильно устарели, т.к. ими вроде как никто не пользуется
- Видимо, с монетизацией фреймворка у разработчиков не сложилось — делать платным и закрытым его категорически не хочется, а MOAI Cloud не оправдал себя. На всякую ерунду типа приведения в порядок документации и примеров у Zipline Games нет ни времени, ни мотивации, все в основном делается сообществом. Это не значит что MOAI SDK мертв, нет, Zipline Games используют MOAI для внутренних проектов, развитие продолжается (вот совсем недавно появилась поддержка векторных примитивов через libtess, в процессе разработки собственный движок для обработки простых столкновений, чтобы не тащить целиком box2d или chipmunk)
Установка (Mac OS X)
Опишу установку только на OS X, т.к. не имею под рукой windows системы.
Клонируем официальный репозиторий:
git clone https://github.com/moai/moai-dev.git
Запускаем скрипт, который соберет хост под нашу систему:
cd moai-dev
bin/build-osx-sdl.sh
Надеюсь все пройдет без ошибок, тогда исполняемый файл станет доступен в release/osx/host-sdl/bin/moai, сделаем на него линк (~/bin у меня добавлен в PATH):
ln -s /Users/vavius/moai-dev/release/osx/host-sdl/bin/moai ~/bin/moai
Запускаем пример, убеждаемся что все работает:
cd samples/hello-moai
moai
Видим крутящуюся фиговину и приветствующий текст:
![](https://habrastorage.org/getpro/habr/post_images/825/847/dec/825847decddeeca51c91a6fd43864a1a.gif)
Пишем на Lua
Начнем с минимального примера — нарисуем квадратный спрайт по центру экрана:
![](https://habrastorage.org/getpro/habr/post_images/674/3e0/49a/6743e049a09b91b8703620c8079b57fd.png)
Код:
-- 1
MOAISim.openWindow ( "sample", 600, 240 )
local viewport = MOAIViewport.new ()
viewport:setSize ( 600, 240 )
viewport:setScale ( 600, 240 )
-- 2
local layer = MOAILayer.new ()
layer:setViewport ( viewport )
-- 3
local renderTable = { layer }
MOAIGfxDevice.getFrameBuffer ():setRenderTable ( renderTable )
-- 4
local deck = MOAIGfxQuad2D.new ()
deck:setTexture ( "moai.png" )
deck:setRect ( -64, -64, 64, 64 )
-- 5
local prop = MOAIProp.new ()
prop:setDeck ( deck )
layer:insertProp ( prop )
Описание происходящего по пунктам:
- Создаем окно и вьюпорт. Вьюпорт позволяет работать в логических координатах, не привязываясь к пикселям
- Слой — это контейнер для пропов (а пропами называется все что может быть нарисовано). Слою можно задать камеру, при этом будут рендериться только те пропы, которые попадают в текущий вьюпорт. Слой отвественнен за сортировку (порядок рендеринга) — есть множество вариантов, по координате X, Y, Z по отдельности или по вектору, по приоритету (целое число), а также специальный вид ISO_SORT для изометрических тайловых игр. У каждого слоя есть контейнер MOAIPartition для оптимизаций пространственных запросов — хит теста и raycast'ов, внутри используется многоуровневая сетка
- Задаем рендер таблицу — список renderable объектов, которые будут отрисованы во фреймбуфер по порядку. Слой при рендере рисует все добавленные в него пропы. Таблица может содержать вложенные таблицы, что очень удобно для создания менеджера сцен
- Deck — объект, определяющий визуальную часть. Он хранит геометрию (в данном случае квад — два треугольника), UV координаты, ссылку на текстуру и шейдер
- Prop — собирательный образ объекта, который рисуется на экране. Это в принципе не обязательно один отдельный спрайт, а также может быть и 3д меш и тайловая карта в зависимости от установленной деки
Сразу бросается в глаза, что уж больно много строк нужно для казалось бы простой задачи нарисовать спрайт. Чаще всего это делается в одну строчку, а тут надо как минимум шесть. Поэтому, API предоставляемый движком не используется «как есть», а многие пишут свои обертки на Lua. В С++ части MOAI SDK отсутствуют многие привычные вещи такие как: кэш текстур, загрузка спрайтов из атласов, менеджер сцен и переходов между ними, всякие кнопки и другие гуи элементы. Все это предлагается реализовать на Lua. На общей производительности это врядли сильно скажется, поскольку большинство операций выполняются только при ининциализации сцены. Конечно, надо подходить с умом к написанию кода, не плодить лишних таблиц, использовать старые объекты, кэшировать что можно.
Полная свобода
Допустим, не хотим квадратный квад, а хотим трапецию, вместо setRect используем setQuad:
local deck = MOAIGfxQuad2D.new ()
deck:setTexture ( "moai.png" )
deck:setQuad (
-64, 64,
64, 64,
100, -64,
-100, -64 ) -- координаты вершин с левого верхнего угла по часовой стрелке
Вот что выйдет:
![](https://habrastorage.org/getpro/habr/post_images/ace/f98/0c8/acef980c8f09e365ad334bf86cf74043.png)
Замостим фон целиком нашей картинкой. Можно использовать GL_REPEAT на текстуре, но он работает только для размеров кратных степеням двойки, и картинку с атласа не получится использовать. Поэтому воспользуемся классом MOAIGrid:
local deck = MOAIGfxQuad2D.new ()
deck:setTexture ( "moai.png" )
deck:setRect ( -0.5, -0.5, 0.5, 0.5 )
local grid = MOAIGrid.new ()
grid:initRectGrid ( 1, 1, 128, 128 )
grid:fill ( 1 )
grid:setRepeat ( true, true )
local prop = MOAIProp.new ()
prop:setDeck ( deck )
prop:setGrid ( grid )
layer:insertProp ( prop )
MOAIGrid нужен для работы с тайлами, здесь мы инициализируем карту из одного тайла размером 128x128. Затем ставим ему индекс 1 методом fill. Некоторые типы дек поддерживают индексацию, например MOAIGfxQuadDeck2D позволяет задавать много пар вершинных и UV координат для одной текстуры, что используется для представления спрайтового атласа. В данном случае в нашей деке есть только один единственный индекс 1. Включаем повторение и указываем пропу, что следует использовать сетку для рендеринга.
![](https://habrastorage.org/getpro/habr/post_images/36d/e99/81b/36de9981b2f16e5eb31467a466a5c520.png)
На этом этапе очень просто сделать анимированный прокручивающийся фон. Добавляем одну строчку в конец:
prop:moveLoc ( -128, 0, 0, 4, MOAIEaseType.LINEAR ):setMode ( MOAITimer.LOOP )
Просто в цикле двигаем наш проп на ширину одного тайла, а движок сам подставляет недостающие куски чтобы замостить весь экран:
![](https://habrastorage.org/getpro/habr/post_images/295/dd5/1e3/295dd51e35a0afb2bfb4f1a20c65e991.gif)
Анимация и Action tree
Перемещение и вращение объектов, а также установка режимов проигрывания анимаций:
local move = prop:moveLoc ( 200, 0, 0, 2 ) -- двигаем
local rot = prop:moveRot ( 0, 0, 360, 2, MOAIEaseType.LINEAR ) -- крутим вокруг оси Z
move:setMode ( MOAITimer.PING_PONG ) -- вперед-назад
rot:setMode ( MOAITimer.LOOP ) -- цикл
По умолчанию анимации добавляются к корню action tree. Но можно их группировать:
local action = MOAIAction.new ()
action:addChild ( move )
action:addChild ( rot )
action:start ()
action:throttle ( 0.5 )
Теперь мы можем останавливать и запускать сразу обе анимации, а с помощью throttle задавать скорость воспроизведения.
Последовательность действий реализуется через Lua корутины. MOAI предоставляет класс MOAICoroutine, унаследованный от MOAIAction, что позволяет добавлять корутины в action tree. Функция blockOnAction вызывает yield пока экшн не закончится.
Двигаем картинку вправо-влево, а при достижении крайних точек делаем один полный оборот:
local function func ()
local distance = 200
while true do
local action1 = prop:moveLoc ( distance, 0, 0, 2 )
MOAICoroutine.blockOnAction ( action1 )
local action2 = prop:moveRot ( 0, 0, 360, 2 )
MOAICoroutine.blockOnAction ( action2 )
distance = -distance
end
end
local thread = MOAICoroutine.new ()
thread:run ( func )
![](https://habrastorage.org/getpro/habr/post_images/e10/2a4/a32/e102a4a32793484778aee9ba41d8557c.gif)
Заключение
В статье рассмотрены совсем примитивные примеры — моей целью было показать некоторые аспекты Lua API, а именно его низкоуровневость и модульность. MOAI SDK старается не принимать за нас никаких решений, не заставляет все делать каким-то одним общепризнанным способом, а оставляет полную свободу. Конечно же сообщество уже реализовало несколько высокоуровневых оберток на чистом Lua, с кэшированием текстур, гуи элементами, менеджером сцен и т.д.
Я бы не советовал использовать MOAI SDK в продакшене без знания С++, нюансов сборки под выбранную платформу и готовности что-то менять внутри. Практически каждый, кто использует MOAI SDK имеет свой форк, который немного отличается от главной ветки. Исторически это связано с тем, что у Zipline Games не было времени мержить пулл-реквесты. Однако, сейчас некоторые члены сообщества получили доступ к официальному репозиторию и разработка пошла бодрее.
Благодаря открытости мы смогли реализовать live reload кода и ресурсов прямо на девайс. Сейчас я потихоньку пилю редактор, по образу и подобию Unity3d. Хотя дела пошли медленнее после заработавшего live reload'a — его хватает с головой для невероятного ускорения разработки. Интерфейсы собираем в векторном редакторе и экпортим сразу в код (декларативного вида, вот пример: gist.github.com/Vavius/9868572). Конечно, все это можно было приделать к любому движку, к cocos2d-x вообще без проблем, к Короне посложней, но тоже реально. Вобщем, для 2д игр пересели мы с кокоса на MOAI и нисколько не жалеем, здесь как-то все более по-взрослому, гибче и круче + код чистый и красивый.
Ссылки
getmoai.com — официальный сайт
getmoai.com/docs/annotated.html — доки
moaiwebsite.github.io — неофициальный сайт, пилится сообществом. Когда-нибудь станет новым лицом
github.com/makotok/Hanappe — высокоуровневый Lua-фреймворк в ООП-стиле. Из всех подобных решений только этот сейчас развивается и поддерживается.
Update:
moaifiddle.com/Q09BJWGMW6/3 — js версия движка. Теперь можно поиграться с движком без установки!