habrahabr

Cocos2d-x: Пишем на Lua

  • понедельник, 4 августа 2014 г. в 03:10:59
http://habrahabr.ru/post/232013/



Доброго времени суток.
Начнем с того, что я не нашел на хабре туториалов по Cocos2d и Lua, поэтому мне пришлось много страдать и чтобы вы не повторяли моих ошибок я решил написать пост. В этой статье я расскажу как создать простую игру используя Сocos2d-x, Cocos Code IDE и Lua. Ранее, я уже писал про создание игр на Love2d. В этой статье я адаптирую старый туториал для кокоса и как это запустить на андроиде (Ни яблока, ни мака у меня нет).

Что нам понадобится?


Список важных вещей:
  • Java JDK
  • Сам Cocos2d-x. На момент выхода этой статьи вышел Cocos2d-x 3.2 и скачать его можно здесь. Нужно скачать той же разрядности, что и JDK
  • IDE для lua. Разработчики движка постарались и выпустили свой IDE на основе Eclipse. Качаем здесь.
  • [Windows-only]Python 2.7. Лежит вот тут.

Если вы хотите писать игры под андроид, то придется скачать еще пару вещей, а именно:
  • Android SDK. Если вы не собираетесь писать под андроид, то лучше найти вкладку «GET THE SDK FOR AN EXISTING IDE»
  • Android NDK. Я использую Android NDK r9, а с r10 возможны проблемы
  • Apache Ant. У меня стоит 1.9.4

Установка


Сначала поставим Java JDK и Python. После распакуйте cocos2d-x и Cocos Code IDE (Если вы скачивали zip версию, если нет, просто запустите установку) в удобное для вас место. Распаковываем Android SDK, NDK и Apache Ant так же в удобное место. Путь к ним не должен содержать пробелов во избежание проблем. Открываем папку с SDK, находим SDK Manager. Запускаем и ставим нужные для нас версии API и всю папку Tools.
Запускаем Cocos Code IDE. В Window->Preferences->Cocos указываем нудные пути. У меня это выглядит так:
.
Теперь кликаем по вкладке Lua которая находится во вкладке Cocos и выбираем путь к cocos2d-x:

Настроено.

Немного теории


Cocos2d-x — кроссплатформенный движок, написанный на С++, был создан как копия Cocos2d. Cocos2d использует Object C и может использоваться только для iOS.
Теперь поговорим о базовой механике движка и сравним ее с Love2D. У кокоса все объекты находятся в слоях, а слои находятся в сценах. В Love2D ни слоев, ни сцен нет. Плюсы сцен и слоев в том, что с их помощью можно создавать гибкое приложение более простыми способами. Минусы в том, что для просто игры это может быть слишком громоздко и неудобно. В кокосе все представленно объектами и чтобы что-то появилось на экране надо просто добавить это на сцену, а в love2d все представленно простыми переменными и чтобы что-то появилось на экране нужно это каждый кадр рисовать. Это можно описывать достаточно долго поэтому я скажу вывод сейчас, а разницу вы сможете почувствовать сами. Cocos2d-x — большой движок с большим количеством возможностей, однако доступ к ним часто сделан не так как хотелось бы. Love2d — простой, не требующий много знаний, движок с отзывчивым коммьюнити, но с отсутствием многих нужных вещей, которые впоследствии пишут сами пользователи, но делают это вполне удачно.

А теперь сам код


Создадим новый проект. И у нас будет два файла в исходниках: hello2.lua и main.lua. Если вы не хотите посмотреть на чудо китайского гейм дева, то первый можно сразу удалять, а второй очистить. Он создан лоя демонстрации возможностей require. Открываем main.lua и видим кучу кода которая нужна чтобы понять как использовать порт под lua. Чтобы увидеть, что это такое нажимаем F11. Можно поиграться. Вот это бегающие существо это собака, а не белка как вы могли подумать. Посмотрели, поигрались и хватит. Удалите hello2.lua и весь код в main.lua и очизаем папку res. В папку res добавляем картинку и называем ее habr.png. Теперь пишем в main следующий код:
--Все классы кокоса
require "Cocos2d"
--Все константы кокоса
require "Cocos2dConstants"

-- Функция вывода сообщений
cclog = function(...)
    print(string.format(...))
end

-- Эта функция выводит ошибки
function __G__TRACKBACK__(msg)
    cclog("\n----------------------------------------")
    cclog("LUA ERROR: " .. tostring(msg))
    cclog(debug.traceback())
    cclog("----------------------------------------")
    return msg
end

collectgarbage("collect")
-- Предотвращает утечку памяти
collectgarbage("setpause", 100)
collectgarbage("setstepmul", 5000)

--Теперь можно использовать файлы которые лежат в папках "src" и "res"
cc.FileUtils:getInstance():addSearchResolutionsOrder("src");
cc.FileUtils:getInstance():addSearchResolutionsOrder("res");

--Записываем в константы ширину и высоту экрана
SCREEN_WIDTH = cc.Director:getInstance():getWinSize().width
SCREEN_HEIGHT = cc.Director:getInstance():getWinSize().height 

local function main()
    print("Resolution: " .. SCREEN_WIDTH .. "x" .. SCREEN_HEIGHT)
    --Запускаем главную сцену
    require "mainScene.lua"
end

--Вызываем функцию main
local status, msg = xpcall(main, __G__TRACKBACK__)
if not status then
    error(msg)
end


В функции main мы вызываем файл mainScene.lua. В нем находится главная сцена игры. Создадим его и напишем туда следующий код:
-- Константы
local NONE = 0
local ROTATION = 1
local SCALLING = 2
local MOVING = 3 
-- Переменные
local state, rotation, scale, ox, oy, delta, habrImage, moving

-- Эта функция будет вызываться когда палец касается экрана
local function onTouchBegan(touch, event)
    -- Включаем следующие состояние
    state = (state + 1) % 4
    -- И возвращаем переменные к дефолту
    resetVariables()
end

-- Эта функция вызывается постоянно, а в dt хранится время с
-- прошлого вызова. Если dt == 1.0, то с прошлого вызова прошла
-- одна секунда
local function update(dt)
    if state == ROTATION then
        --крутим картинку
        rotation = rotation + delta * dt
    elseif state == SCALLING then
        --увеличиваем
        scale = scale + dt * delta
    elseif state == MOVING then
        --Здесь немного посложнее, но все же просто:
        --Каждый раз мы увеличивыем переменную moving, а
        --потом берем ее за угол для косинису и синуса
        --и крутим картинку по кругу
        moving = moving + delta * dt
        local radius = 50
        ox = radius * math.sin(moving)
        oy = radius * math.cos(moving)
    end 
    
    -- В love2d мы использовали эти параметры при рисовании
    -- Здесь у нас спрайт и нам нужно постоянно менять ему
    -- все вручную
    
    -- Меняем размер
    habrImage:setScale(scale)
    -- Меняем угол
    habrImage:setRotation(rotation)
    -- Меняем позицию
    habrImage:setPosition(cc.p(SCREEN_WIDTH / 2 + ox, SCREEN_HEIGHT / 2 + oy))
end

local function init()
    print("Creating main scene")
    -- Создаем сцену
    local mainScene = cc.Scene:create()
    -- Создаем слой с фоновым цветом
    -- Здесь я бы хотел поставить паузу и рассказать более подробно
    -- потому что у меня ушло 2 часа чтобы узнать как мне изменить цвет
    -- фона. 1) В C++ нет функции cc.c4b(r,g,b,a) поэтому мне пришлось
    -- догадаться, что нужно смотреть не на C++  таких случаях, а на
    -- cocos2d-x-js, т.к. он более популярен чем cocos2d-x-lua
    -- и если вы не можете найти нужную функцию в C++, то ищите ее в
    -- JavaScript версии, однако это не поможет с евентами
    local gameLayer = cc.LayerColor:create(cc.c4b(255, 255, 255, 255))
    -- Создаем спрайт
    habrImage = cc.Sprite:create("res/habr.png")
    --Ставим его смещение по центру
    habrImage:setAnchorPoint(0.5, 0.5)
    --Ставим позицию на центр экрана
    habrImage:setPosition(cc.p(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2))
    --Добавляем его к слою
    gameLayer:addChild(habrImage)

    state = 0
    resetVariables()
    
    -- Чтобы функция update вызывалась нужно сообщить кокосу, что ее нужно
    -- вызывать
    cc.Director:getInstance():getScheduler():scheduleScriptFunc(update, 0, false)
    -- Создаем лисениры для событий. Хочу обратить внимание, что для каждого
    -- типа событий есть свой лисенер.
    local listener = cc.EventListenerTouchOneByOne:create()
    -- Говорим лисениру, что такой то тип событий связвн с такой то функцией
    listener:registerScriptHandler(onTouchBegan, cc.Handler.EVENT_TOUCH_BEGAN )
    -- Добавляем лисенер к слою
    local eventDispatcher = gameLayer:getEventDispatcher()
    eventDispatcher:addEventListenerWithSceneGraphPriority(listener, gameLayer)
    --Добавляем слой к сцене
    mainScene:addChild(gameLayer)
    
    --Запускаем или если уже что-либо запущено заменяем сцену
    if cc.Director:getInstance():getRunningScene() then
        cc.Director:getInstance():replaceScene(mainScene)
    else
        cc.Director:getInstance():runWithScene(mainScene)
    end
end

function resetVariables()
    rotation = 0
    scale = 1
    ox = 0
    oy = 0
    delta = 20
    moving = 0
end

--Запускаем сцену
init()


Теперь немного о помощи с поиском ответов. Во-первых: Порт на луа практически идентичен С++ и основные отличия можно прочесть здесь. Во-вторых: как я ранее писал, если вы не можете найти что-либо в порте С++, то попробуйте поискать эту проблему для JS движка. Он более популярен чем луа, однако в нем есть множество отличий, но очень часто он мне помогал. Чтобы запустить то, что у нас вышло нажмите F11. Чтобы запустить на андроид нажмите на кнопку «Debug configurations» и выберите андроид. Для запуска нужен настроенный adb, но про это и так много статей, поэтому я не буду писать здесь об этом. На этом все Спасибо за прочтение (:

Вот что вышло в итоге: