Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon
21. September 2012

 

Alright, the title might be a bit over the top… what we are about to do is look at some of the most popular 2D game engines powered by Lua.  First there will be a matrix of features, to give you an “at a glance” view of what each engine offers.  Then we will follow up with a simple Hello World example for each, so you can see what the code would look like.  Hopefully this will help you decide which engine is right for you.

 

Engine Features Matrix

 

 

 

Corona

Gideros

LÖVE

Moai

Site Link

Link Link Link Link

Price

199$ /year iOS
199$ /year Android
349$ /year Both
Free trial available
149$ /year Indie
449$ /year Pro
0$ /year Community
Free Free

Free Limitations

Cannot publish to app store with free version Mandatory splash screen
Pro required if income greater than 100K$
N/A N/A

Target Platforms

iOS
Android
iOS
Android
(Mac and Windows under development)
Windows
Mac
Linux
iOS
Android
Windows
Mac
Linux (in late stage development)
Chrome NacL

Dev Platforms

Windows
Mac
Windows
Mac
Windows
Mac
Linux
Windows
Mac
Linux

Support Available

Forum
Paid support
Forum Forum Forum
Paid Support

Open Source

No No Yes Yes

Books

Corona SDK Mobile Game Development

Learning Corona SDK (DVD)
N/A N/A N/A

Other Details

Builds occur on Corona Labs servers, internet connection required
3rd party tools available
Enterprise version available
Includes it’s own IDE Gideros Studio   Paid cloud computing offering for back-end services

Example Published Games

Go Ninja
The Lorax (Movie Game)
Joustin Beaver
Cerberus: The Puppy
N/A?
Unpublished list
Crimson Steam Pirates
Strikefleet Omega

 

* Note, I gave iTunes link only, although many of those games are also available on Google Play.

 

 

Now we are going to look at a simple Hello World app written with each suite.  I do not pretend mastery of any of these suites, or Lua in general, so take the code for what it’s worth.  If you wish to submit a better rendition, please do so!

 

In this sample we are going to create a window at a resolution of 1280x800, then we are going to start a background song looping ( Richard Wagners – Ride of the Valkyrie taken from here ).  Then we are going to create a Hello World text/graphic centered to the screen, and position it where ever the user clicks/touches.  Some files handle window creation in a different file, some handle it in a single file.  That is why some versions have two lua files, while others have only one.

 

Corona SDK Hello World

 

config.lua

-- config.lua

application =
{
    content =
    {
        width = 1280,
        height = 800,
        scale = "letterbox"
    },
}

main.lua

-- HelloWorld sample

-- Load audio file
local song = audio.loadSound("../../Media/Ride_of_the_Valkyries.mp3")

-- set volume to 50%
audio.setVolume(0.5)

-- play audio file, looping forever
audio.play(song,{ channel=1,loops=-1})


-- create text to display on screen in 72point font
local helloText = display.newText("Hello World!",0,0,native.systemFont,72)

-- center to screen
helloText.x = display.contentWidth/2
helloText.y = display.contentHeight/2

-- red
helloText:setTextColor(255,0,0)

-- function to handle touch event, move helloText to the touch location
function onTouchHandler(event)
    helloText.x = event.x
    helloText.y = event.y
end

-- register touch function to respond to global touch events
Runtime:addEventListener("touch",onTouchHandler)

 

 

Gideros

 

main.lua

-- Helloworld sample

-- setup our window to our 1280x800 resolution
application:setLogicalDimensions(1280,800)
application:setOrientation(Application.LANDSCAPE_LEFT)

-- Load song, cannot use relative path to parent directory since file needs to be added to project
local song = Sound.new("Ride_of_the_Valkyries.mp3")

-- play audio file, looping forever
local soundChannel = song:play(0,math.huge)

-- Set song volume to 50%, not set globally
soundChannel:setVolume(0.5)


-- need to load a ttf font, size cannot specify character size in TextField
local font = TTFont.new("arial.ttf",72,false)

-- create text to display on screen
local helloText = TextField.new(font,"Hello World!")

-- center to screen
helloText:setPosition(
        application:getLogicalWidth()/2 - helloText:getWidth()/2,
        application:getLogicalHeight()/2 + helloText:getHeight()/2)

-- set text to red, color is hex encoding
helloText:setTextColor(0xff0000)

-- display text
stage:addChild(helloText)

-- function to handle touch event, move helloText to the touch location
function onTouchHandler(event)
    helloText:setPosition(event.x - helloText:getWidth()/2,event.y + helloText:getHeight()/2)
end

-- register touch function to respond to global touch events
stage:addEventListener(Event.TOUCHES_BEGIN,onTouchHandler)
-- The above doesn't work in the simulator, so handle mouse too
stage:addEventListener(Event.MOUSE_DOWN,onTouchHandler)

LÖVE

 

love.conf

function love.conf(t)
    t.screen.width = 1280
    t.screen.height = 800
end

main.lua

-- love2d works slightly different, expecting users to implement methods that will be called within the game loop
-- such as love.draw() and love.update()

-- create a 72 point font using the system default
font = love.graphics.newFont(72)
-- set the font active
love.graphics.setFont(font)
-- set red as the active color
love.graphics.setColor(255,0,0,255)

-- load audio file
local song = love.audio.newSource("Ride_of_the_Valkyries.ogg")

-- we want to loop, we want to loop, we want to loop, we want t^Z
song:setLooping(true)

-- set volume to 50%
love.audio.setVolume(0.5)
-- play song
love.audio.play(song)

-- create a variable for print coordinates to update on touch, default to screen center
-- LOVE does not have a positionable text object, so we call print each frame
local x = love.graphics.getWidth()/2
local y = love.graphics.getHeight()/2
local stringWidth = font:getWidth("Hello World!")
local stringHeight =  font:getHeight("Hello World!")


-- This function is called once per frame to draw the screen
function love.draw()
    love.graphics.print("Hello World!",x - stringWidth/2,y-stringHeight/2)
end

-- called on click, move our print x,y to the click location
-- no touch handler because LOVE is desktop only
function love.mousepressed(mouse_x,mouse_y,button)
        x = mouse_x
        y = mouse_y
end

 

Moai

 

main.lua

-- create the window, viewport and layer
MOAISim.openWindow("Window", 1280, 800)
local viewport = MOAIViewport.new()
viewport:setSize(1280,800)
viewport:setScale(1280,800)

local layer =  MOAILayer2D.new()
layer:setViewport(viewport)

-- Let Moai know we want this layer rendered
MOAIRenderMgr.pushRenderPass(layer)

-- Initialize the audio system
MOAIUntzSystem.initialize()

-- set volume to 50%
MOAIUntzSystem.setVolume(0.5)

-- load the song
song1 = MOAIUntzSound.new()
song1:load("../Media/Ride_of_the_Valkyries.ogg")

-- play audio file, looping forever
song1:setLooping(true)
song1:play()

-- save memory by only rendering the chars we need
chars = 'Helo Wrd!'

-- create a font
local font = MOAIFont.new()
font:loadFromTTF('../Media/arial.ttf',chars,72)

-- create and position text centered
local helloText = MOAITextBox.new()
helloText:setString('Hello World!')
helloText:setFont(font)
helloText:setYFlip(true)
helloText:setRect(-640,-400,640,400)
helloText:setAlignment(MOAITextBox.CENTER_JUSTIFY,MOAITextBox.CENTER_JUSTIFY)

layer:insertProp(helloText)

-- handle mouse/touch click events
function handleClickOrTouch(x,y)
    helloText:setLoc(layer:wndToWorld(x,y))
end

if MOAIInputMgr.device.pointer then
    -- Mouse based device
    MOAIInputMgr.device.mouseLeft:setCallback(
        function(isButtonDown)
            if(isButtonDown) then
                handleClickOrTouch(MOAIInputMgr.device.pointer:getLoc())
            end
        end
    )
else
    -- Touch based device
    MOAIInputMgr.device.touch:setCallback(
        function(eventType,idx, x, y, tapCount)
            if eventType == MOAITouchSenser.TOUCH_DOWN then
                handleClickOrTouch(x,y)
            end
        end
    )
end

 

 

 

My Opinions

 

 

First off, take these with a grain of salt, these are just my initial impressions and nothing more.  Obviously it is all very subjective.  It is also stupid to say X is best, they are all good libraries, each with their own strengths and weaknesses.  I think that is perhaps the greatest surprise, not one of these four options is bad.

 

 

Love: Not a big fan of the abstraction and it forces a design on you, but this isn’t necessarily a bad thing, especially for a beginner.  Good for beginners, so-so to slight levels of documentation but absolutely wonderful reference materials.  Only library in this group with no mobile support, which is a big deal.  Open source and free, targeted to hobbyist.  Few ( none? ) commercial games.  All told, it reminded me a lot of the Python based PyGame, which is frankly a great beginners library.  Also the name “Love” proved a gigantic handicap, as it made Googling for anything beyond the Love2D website very difficult.  This is the downside to using a very generic name for your library ( cough… GamePlay, I’m looking at you! ).  The generic name really did prove to be a pain in the butt at times.  Love is certainly a good library, but probably not for professional use, at least, as is. 

 

 

Corona: Most polished of the four.  Best documentation, good API.  Only library with published books available and good tooling support.  Also most expensive and closed.  If it isn’t part of Corona, you are hosed.  Have to pay more for native access.  Great developer backing, lots of successful companies using Corona.  Corona is certainly a great library, although thanks to the price tag, it wont appropriate for all developers.  The lack of freedom ( no source, paying for native access ) are definitely the biggest drawbacks.

 

 

Gideros: Ok-good documentation, good reference but other material is a bit too scattered.  IDE is a HUGE boon for newer developers, especially with auto-completion.  That said, the IDE got a bit flaky at times too.  API itself a bit less intuitive ( to me ).  Licensing terms reasonable ( better than Corona, worse than Love and Moai ), same for price.  Good choice for beginner who wants to support mobile, lack of major published games a bit of a deterrent for professional developers, as is the lack of source code.

 

 

Moai: Moai is certainly the most difficult of the four, and the documentation is in heavy need of updating.  The reference itself is actually very good, where it exists.  In some cases there is none and in others, it is lacking or out-dated.  The developers are aware and this is a priority for them to fix.  On the other hand, Moai is also the most flexible by a mile.  The code ( as you can see from the example above ), is a bit more verbose, but that is because the library makes less decisions for you.  This is a double edged sword of flexibility vs ease, and Moai slants heavily towards flexibility.  Supports the most targets of all the libraries, has complete source code, and more importantly, the source code is very well written and very easy to read.  Like Corona, there are some very good shipped games.

 

 

Final verdict:

For a commercial product for iOS/Android, I would select Moai.  The API is a natural fit to my coding style ( I prefer flexibility over accessibility for time critical code ) and the C++ source code is a great boon to me, but to a non-C++ programmer, this would obviously be less important.  Also of course, the price is nice.  Most importantly, the open nature means I know I will never encounter a problem that I can’t code my way out of, the biggest downside to Corona.  If it wasn’t for the open source nature of Moai, I would probably go with Corona for the sake of it’s excellent documentation and clean API.

 

If I was just starting out, I would be torn between Gideros and LOVE.  LOVE is certainly the most beginner friendly, but the turn-key all in one nature of Gideros… you literally install, load the studio, write some code and hit play… with full autocomplete code editing.  This really is a huge deal!  In it’s favour over LOVE is also the support for mobile platforms.  That said, if the API isn’t to your liking, or you struggle with it, Love is easily the most accessible code wise.  I will be looking a bit closer at Gideros in the future.  Ran into a few annoyances during my brief exposure, like the inability to set anchor points for TextField values ( http://bugs.giderosmobile.com/issues/41 ), forcing me to wait for the feature to be added by someone else.

 

This isn’t to say Corona is bad, it obviously isn’t.  It is polished, has the best documentation and a very solid/natural API.  For me though, the lack of flexibility and access to source code provides outweigh it’s advantages.  If the source isn’t a big deal to you, or you do not have access to C++ resources and are willing to pay 200$ a year or more, Corona is a very good option with a ton of developers using it.  Also, Corona is the only option with a paid support option, which can be a huge advantage.

 

 

 

TL;DR verdict:

 

For a Pro developer:  Go Moai, unless you have no in-house C++ talent, in which case, go Corona.

For a new developer: Go Gideros, especially if you want to do mobile development. If you don’t like it, Love is always a great option.

Programming Design General


20. September 2012

I ran into a small problem today, that took more then a few cycles to puzzle out.

 

Basically I was installing and configuring Moai to work on Mac, and this process had a few steps.

First I had to install the FMOD libraries and configure them in Xcode. 

Then I needed to build each host ( I am working from Git instead of the compiled binaries )

I then configured my preferred Lua/Moai IDE IntelliJ according to my own guide, which by the way, worked exactly the same.

 

But then, when it came time to run my code via moai-fmod-ex I got an error along the lines of error ./libfmodex.dylib does not exist which makes sense in the end.  The Moai Mac host is built to expect the FMOD dylib to be in the same directory as executable.  Problem is, when you run it as a tool within IntelliJ and give it a different working directory, it will not find the DLL.  I tried setting the path using DYLD_LIBRARY_PATH, but oddly this didn't work.  I did manage to get the Moai host running by using the bizarrely named install_name_tool, which also presented a new challenge.

 

Apparently… Xcode used to install this tool in the /usr/bin directory, but then they changed it to the /Developer/usr/bin directory… then apparently they changed it again to the /Applications/Xcode.app/Contents/Developer/usr/bin/ folder, which is not in the PATH and is a pain in the ass to type, so I copied it to /usr/bin ( sudo required).

 

I then relocated the path to the dylib by:

change to moai fmod host directory

install_name_tool -change './libfmodex.dylb' '/path/to/dylib/file/libfmodex.dylb' moai-fmod-ex

 

 

Now it runs properly from within IntelliJ. 

General


17. September 2012

 

In this tutorial, we are going to look at how to play audio using Moai.  This is going to make heavy use of moaigui, so if you haven’t already, you may want to check the prior tutorial in this series.

 

As always, let’s jump right in to the code.  You may notice that the code order is starting to seem a little… odd. This is because I am grouping code mostly in order of newness, as opposed to how it would logically be laid out.  If you are thinking “I wouldn’t order things this way!”, well.. neither would I, normally.

 

screenWidth = MOAIEnvironment.screenWidth
screenHeight = MOAIEnvironment.screenHeight
if screenWidth == nil then screenWidth =1280 end
if screenHeight == nil then screenHeight = 800 end


MOAISim.openWindow("Window",screenWidth,screenHeight)

viewport = MOAIViewport.new()
viewport:setSize(screenWidth,screenHeight)
viewport:setScale(screenWidth,screenHeight)

package.path = './moaigui/?.lua;' .. package.path
require "gui/support/class"
local moaigui = require "gui/gui"
local resources = require "gui/support/resources"
local filesystem = require "gui/support/filesystem"
local inputconstants = require "gui/support/inputconstants"
local layermgr = require "layermgr"


local gui = moaigui.GUI(screenWidth,screenHeight)


gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "fonts"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "gui"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "media"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "themes"))

layermgr.addLayer("gui",99999, gui:layer())
gui:setTheme("basetheme.lua")
gui:setCurrTextStyle("default")

function onPointerEvent(x, y)
    gui:injectMouseMove(x, y)
end

function onMouseLeftEvent(down)
    if(down) then
        gui:injectMouseButtonDown(inputconstants.LEFT_MOUSE_BUTTON)
    else
        gui:injectMouseButtonUp(inputconstants.LEFT_MOUSE_BUTTON)
    end
end

function onTouchEvent(eventType,idx,x,y,tapCount)
    --gui:injectTouch(eventType,idx,x,y,tapCount)
    onPointerEvent(x, y)
    if (MOAITouchSensor.TOUCH_DOWN == eventType) then
        onMouseLeftEvent(true)
    elseif (MOAITouchSensor.TOUCH_UP == eventType) then
        onMouseLeftEvent(false)
    end
end


if MOAIInputMgr.device.pointer then
    MOAIInputMgr.device.pointer:setCallback(onPointerEvent)
    MOAIInputMgr.device.mouseLeft:setCallback(onMouseLeftEvent)
else
    MOAIInputMgr.device.touch:setCallback(onTouchEvent)
end


function onButtonClick(event,data)
    if(string.find(buttonPlay:getText(),"Play") ==1) then
        song1:play()
        buttonPlay:setText("Stop, that's creeping me out!")

        function checkSongDone()
            local song = song1
            while song1:isPlaying() do coroutine:yield() end

            song1:stop()
            buttonPlay:setText("Play Audio File using Untz")
        end

        if not checkLoop:getChecked() then
            songPlayingThread = MOAICoroutine.new()
            songPlayingThread:run(checkSongDone)
        end
    else
        song1:stop()
        buttonPlay:setText("Play Audio File using Untz")
    end
end

function onButtonRewind(event,data)
    if song1:isPlaying() then
        local pos = song1:getPosition()
        local length = song1:getLength()
        local tenPercent = (length * 1.10) - length
        pos = pos - tenPercent
        if(pos > 0) then
            song1:setPosition(pos)
        end
    end
end

function onButtonFastForward(event,data)
    if song1:isPlaying() then
        local pos = song1:getPosition()
        local length = song1:getLength()
        local tenPercent = (length * 1.10) - length
        pos = pos + tenPercent
        if(pos < length) then
            song1:setPosition(pos)
        end
    end
end

function onCheckboxChanged()
    song1:setLooping(checkLoop:getChecked())
end

function onVolumeChanged()
    MOAIUntzSystem.setVolume(sliderVolume:getCurrValue() / 100)
end



buttonPlay = gui:createButton()
buttonPlay:setPos(0,0)
buttonPlay:setDim(100,25)
buttonPlay:setText("Play Audio File using Untz")
buttonPlay:registerEventHandler(buttonPlay.EVENT_BUTTON_CLICK,nil,onButtonClick)

checkLoop = gui:createCheckBox()
checkLoop:setPos(0,26)
checkLoop:setDim(100,10)
checkLoop:setText("Loop?")
checkLoop:registerEventHandler(checkLoop.EVENT_CHECK_BOX_STATE_CHANGE,nil,onCheckboxChanged)

labelVolume = gui:createLabel()
labelVolume:setText("Volume")
labelVolume:setPos(0,36)
labelVolume:setDim(30,10)

sliderVolume = gui:createHorzSlider()
sliderVolume:setPos(30,36)
sliderVolume:setDim(70,10)
sliderVolume:setRange(0,100)
sliderVolume:setCurrValue(50)
sliderVolume:setValueDisplayLoc(sliderVolume.VALUE_DISPLAY_RIGHT)
sliderVolume:registerEventHandler(sliderVolume.EVENT_SLIDER_VALUE_CHANGED,nil,onVolumeChanged)

buttonRewind = gui:createButton()
buttonRewind:setPos(0,50)
buttonRewind:setDim(50,10)
buttonRewind:setText("Rewind")
buttonRewind:registerEventHandler(buttonRewind.EVENT_BUTTON_CLICK,nil,onButtonRewind)

buttonFastForward = gui:createButton()
buttonFastForward:setPos(51,50)
buttonFastForward:setDim(50,10)
buttonFastForward:setText("Fast Forward")
buttonFastForward:registerEventHandler(buttonFastForward.EVENT_BUTTON_CLICK,nil,onButtonFastForward)

MOAIUntzSystem.initialize ()
MOAIUntzSystem.setVolume(0.5)
song1 = MOAIUntzSound.new()
song1:load("demongirls.ogg")

 

If we run the application( in the Moai-untz host ), we see:

 

image

 

Click the “Play Audio File using Untz” button and the song will play, the slider increases and decreases the volume, the loop check box I bet you can guess what it does.  Rewind and Fast Forward change the playback position by 10% at a time.

 

This looks like a wall of code, but don’t worry, it’s not all that bad.  In reality, most of it is maoigui code that we covered earlier.  We use a couple of new controls ( the check box and slider ), but nothing in the UI code should be all together new.  Most of our new logic is in the click handler methods, so let’s start there.

 

function onButtonClick(event,data)
    if(string.find(buttonPlay:getText(),"Play") ==1) then
        song1:play()
        buttonPlay:setText("Stop, that's creeping me out!")

        function checkSongDone()
            local song = song1
            while song1:isPlaying() do coroutine:yield() end

            song1:stop()
            buttonPlay:setText("Play Audio File using Untz")
        end

        if not checkLoop:getChecked() then
            songPlayingThread = MOAICoroutine.new()
            songPlayingThread:run(checkSongDone)
        end
    else
        song1:stop()
        buttonPlay:setText("Play Audio File using Untz")
    end
end

This is the code that is executed if the brilliantly named buttonPlay button is clicked.  The first thing we do is check to see if the button’s text starts with “Play”, which if it does, that means we are currently not playing a song.  Therefore we start playing our song ( aptly named song1 ) with a call to the play() method.  We then update our button’s text to indicate it’s new status.  The next little bit of code might seem a bit confusing at first, but it is pretty powerful.

 

First we are declaring a nested function checkSongDone().  The next line is where the magic happens.  It’s a while loop that loops until the condition isPlaying() becomes false.  Each pass through the loop we call coroutine:yield().  So, what the heck was that all about? Well… this is going to be a bit tricky to explain, but I will do my best.

 

If you are an old fart like me, you may remember multitasking in the pre-Win95 pre-emptive multitasking days.  Essentially you ran multiple programs at the same time by giving each one a chunk of processing time and a well behaved program was supposed to yield control when it wasn’t busy.  Essentially the operating system switched between running programs and said “ok, your turn” and a well behaved program would quickly exit if it was done doing whatever it needed to do.  Well, in a nutshell that is what is happening here.

 

A coroutine is sort of the Moai version of a thread.  Using coroutines, you are able to run code in parallel, so while our game loop is still running, the function checkSongDone is blissfully churning away in the background checking to see if the song is done.  Thing is, until the song is actually done, the coroutine doesn’t really have anything else to do, so it calls yield().  It is not until that condition is true ( isSongPlaying returns false ) will the function continue execution.  Once this happens we (somewhat redundantly) call song1:stop() then update the button’s text to “Play Audio…”.  The end result of this whole chunk of code is, we essentially created an on song end handler, so when the song is done playing, the text of the button will update.

 

The next chunk of code checks the checkbox checkLoop, which if checked indicates that we want the song to loop continuously.  If it is not checked, that means we want to run our coroutine, which we create with a call to MOAICoroutine.new(), then run with the run() method, passing in the function to run.

 

If the button’s text however indicates that the song is playing, in which case the button acts like a STOP button, simply stopping playback ( even if loop is checked ) and resetting back to the “Play…” text.  I encourage you to look deeper in to Coroutines, they are a very powerful feature of Lua. MOAICoroutine itself is only a thin wrapper over Lua’s coroutine abilities.

 

Also, you may have noticed MOAIThread in the examples and wondered when to using MOAIThread instead of MOAICoroutine.  The short answer is, you dont!  The more accurate and useful short answer is, MOAIThread *IS* MOAICoroutine.  It was renamed and the documentation just hasn’t caught up yet.  Well at least, it hadn’t at the point I wrote this.

 

TL;DR, coroutines are cool, learn about them, use them, love them.

 

function onButtonRewind(event,data)
    if song1:isPlaying() then
        local pos = song1:getPosition()
        local length = song1:getLength()
        local tenPercent = (length * 1.10) - length
        pos = pos - tenPercent
        if(pos > 0) then
            song1:setPosition(pos)
        end
    end
end

function onButtonFastForward(event,data)
    if song1:isPlaying() then
        local pos = song1:getPosition()
        local length = song1:getLength()
        local tenPercent = (length * 1.10) - length
        pos = pos + tenPercent
        if(pos < length) then
            song1:setPosition(pos)
        end
    end
end

This is the code that executes if you hit the Fast Forward or Rewind buttons.  Basically it gets the current position of the song ( playback-wise ) using getPosition() and the length of the song using getLength().  Next we calculate what 10% of the length is, and either add or subtract that amount from the current position depending if we are fast forwarding or rewinding.  Next we check sure to make sure the position is valid after being modified and if it is, we change the position of the song using setPosition().  End result, pressing Fast forward advances the song by 10% of it’s length, while hitting rewind moves it back 10%.

 

function onCheckboxChanged()
    song1:setLooping(checkLoop:getChecked())
end

This is the code that executes if the checkbox checkLoop is checked or unchecked.  It simply toggles the value of setLooping based off the if the checkbox is checked, using getChecked().

 

function onVolumeChanged()
    MOAIUntzSystem.setVolume(sliderVolume:getCurrValue() / 100)
end

This is the code that is executed if the slider sliderVolume is changed.  This uses the MOAIUntzSystem singleton to change the global volume level using setVolume().  setVolume takes a value from 0.0 to 1.0, while the sliders value returned by getCurrValue() is from 0 to 100, so we divide it by 100.

 

So then, what the heck is an Untz?  It’s the included cross platform audio system.  You also have the option to use FMOD, a commercial audio solution.  Untz does the trick though, works on Android and iOS.  There is a very important caveat… if you are going to be running using the UNTZ audio library you need to use the Moai-untz host!!! when running on the PC.  You will get exceptions if you try to call MOAIUntzSystem from the normal Moai host.  The Moai-untz host is in the same directory as the Moai host.

 

Most of the UI code is pretty much identical to what you’ve seen already, so I wont really go into it, that leaves:

 

MOAIUntzSystem.initialize ()
MOAIUntzSystem.setVolume(0.5)
song1 = MOAIUntzSound.new()
song1:load("demongirls.ogg")

This code first initializes the MOAIUntzSystem singleton.  Then it sets the global volume to 50%.  Next we create our song song1 with a call to MOAIUntzSound.new().  Finally load the song ( in the same directory as main.lua ) with a call to load().

 

You might be asking yourself “what sound formats does Untz support?”.  That’s a good question with a tricky answer.  Basically the safe answer is ogg and wav.  The not so safe answer is, it depends on the platform.  For example, iOS, Mac and Windows will work with mp3, but Android wont.  Beyond WAV and OGG, the file is just passed to the native system for handling, so if the platform supports it, Moai supports it.

 

But if you really want to be cross platform, just encode everything in to ogg.  In this day of increasingly wealthy lawyers, the last thing we want to do is deal with a patent encumbered format like mp3!

 

Oh yeah, and the music… it’s seriously creepy stuff and I downloaded it from here.  Obviously you can substitute any song you want from your Justin Beiber collection, just make sure it’s not an MP3 if you are running on Android.  Of course, you can convert from just about any audio format to just about any other audio format using the free and amazing Audacity.  I do get 10% of all proceeds for saying that.  I may need a new business model.

 

Programming


11. September 2012

 

In this recipe, we are going to show how to create a GUI using Moai.  This functionality isn’t actually built directly into Moai, it is provided by a third party library, so we have to do a bit of setup first.

 

There are a few ways you can get moaigui.  First, it is already included in your moai install in the samples directory.  In the samples folder, look for a contrib folder, in my case it was located at C:\moai-sdk\samples\contrib.  Select the folder moaigui and copy it into your project directory.  Otherwise you can get the newest version using github. To do so ( assuming you have git installed and configured ), open a console window, change directory to your project and type:

git clone https://code.google.com/p/moaigui/

 

This will clone the newest source of the moaigui as a subdirectory in your project.  Alright, that done, on with the code:

 

screenWidth = MOAIEnvironment.screenWidth
screenHeight = MOAIEnvironment.screenHeight
if screenWidth == nil then screenWidth =1280 end
if screenHeight == nil then screenHeight = 800 end

MOAISim.openWindow("Window",screenWidth,screenHeight)

viewport = MOAIViewport.new()
viewport:setSize(screenWidth,screenHeight)
viewport:setScale(screenWidth,screenHeight)

package.path = './moaigui/?.lua;' .. package.path
require "gui/support/class"
local moaigui = require "gui/gui"
local resources = require "gui/support/resources"
local filesystem = require "gui/support/filesystem"
local inputconstants = require "gui/support/inputconstants"
local layermgr = require "layermgr"


local gui = moaigui.GUI(screenWidth,screenHeight)


gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "fonts"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "gui"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "media"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "themes"))

layermgr.addLayer("gui",99999, gui:layer())
gui:setTheme("basetheme.lua")
gui:setCurrTextStyle("default")

function onButtonClick(event,data)
    label1:setText("You clicked the button")


end



function onLessProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress > 0) then
        progress:setProgress(curProgress-10)
    end

end

function onMoreProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress < 100) then
        progress:setProgress(curProgress+10)
    end
end


button = gui:createButton()
button:setPos(0,0)
button:setDim(100,25)
button:setText("This is a button")
button:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onButtonClick)
button:registerEventHandler(button.EVENT_TOUCH_ENTERS,nil,onButtonClick)

progress = gui:createProgressBar()
progress:setPos(0,25)
progress:setDim(100,25)
progress:setText("This is a progress bar")

button2 = gui:createButton()
button2:setPos(0,50)
button2:setDim(49,25)
button2:setText("Less Progress")
button2:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onLessProgressButtonClick)
button2:registerEventHandler(button.EVENT_TOUCH_ENTERS,nil,onLessProgressButtonClick)


button3 = gui:createButton()
button3:setPos(51,50)
button3:setDim(49,25)
button3:setText("More Progress")
button3:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onMoreProgressButtonClick)
button3:registerEventHandler(button.EVENT_TOUCH_ENTERS,nil,onMoreProgressButtonClick)
button3:registerEventHandler(button.EVENT_TOUCH_TAP,nil,onMoreProgressButtonClick)

label1 = gui:createLabel()
label1:setPos(0,75)
label1:setDim(100,25)
label1:setText("Click the top button")
label1:setTextAlignment(label1.TEXT_ALIGN_CENTER)

function onPointerEvent(x, y)
    gui:injectMouseMove(x, y)
end

function onMouseLeftEvent(down)
    if(down) then
        gui:injectMouseButtonDown(inputconstants.LEFT_MOUSE_BUTTON)
    else
        gui:injectMouseButtonUp(inputconstants.LEFT_MOUSE_BUTTON)
    end
end

function onTouchEvent(eventType,idx,x,y,tapCount)
    --gui:injectTouch(eventType,idx,x,y,tapCount)
    onPointerEvent(x, y)
        if (MOAITouchSensor.TOUCH_DOWN == eventType) then
                onMouseLeftEvent(true)
        elseif (MOAITouchSensor.TOUCH_UP == eventType) then
                onMouseLeftEvent(false)
        end
end


if MOAIInputMgr.device.pointer then
    MOAIInputMgr.device.pointer:setCallback(onPointerEvent)
    MOAIInputMgr.device.mouseLeft:setCallback(onMouseLeftEvent)
else
    MOAIInputMgr.device.touch:setCallback(onTouchEvent)
end

Run the code and you will see:

image

 

Buttons and bars and labels, oh my!

 

The top 10 lines are pretty much unchanged from earlier tutorials, except I switched to the native resolution of my device, since I am doing a lot more on device testing lately.  Obviously you can change screenWidth and screenHeight to match your preferred settings.  The new code starts at:

 

package.path = './moaigui/?.lua;' .. package.path
require "gui/support/class"
local moaigui = require "gui/gui"
local resources = require "gui/support/resources"
local filesystem = require "gui/support/filesystem"
local inputconstants = require "gui/support/inputconstants"
local layermgr = require "layermgr"

The first line looks a bit daunting, but it is important to realize that Lua doesn’t really understand file directories the way you or I do.  It uses a variable called package.path to determine where to look for source files.  We want to add the moaigui subdirectory, which is what this line does, we append that folder and tell it to include all .lua files within in the path.  We then have a series of require statements for importing various required parts of the moaigui library.  We don’t have to add paths to these values, as moaigui takes care of that for us.

 

local gui = moaigui.GUI(screenWidth,screenHeight)

Create a moaigui GUI object named gui, by passing in the screens width and height.

 

gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "fonts"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "gui"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "media"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "themes"))

These are a number of essential files for moaigui, these lines make them all available in the resource path. 

 

layermgr.addLayer("gui",99999, gui:layer())
gui:setTheme("basetheme.lua")
gui:setCurrTextStyle("default")

Add a layer to the moaigui layer manager for the gui layer.  It needs to be the topmost layer so it will receive first crack at all the input coming in.  This is why we set it at layer 99999.  Just don’t go creating a layer 100000 now, ok?  We then load a default theme ( we will look at this in a second, but don’t worry, someone else has already done the work for us ) named basetheme.lua, and set the text style to default… if you don’t do this, text wont show up, so do it.

 

function onButtonClick(event,data)
    label1:setText("You clicked the button")
end

function onLessProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress > 0) then
        progress:setProgress(curProgress-10)
    end
end

function onMoreProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress < 100) then
        progress:setProgress(curProgress+10)
    end
end

Here we create 3 button click/touch handlers, one for each button we are going to create.  The first simply updates the text on a label, while the next two advance or decrease the progress status on a progress bar by 10%.

 

button = gui:createButton()
button:setPos(0,0)
button:setDim(100,25)
button:setText("This is a button")
button:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onButtonClick)

progress = gui:createProgressBar()
progress:setPos(0,25)
progress:setDim(100,25)
progress:setText("This is a progress bar")

button2 = gui:createButton()
button2:setPos(0,50)
button2:setDim(49,25)
button2:setText("Less Progress")
button2:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onLessProgressButtonClick)


button3 = gui:createButton()
button3:setPos(51,50)
button3:setDim(49,25)
button3:setText("More Progress")
button3:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onMoreProgressButtonClick)

Speaking of buttons and progress bars, here we create 3 buttons and a progress bar.  There are a few things to note about moaigui.  First off, position is all done by percentages ( which I personally love!  so easy ).  So saying setDim(100,25), sets the dimensions to 100% width and 25% height.  Nice easy way to scale your UI to the device you are running on.  For each of the buttons, we register the click function we just declared in the call registerEventHandler().

 

label1 = gui:createLabel()
label1:setPos(0,75)
label1:setDim(100,25)
label1:setText("Click the top button")
label1:setTextAlignment(label1.TEXT_ALIGN_CENTER)

Oh, and we create a label too, that will update when the user clicks the topmost button.  Using setTextAlignment, we set the text centered.

 

function onPointerEvent(x, y)
    gui:injectMouseMove(x, y)
end

function onMouseLeftEvent(down)
    if(down) then
        gui:injectMouseButtonDown(inputconstants.LEFT_MOUSE_BUTTON)
    else
        gui:injectMouseButtonUp(inputconstants.LEFT_MOUSE_BUTTON)
    end
end

You need to feed UI events in to moaigui, so when the user hits a key, moves the mouse or taps the screen, if you want moaigui to know about it, you need to tell it.  This is done with a series of inject calls, like injectMouseMove() when the user moves the mouse.  If you don’t wire events up as moaigui is concerned, they didn’t happen.  For example, with the current setup, you can type till the cows come home, and your gui will be completely oblivious.

 

function onTouchEvent(eventType,idx,x,y,tapCount)
    --gui:injectTouch(eventType,idx,x,y,tapCount)
    onPointerEvent(x, y)
        if (MOAITouchSensor.TOUCH_DOWN == eventType) then
                onMouseLeftEvent(true)
        elseif (MOAITouchSensor.TOUCH_UP == eventType) then
                onMouseLeftEvent(false)
        end
end

Currently touch support in moaigui is a bit… in development.  So for now we emulate the user using a mouse.  This means when the user touches the screen, we create a mouse pointer event, when the user touches the screen, we create a left click event and when the user releases the screen, be set the left mouse button status to up.  I also got it working by making a few small changes to the moagui code itself, but unless someone specifically requests it, I wont go into them here.

 

if MOAIInputMgr.device.pointer then
    MOAIInputMgr.device.pointer:setCallback(onPointerEvent)
    MOAIInputMgr.device.mouseLeft:setCallback(onMouseLeftEvent)
else
    MOAIInputMgr.device.touch:setCallback(onTouchEvent)
end

Finally, we wire up a callback for each io event we want moai to tell us about.

 

One other thing I mentioned earlier was the theme file basetheme.lua.  This file is located in the /moaigui/resources/themes/ directory, and looks like this:

data = {
    textstyles = {
        default = {
            font = "arial-rounded.TTF",
            size = 44,
        },
        listselected = {
            font = "arial-rounded.TTF",
            size = 14,
            color = {0, 0, 0, 1}
        },
        listunselected = {
            font = "arial-rounded.TTF",
            size = 14,
            color = {1, 1, 1, 1}
        }
    },
    label = {

    },
    button = {
        normal = "button normal center.png",
        pushed = "button pushed center.png",
        hover = "button hover center.png",
    },
    checkbox = {
        normal = "check box.png",
        hover = "check box.png",
        pushed = "check box.png",
        selected = "check box select.png",
    },
    radiobutton = {
        normal = "radio button.png",
        hover = "radio button.png",
        pushed = "radio button.png",
        selected = "radio button select.png",
    },
    editbox = {
        normal = "edit box center.png",
    },
    vertscrollbar = {
        normal = "vert scroll center.png",
        scrollernormal = "vert scroller.png",
        scrollerhover = "vert scroller.png",
    },
    horzscrollbar = {
        normal = "horz scroll center.png",
        scrollernormal = "horz scroller.png",
        scrollerhover = "horz scroller.png",
    },
    vertslider = {
        normal = "vert slider center.png",
        slidernormal = "vert slider.png",
        sliderhover = "vert slider.png",
    },
    horzslider = {
        normal = "horz slider center.png",
        slidernormal = "horz slider.png",
        sliderhover = "horz slider.png",
    },
    progressbar = {
        normal = "progress bar center.png",
        bar = "progress bar.png",
        mask = "progress bar mask.png",
    },
    textbox = {
    
    },
    widgetlist = {
        selected = "listselected",
        unselected = "listunselected",
    },
    cursors = {
        default = {

        },
    },
}

return data

You can easily make small changes to your UI look and feel by changing this file.  As you can see, I vastly increased the default font size to improve visibility on my phone.  You can also change the button images, and other UI elements, by altering their images in the /moaigui/resources/gui directory, while additional or different fonts should go in the moaigui/resources/fonts folder.

 

In the next exciting tutorial we will put our new found GUIness to work.

 

Now you know, and knowing… is half the battle. Or was that a quarter?  No, no, definitely half.

 

Programming


5. September 2012

 

In this section we are going to do something we probably shouldn’t… work with the keyboard.  As you will soon see, when dealing with mobiles, this isn’t a particularly smooth journey! Mobile and keyboards go together like peanut butter and hand grenades.

 

As is always the way, let’s jump right in with code:

 

screenWidth = MOAIEnvironment.screenWidth
screenHeight = MOAIEnvironment.screenHeight
print("Starting up on:" .. MOAIEnvironment.osBrand);

if screenWidth == nil then screenWidth =640 end
if screenHeight == nil then screenHeight = 480 end

MOAISim.openWindow("Window",screenWidth,screenHeight)

viewport = MOAIViewport.new()
viewport:setSize(screenWidth,screenHeight)
viewport:setScale(screenWidth,screenHeight)


layer = MOAILayer2D.new()
layer:setViewport(viewport)

MOAIRenderMgr.pushRenderPass(layer)


chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

font = MOAIFont.new()
font:loadFromTTF('comic.ttf',chars,60,72)


text = MOAITextBox.new()
text:setString('Press a key')
text:setFont(font)
text:setTextSize(60,72)
text:setYFlip(true)
text:setRect(-320,-240,320,240)
text:setAlignment(MOAITextBox.CENTER_JUSTIFY,MOAITextBox.CENTER_JUSTIFY)


if(MOAIInputMgr.device.keyboard) then
    print("Keyboard")
    MOAIInputMgr.device.keyboard:setCallback(
        function(key,down)
            if down==true then
                text:setString(string.char(tostring(key)))

            end
        end
    )
else
    print("No Keyboard")

    if(MOAIEnvironment.osBrand == "iOS")   then
        MOAIKeyboardIOS.showKeyboard()
        MOAIKeyboardIOS.setListener(MOAIKeyboardIOS.EVENT_INPUT,function(start,length,textVal)
            text:setString(textVal);
        end
        )
    else
        print("The keyboard is a lie");
        -- Android, no keyboard support :(
    end
end

layer:insertProp(text)

 

Here is the program in action:

 

image

 

Exciting eh?  Again, we are going to skip over the familiar bits and jump right in to the new stuff.

 

One thing you might notice compared to prior tutorials, the line:

 

print("Starting up on:" .. MOAIEnvironment.osBrand  .. " version:" .. MOAIEnvironment.osVersion)

 

Has changed to

 

print("Starting up on:" .. MOAIEnvironment.osBrand);

 

Why is this?  Well remember when I said it was up to the host to implement the various values in MOAIEnvironment.  Well, I finally got around to testing on iOS and apparently MOAIEnvironment.osVersion isn’t implemented, at least, it isn’t on the Simulator.  As I recommended earlier, never trust these values to exist on all platforms! In production code, be sure to check for Nil.

 

Now the new code, let’s start with:

chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

font = MOAIFont.new()
font:loadFromTTF('comic.ttf',chars,60,72)

 

The first line is a string of characters representing the individual characters we are going to be using.  If you need additional characters ( such as punctuation ) be sure to add them to this string.  We then create a new MOAIFont object, then load the font from a ttf file, comic.ttf ( I copied from the Windows font directory, you cannot legally redistribute fonts you don’t license! ).  Regardless to where you got it from, be sure to place a ttf file in the same directory as main.lua.  I moai to create the font created at a font height of 60 pixels and at a resolution of 72dpi.

 

Next up:

text = MOAITextBox.new()
text:setString('Press a key')
text:setFont(font)
text:setTextSize(60,72)
text:setYFlip(true)
text:setRect(-320,-240,320,240)
text:setAlignment(MOAITextBox.CENTER_JUSTIFY,MOAITextBox.CENTER_JUSTIFY)

 

Now we are creating a MOAITextBox, which is a bit misleading in name, especially if you have done some prior WinForm or ASP.NET programming.  A MOAITextBox is simply a text area on screen, often referred to as a label or text area in other libraries.  We then set text’s string value to “Press a Key” using the setString() method, set the font to the font we created earlier with a call to setFont(), and set the text size and dpi to match the values we used to create the font.  Next we call setYFlip to invert the font.  For reasons I don’t completely understand, MOAI renders text upside down by default… so this inverts it to the proper position.  Next we position and size the MOAITextBox with a call to setRect, telling it to position centered and use our full 640x480 screen ( again, remembering that coordinates are relative to 0,0 being the middle of the screen ).  Finally we center the text horizontally and vertically within the text area with a call to setAlignment().

 

Now we actually deal with handling keyboard entry:

 

if(MOAIInputMgr.device.keyboard) then
    print("Keyboard")
    MOAIInputMgr.device.keyboard:setCallback(
        function(key,down)
            if down==true then
                text:setString(string.char(tostring(key)))

            end
        end
    )
else

 

Here we are testing to see if a MOAIInputMgr.device.keyboard has been defined.  If this value is assigned, it means we have a keyboard ( currently this means we are running on a PC or Mac host, but don’t expect that to stay true forever ).  If a keyboard is in fact available, we set a callback function to handle key input.  This callback takes the key code ( as a number ) and a boolean, indicating if it was pressed or released and will be called every time a key is pressed.  We check to see if the key was pressed ( as opposed to released ), and if so, we simply convert the key code to an actual character and display it in our text box.

 

However, if there isn’t a keyboard…

 

else
    print("No Keyboard")

    if(MOAIEnvironment.osBrand == "iOS")   then
        MOAIKeyboardIOS.showKeyboard()
        MOAIKeyboardIOS.setListener(MOAIKeyboardIOS.EVENT_INPUT,function(start,length,textVal)
            text:setString(textVal);
        end
        )
    else
        print("The keyboard is a lie");
        -- Android, no keyboard support :(
    end
end

 

First we check to see if we are running on an iOS device.  If we are, we display the on screen keyboard, then set an event listener using MOAIKeyboardIOS listening for EVENT_INPUT events.  We then set the typed value textVal to our text box, which will be the currently typed character.  Otherwise we assume we are running on Android in which case we are…

 

 

Screwed.  Basically.  As of exactly this moment, there is no MOAIKeyboardAndroid available, although one has been developed so it should be available soon.  Until then, you can’t really handle keyboard entry on Android, unless you extend the host yourself.  I will update this guide when Android support is officially added.  You may be thinking to yourself “what about my hardware keyboard, it surely works, right???”.  Actually no.  Alternatives do exist ( there is a GUI package with an onscreen keyboard included ) that we will cover later, but for now at least until Android keyboard support is made publically available, you are kinda screwed.

 

 

Finally, we add our MOAITextBox to the layer with a call to

layer:insertProp(text)

 

Now you are happily traveling along with full keyboard support in your application!  Well, unless of course you have an Android device, in which case you are probably sulking in a corner.

 

 

Programming


GFS On YouTube

See More Tutorials on DevGa.me!

Month List