[Из песочницы] История о создании игры

42cf2c727fec96a8211b7dfcf85da2df.pngСтатья будет о создании второй по счету моей игры под Android, на кроссплатформенном движке Cocos2D-x (v3.1). Код преимущественно на С++, местами Java и Lua. Попытаюсь вкратце рассказать про основные моменты разработки.

ВступлениеПрошел ровно год как я серьезно решил попасть в индустрию. Тогда разработка под мобильные платформы казалось невероятно перспективным, а разработка таких, относительно простых, игр — простой. Будучи 18-летним студентом с небольшим навыком программирования и работы с графикой, но с огромными амбициями хотелось делать что-то свое, что-то огромное и совершенное. Реальность быстро поставила на место и было решено сделать что-то маленькое, но максимально использовать весь свой опыт и навыки. Отсутствие ограничений во времени и отсутствие ответственности перед собой растянуло разработку игры и выход в Google Play состоялся аж в конце марта текущего (2014) года. Несмотря на весьма теплые отзывы на разных форумах, результаты совсем не порадовали — 200 загрузок за все время и около 0.30€ с рекламы.Первый блин — комом 28ef6eaa6fe6395dab09e5d4fea5b2ad.png Разочарование только добавило азарта. Проанализировав неудачу и прочитав немного литературы, через полтора месяца решил написать что-то простое чтобы вложиться в месяц-два разработки. Сказано — сделано! Я захотел сделать головоломку-пазл. Суть игры в том что дается изображение и элементы изображения в разных формах (полупрозрачные и перевернутые, например) и нехитрыми манипуляциями надо поставить их на место.

Первый уровень 30b43c85979306d653469341d2f2b348.gif Cocos2D-x Cocos2D-x — порт популярного движка под iOS. Бесплатный и кроссплатформенный. Если вы только решаете начинать делать игры под мобильные устройства и не знаете какой движок выбрать, обязательно рассмотрите его как вариант.Преимущества, которые стали ключевыми:

кроссплатформенность C++ разработка в Windows, с последующим переносом на Android поддержка Lua открытый исходный код Отсутствие полноценной документации и уроков усложняют начало работы с движком, но в целом он себя проявил очень хорошо. К тому же комьюнити постоянно расширяет функционал и документацию. Позволяет писать 2D игры практически любой сложности, так как можно вызывать функции OpenGL ES напрямую.

Если кому-то будет интересно то могу написать небольшой туториал по созданию «Hello, world!» и основным моментам.

Концепт Итак, как было сказано выше, концепт игры в том что дается картинка и некоторые ее части (элементы) разбросаны в разных местах. Задачей игрока является поставить все элементы на свое место. Элементы могут иметь разные свойства, например: вращение, прозрачность, изменение размера и т.д. Также должна быть возможность создания разных форм и разного поведения.Пример уровня с вращающимися элементами a75ed2d9663ab2ef7235a584c42c0c6e.gif Дизайн Возможности нанять профессионального дизайнера нету. Было решено сделать максимально простой дизайн, без лишних деталей и мне кажется что я в достаточной мере справился с этой задачей.Меню выбора уровня 734bf741517024fbe71cd0581aa95081.png Завершение уровня Слева — выход в меню выбора уровня, центр — название изображения и автор, справа — переход на следующий уровень.bba87e78437e0c976d8e2e500aec30e8.png

В качестве иконок для Google Play и самого приложения использовал скриншот одного из уровней.

Все виды иконок e87e72790963d9f2ffde7d6c3724e965.png Реализация уровней Из описания ясно что главной задачей есть создание такого кода, который без проблем бы позволил создавать разные уровни без перекомпиляции, и при этом сохранять достаточную гибкость для создания элементов разных видов. Для этих целей были выбраны XML и Lua. XML описывает уровень, позиции элементов, их позиции на картинки, форму, прозрачность, размеры и т.д. А также имеет теги, в которые можно вставить кусочки кода написанного на Lua.Пример описания одного из уровней на XML e4Opacity = 5 e4BeginOpacityAnimation = false element: setOpacity (e4Opacity) local dX = touch: getLocation ().x — touch: getPreviousLocation ().x local dY = touch: getLocation ().y — touch: getPreviousLocation ().y local scale = level: getScale () element: setRotation (element: getRotation () + dX / 2 / scale+ dY / 2 / scale) if not e4BeginOpacityAnimation then return end

if 150 > e4Opacity then e4Opacity = e4Opacity + 1 element: setOpacity (e4Opacity) end e4Opacity = nil e4BeginOpacityAnimation = nil e3Opacity = 5 e3BeginOpacityAnimation = false element: setOpacity (e3Opacity) local dX = touch: getLocation ().x — touch: getPreviousLocation ().x local dY = touch: getLocation ().y — touch: getPreviousLocation ().y local scale = level: getScale () element: setRotation (element: getRotation () + dX / 2 / scale+ dY / 2 / scale) if not e3BeginOpacityAnimation then return end if 150 > e3Opacity then e3Opacity = e3Opacity + 1 element: setOpacity (e3Opacity) end e3Opacity = nil e3BeginOpacityAnimation = nil e4BeginOpacityAnimation = true e2Opacity = 5 e2BeginOpacityAnimation = false element: setOpacity (e2Opacity) local dX = touch: getLocation ().x — touch: getPreviousLocation ().x local dY = touch: getLocation ().y — touch: getPreviousLocation ().y local scale = level: getScale () element: setRotation (element: getRotation () + dX / 2 / scale+ dY / 2 / scale) if not e2BeginOpacityAnimation then return end

if 150 > e2Opacity then e2Opacity = e2Opacity + 1 element: setOpacity (e2Opacity) end e2Opacity = nil e2BeginOpacityAnimation = nil e3BeginOpacityAnimation = true e1Opacity = 10 e1BeginOpacityAnimation = false element: setOpacity (e1Opacity) e1BeginOpacityAnimation = true local dY = touch: getLocation ().y — touch: getPreviousLocation ().y local dX = touch: getLocation ().x — touch: getPreviousLocation ().x local scale = level: getScale () element: setPosition (element: getPositionX () + dX / scale, element: getPositionY () + dY / scale) if not e1BeginOpacityAnimation then return end

if 200 > e1Opacity then e1Opacity = e1Opacity + 2 element: setOpacity (e1Opacity) end e1Opacity = nil e1BeginOpacityAnimation = nil e2BeginOpacityAnimation = true

local dY = touch: getLocation ().y — touch: getPreviousLocation ().y local dX = touch: getLocation ().x — touch: getPreviousLocation ().x local scale = level: getScale () element: setPosition (element: getPositionX () + dX / scale, element: getPositionY () + dY / scale) local dY = touch: getLocation ().y — touch: getPreviousLocation ().y local dX = touch: getLocation ().x — touch: getPreviousLocation ().x local scale = level: getScale () element: setPosition (element: getPositionX () + dX / scale, element: getPositionY () + dY / scale) local dY = touch: getLocation ().y — touch: getPreviousLocation ().y local dX = touch: getLocation ().x — touch: getPreviousLocation ().x local scale = level: getScale () element: setPosition (element: getPositionX () + dX / scale, element: getPositionY () + dY / scale) local dY = touch: getLocation ().y — touch: getPreviousLocation ().y local dX = touch: getLocation ().x — touch: getPreviousLocation ().x local scale = level: getScale () element: setPosition (element: getPositionX () + dX / scale, element: getPositionY () + dY / scale)

С помощью Lua можно писать такие функции как:

onCreate — вызывается при создании элемента onTouchBeganFunction — вызывается при «прикосновении» к элементу onTouchMovedFunction — вызывается при перемещении прикосновения onTouchEndedFunction — вызывается при окончании прикосновения onUpdateFunction — вызывается при обновлении элемента onDestroy — вызывается при уничтожении элемента В каждую функции передаются такие переменные как сам эелемент, спрайт элемента, спрайт формы, уровень и спрайт уровня. В функции «onTouch» также передается переменная типа «Touch», которая имеет в себе такие данные как положение прикосновения или предыдущее положение. В связи с тем что Cocos2D-x поддерживает Lua, то можно работать с функциями и объектами Cocos2D-x напрямую, передавая их в качестве аргументов. Такая реализация уровней получилась достаточно эффективной и очень гибкой (ведь даже можно, например, закрыть игру при нажатии на элемент или управлять другими элементами).

Реализация вызова Lua-функций элементов на Cocos2D-x выглядит так (на примере функции onCreate, которая вызывается при создании элемента):

Скрытый текст if (! m_scriptFunctionOnCreate.empty ()) { LuaEngine* engine = LuaEngine: getInstance (); LuaStack* luaS = engine→getLuaStack (); luaS→executeString (m_scriptFunctionOnCreate.c_str ()); lua_getglobal (luaS→getLuaState (), «onCreate»); luaS→pushObject (this, «cc.Node»); // element luaS→pushObject (m_sprite, «cc.Sprite»); // element sprite if (m_shapeMaskSprite!= nullptr) { luaS→pushObject (m_shapeMaskSprite, «cc.Sprite»); // shape } else { luaS→pushNil (); } luaS→pushObject (getParent (), «cc.Node»); // level luaS→pushObject (((Level*)getParent ())→getLevelSprite (), «cc.Sprite»); // levelSprite lua_call (luaS→getLuaState (), 5, 0); luaS→clean (); } Вместо заключения Основной целью создания этой игры было скорее «сделать, чтобы сделать», поэтому нету никакого ожидания успеха. Возможно в ближайшем будущем выложу её исходный код в свободный доступ.Если будет интересно, то в следующих статьях расскажу про подключения AdMob и Google Analytics в игру, написанную на Cocos2D-x.

© Habrahabr.ru