Знакомство с Marmalade Quick, новый игрок в быстрой кроссплатформенной разработке



Добрый день!

Честно говоря, я немного удивлен, почему на хабре об этом ещё нет упоминания. Возможно из-за того, что инди разработчикам уже ничего не светит? Быть может причина в запредельной цене, которую просит Marmalade за свой продукт? Или альтернатив столько много, что за всем и не уследить? Как бы там не было, сегодня поговорим именно об Marmalade Quick и постараюсь Вам показать, что свое название оно вполне оправдывает. Опыта в мобильных разработках я не имел, но Quick настолько прост, что за пару часов мне удалось написать полноценное приложение.

Почему бы и Вам не попробовать ...

Что собственно из себя представляет Marmalade Quick?


Marmalade Quick позволяет писать приложения на языке Lua, который сейчас имеет достаточно большую популярность среди разработчиков игр. Lua заманчив тем, он очень простой и достаточно мощный язык. Возможность подключения таких модулей как: Cocos2D-x для 2D графики, Box2D для физики и SQLite для локального хранения данных, JSON для обмена данными.

На данный момент версия 1.0, но уже реализовано и добавлено в API:
  • Воспроизведение аудио и видео;
  • Проведение платежных операция в Google Play, BlackBerry World, Apple App Store;
  • Компас и определение местоположения;
  • Авторизация в Facebook и обновление статуса;
  • Получение общей информации об устройстве;
  • Нативные UI компоненты;
  • Сокеты;
  • Физика;

Ядром Marmalade Quick является "OpenQuick", открытый и полностью независимый от Marmalade код.
Marmalade Quick и OpenQuick распространяется под лицензией MIT.
Задавать вопросы, на которые достаточно быстро дают решения, можно здесь.
API на английском языке здесь.

Подготовительная часть


  1. Скачиваем и устанавливаем Marmalade SDK 6.2 и выше (30 дней trial версия);
  2. Скачиваем Marmalade Quick. Распаковываем в папку с Marmalade SDK (/quick);
  3. Запускаем quickLaunchPad.exe в папке /quick/tools;
  4. Import Project — импорт готовых проектов, в частности можно протестировать работу «Hello World» из папки /quick/data/examples;
  5. New Project — создаем новый проект;

Переходим к разработке


Что у нас должно в конце получиться? — Бутылочка! Выбираем число игроков, крутим бутылку и на кого попадает горлышка, тот выполняет задание.



Создав новый проект, у вас создается папка с файлами проекта. Её расположение можно увидеть в Source Location, откроем её Open Folder. Папка в которой все наши файлы это /resources, а главный исполняющий файл main.lua, в нем мы и будем работать.

Определяем разрешение экрана:
local dx=director.displayWidth
local dy=director.displayHeight

Вычисляем число, на которое будем масштабировать все элементы на экране, считая что наше приложение рассчитано на экран не больше 1280x768:
local skalex = dx/1280
local skaley = dy/768

Создадим первую сцену, где расставлены бутылки для выбора числа игроков (от 4-х до 10-и):
local scene1 = director:createScene()

-- Рисуем фон
local fon = director:createSprite( {
    x=0, y=0, -- отсчет идет с левого нижнего угла
    source="fon.jpg", -- файл изображения
    xScale=skalex, yScale=skaley -- масштабирование, для экранов отличных от 1280x768
} )

-- По порядку расставляем бутылки
local player4 = director:createSprite( {
    x=dx/2-360*skalex, y=dy/2,
    xAnchor=0.5, yAnchor=0.5, -- точка вращения картинки, в нашем случае это её центр; 1 - правый верхний угол; 0 - левый нижний угол
    source="but/player4.png",
    xScale=skalex, yScale=skaley
} )

local player5 = director:createSprite( {
    x=dx/2-240*skalex, y=dy/2,
    xAnchor=0.5, yAnchor=0.5,
    source="but/player5.png",
    xScale=skalex, yScale=skaley
} )

local player6 = director:createSprite( {
    x=dx/2-120*skalex, y=dy/2,
    xAnchor=0.5, yAnchor=0.5,
    source="but/player6.png",
    xScale=skalex, yScale=skaley
} )

local player7 = director:createSprite( {
    x=dx/2, y=dy/2,
    xAnchor=0.5, yAnchor=0.5,
    source="but/player7.png",
    xScale=skalex, yScale=skaley
} )

local player8 = director:createSprite( {
    x=dx/2+120*skalex, y=dy/2,
    xAnchor=0.5, yAnchor=0.5,
    source="but/player8.png",
    xScale=skalex, yScale=skaley
} )

local player9 = director:createSprite( {
    x=dx/2+240*skalex, y=dy/2,
    xAnchor=0.5, yAnchor=0.5,
    source="but/player9.png",
    xScale=skalex, yScale=skaley
} )

local player10 = director:createSprite( {
    x=dx/2+360*skalex, y=dy/2,
    xAnchor=0.5, yAnchor=0.5,
    source="but/player10.png",
    xScale=skalex, yScale=skaley
} )

Так, у нас уже есть что-то вроде такого:



Теперь создадим сцену вторую, в ней то и происходит вся игра. Выводить поэтапно я не буду, а лучше напишу подробные комментарии.
local scene2 = director:createScene()

local fon = director:createSprite( {
    x=0, y=0,
    source="fon.jpg",
    xScale=skalex, yScale=skaley
} )

-- кнопка меню
local menu = director:createSprite( {
    x=dx, y=dy,
    xAnchor=1, yAnchor=1,
    source="menu.png",
    xScale=skalex, yScale=skaley
} )

-- кнопка "Раскрутить" бутылку
local buty = director:createSprite( {
    x=dx/2, y=20,
    xAnchor=0.5, yAnchor=0,
    source="raskrutit.png",
    xScale=skalex, yScale=skaley
} )

local neLez = nil -- создадим пустую переменную

local but = function()
    neLez = 2
    tween:to(buty, { alpha=1, time=0.5, onComplete = kruti} ) -- здесь происходит плавное появление за 0.5 сек выше созданной кнопки "Раскрутить" (kruti).
end

local win_p = "WIN" -- переменная, в которую заносится победитель

local fonz = function(event)
    audio:stopStream() -- остановим воспроизведение музыки
    r = math.random(1,42) -- создаем случайное число от 1 до 42
    s = "zadanie/"..r..".png" -- в папке 42 картинки с заданиями, случайным образом достаем одну из них
    -- выводим на экран задание
    local zadanie = director:createSprite( {
        x=dx/2, y=dy/2, -- центр экрана
        xAnchor=0.5, yAnchor=0.5,
        source=s,
        xScale=skalex, yScale=skaley
    } )
    -- выводим на экран кнопку "Сделано"
    local sdelano = director:createSprite( {
        x=dx/2, y=dy/2-180*skaley,
        xAnchor=0.5, yAnchor=0.5,
        source="sdelano.png",
        xScale=skalex, yScale=skaley
    } )
    -- выведем текст, с номером игрока  на котором остановилось горлышко
    local label = director:createLabel( {
        x=dx/2-90, y=dy/2+120,
        xAnchor=0.5, yAnchor=0.5,
        font="fonts/ComicSans24.fnt", -- файл шрифтов, обязательно формат .fnt
        color={99, 61, 2}, -- цвет
        xScale=3, yScale=3, -- увеличиваем размер шрифта на 3
        text="Player "..win_p
    } )
    local touchz = function(event)
        if event.phase == "began" then -- произошло событие нажатия - began; ended - завершение нажатия; moved - скольжение по экрану; 
            tween:to(label, { alpha=0, time=0.5} ) -- исчезает текст
            tween:to(zadanie, { alpha=0, time=0.5} ) -- исчезает задание
            tween:to(sdelano, { alpha=0, time=0.5, onComplete = but} )  -- исчезает кнопка "Сделано" и после исчезновения вызывается функция but, которая выводи на экран кнопку "Раскрутить"
        end
    end
    sdelano:addEventListener("touch", touchz) -- отслеживаем нажатие ("touch") на кнопку "Сделано" и после чего вызываем функцию touchz 
end

local kruti = function(event)
    if event.phase == "ended" then -- нажатие
        if neLez == nil or neLez == 2 then
            local payer = math.random(1,360) -- определяем, на кого попадёт горлышко, случайный градус от 1 до 360
            win_p = math.ceil(payer/(360/players)) -- вычисляем, на кого попало горлышко, зная сколько всего игроков играет
            -- бутылка, которой играют
            local butyl = director:createSprite( {
                x=dx/2, y=dy/2,
                xAnchor=0.5, yAnchor=0.5,
                source=play_but,
                xScale=skalex, yScale=skaley
            } )
            audio:playStreamWithLoop("mus.mp3", false) -- воспроизведение .mp3 файла, начинается при раскрутке бутылки, false - без повторений
            tween:to(buty, { alpha=0, time=0.5} ) -- исчезает кнопка "Раскрутить"
            tween:to(butyl, { rotation=payer+5400, easing=ease.expOut, easingValue=2, time=12} ) -- rotation - угол на который повернуть бутылку, в нашем случае мы его случайным образом создали, но чтобы кручение было побольше прибавим ещё 15 оборотов (5400 градусов), easing=ease.expOut - постепенное затухание за 12 сек
            tween:to(butyl, { alpha=0.1, delay=14, time=1, onComplete = fonz } ) -- delay - ждём 14 сек, делаем видимость бутылки по альфа каналу 0.1 и вызываем функцию fonz, которая отвечает за вывод задания на экран
            neLez = 1
        end
    end
end




Дальше собственно элементы управления для первой сцены, так как мы нарисовали только бутылки, а теперь добавим им нажатия.
local touch4 = function(event) -- функция нажатия на 4-ю бутылку
    players = 4 -- изменяем переменную, отвечающую за числом игроков
    play_but = "but/4player.png" -- файл бутылки, которой будем играть
    if event.phase == "began" then -- произошло нажатие
        director:moveToScene(scene2, {transitionType="crossFade", transitionTime=0.5}) -- переход к сцене 2
    end
end
local touch5 = function(event)
    players = 5
    play_but = "but/5player.png"
    if event.phase == "began" then
        director:moveToScene(scene2, {transitionType="crossFade", transitionTime=0.5})
    end
end
local touch6 = function(event)
    players = 6
    play_but = "but/6player.png"
    if event.phase == "began" then
        director:moveToScene(scene2, {transitionType="crossFade", transitionTime=0.5})
    end
end
local touch7 = function(event)
    players = 7
    play_but = "but/7player.png"
    if event.phase == "began" then
        director:moveToScene(scene2, {transitionType="crossFade", transitionTime=0.5})
    end
end
local touch8 = function(event)
    players = 8
    play_but = "but/8player.png"
    if event.phase == "began" then
        director:moveToScene(scene2, {transitionType="crossFade", transitionTime=0.5})
    end
end
local touch9 = function(event)
    players = 9
    play_but = "but/9player.png"
    if event.phase == "began" then
        director:moveToScene(scene2, {transitionType="crossFade", transitionTime=0.5})
    end
end
local touch10 = function(event)
    players = 10
    play_but = "but/10player.png"
    if event.phase == "began" then
        director:moveToScene(scene2, {transitionType="crossFade", transitionTime=0.5})
    end
end

local menu_back = function(event) -- при нажатии на кнопку меню
    if event.phase == "began" then
        audio:stopStream()
        director:moveToScene(scene1, {transitionType="crossFade", transitionTime=0.5}) -- переходим на первую сцену
    end
end

player4:addEventListener("touch", touch4) -- отслеживаем нажатие на бутылку 4
player5:addEventListener("touch", touch5) -- отслеживаем нажатие на бутылку 5
player6:addEventListener("touch", touch6) -- отслеживаем нажатие на бутылку 6
player7:addEventListener("touch", touch7) -- отслеживаем нажатие на бутылку 7
player8:addEventListener("touch", touch8) -- отслеживаем нажатие на бутылку 8
player9:addEventListener("touch", touch9) -- отслеживаем нажатие на бутылку 9
player10:addEventListener("touch", touch10) -- отслеживаем нажатие на бутылку 10

buty:addEventListener("touch", kruti) -- отслеживаем нажатие на кнопку "Ракрутить"
menu:addEventListener("touch", menu_back) -- отслеживаем нажатие на кнопку "Меню"

director:moveToScene(scene1) -- говорим, чтобы при запуске приложения главная была первая сцена, так как вначале сверху вниз делается обход всего кода и если не назначить сцену, то отобразится вторая, так как она находится ниже.


Вот собственно и все. Бутылка крутится, музыка играет… люди выполняют…

Ещё что хотелось добавить, так это то, что при запуске приложения на устройстве, вначале некоторое время черный экран, чтобы ускорить его исчезание сделайте следующее:
  • Создайте файл config.lua
  • Добавьте в него строку — config={debug={general = false}}
  • В файле app.icf добавьте в конец строку — configFileName=«config.lua»

Вот, теперь точно всё. Держите, если вдруг кому понадобится, архив с файлами и успехов в разработке!
Поделиться публикацией

Комментарии 26

    +1
    Кстати api очень похоже на Corona SDK.
    Вот ещё видео с последнего casual connect про Marmalade Quick


    Платформа очень интересна для разработки, но блин цена очень высокая. Если, конечно, уже есть успешная игра, приносящая доход, то это не проблема, но в противном случае весьма накладно. Остаётся только использовать trial версию до тех пор, пока игра не будет готова.
      +2
      Цена высока, что аж неадекватна. А уж тот факт, что они даже за 500 уе не предоставляют вменяемого суппорта… Это просто очень странное решение. Не знаю, кто его вообще использовать будет.

      Тем более API судя по топику не блещет ничем невероятным.
        0
        По сравнению с Corona SDK меня в Marmalade Quick больше всего привлекает открытость кода и поддержка гораздо большего числа платформ. Это именно то, чего не хватает мне в Corona.

        Всё смотрю на Moai и пока не решился пробовать перейти. И тут возникает делема Moai vs Marmalade Quick. Может ко времени следующего игрового проекта определюсь.
        –5
        150$ в год очень высокая цена?
          +13
          150 уе в год за:
          1. Отсутствие вменяемого суппорта
          2. Ограниченный доход
          3. Обязанность показать длительную рекламу при старте приложения:
          A Made with Marmalade splash screen will be displayed when your app loads. The splash screen will display automatically, and will remain on screen for a short time.

          4. Ограничение штата разработчиков.

          Это не «высокая цена». Это «невероятные грабёж и наглость».
            0
            «невероятные грабёж и наглость» :). Юнити насколько я знаю $400 за платформу. то-есть 800 за андроид и айос. Я понимаю, там наверное крутая поддержка и все такое, но 150 против 800 ощутимая разница.

            Я считаю за 150$, мармелад можно купить просто поиграться, как игрушку :)… (причем перед этим попробовав тирал), а если понравится и пойдет дело покупать более дорогие версии.

            Скажите, какие есть альтернативные нормальные кроссплатформенные СДК дешевле $150, нормальные — потому что мармелад не так плох.
              +1
              Gideros — free with splash screen, Moai SDK — free opensource, Cocos2d-x with OpenQuick — free opensource, Monkey — $100.
        +4
        Улыбнули названия переменных kruti, neLez, sdelano и т.п.
          +2
          Я пробовал Quick. Он местами сносен, иногда даже удобен. Но некоторые вещи были очень странными. Так по памяти не скажу, но эмоций в своё время было много.
          Ну и, саппорт, конечно, не очень серьёзный. Splash-screen — просто фейл.
          Для меня главный плюс — возможность делать билды для iOS без самой MacOS, чего я не могу сделать, используя Gideros Mobile(другой lua-framework). При этом не могу сказать, как это работает, т.к. лицензия была только для Blackberry.
            0
            И как вам Gideros?
              0
              По началу он кажется очень user-friendly. Всё просто, примеры есть, документация относительно того же quick, например, достаточная. Но чем глубже копаешь, тем становится хуже. Доступ к практически любой системной функции надо реализовывать через плагин. Для этого надо сначала реализовать сам плагин. А в моём понимании, я плачу за лицензию framework'а, чтобы спокойно писать на нём, а не писать для него.
              Если пишете простые 2d игры, то gideros вполне подходит. Если появляются специфические требования, то лучше искать альтернативу.
                0
                Какие-нибудь ещё фреймворки использовали?
                  0
                  Corona. Но там процесс получения apk сделан не для людей. Плюс связанные с этим проблемы(зачастую это невозможность) с внедрением рекламы. Хотя сообщество там самое активное.
                    0
                    Подтверждаю, без Enterprise версии Coron'ы, что-то вменяемое сделать очень тяжело. Особенно если публиковаться через издателей, где у каждого свой SDK.
                      0
                      Они работают над системой плагинов — project gluon — может их будет достаточно.
            0
            Пробовал сделать простой Match3 на Quick. Память у него течёт просто водопадом.
              0
              А какую версию Вы пробовали?
              Альфа и бета версии не были оптимизированными.
                0
                Версия та, которая доступна на момент написания предыдущего комментария. 1.0
                  0
                  В итоге я просто взял lua binding к cocos2d-x напрямую.
              +2
              А я просто пробовал Мармелад (по акции с блекберри они лицензии давали), больше с Мармеладом сталкиваться не хочу… тем более что Мармелад позволяет делать кросплатформенные программы, но сам работает исключительно под виндой, а мне как линуксоиду это вообще не вариант =(((
              А, кстати, помимо цены на сам мармелад, прибавьте цену ещё за Visual Studio (вроде с этой IDE он работает)

              Дорого, глючно, виндово… ЗАЧЕМ?!!! Ведь столько всего другого есть, при этом даже бесплатного.
                +1
                visual studio можно и бесплатную использовать, все работает и в ней
                  –1
                  у вас линукс головного мозга :)
                  «глючно, виндово»
                    0
                    Глючно, это вы уже внесли ложку субъективизма, нормальный фреймворк, хотя и не отменяет того факта, что на линуксе не работает — принципиальных причин, почему он не может на нем работать нету.
                    0
                    Как я понял из информации на оф. сайте у Quick нету библиотеки виджетов ни своих ни нативных. Для игр может еще как покатит.
                      0
                      Попробуйте oxygine, фреймворк, написанный моим другом. Работает поверх мармелада, но может и поверх SDL. Много фич, продуманная архитектура и ничего не течет :)
                        0
                        А кто-нибудь использовал marmalade quick для windows phone? Не могу найти ничего про биллинг на windows phone, используя marmalade quick.

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое