How To Make a Simple Game with Moai

Learn how to make a simple game with Moai!

Learn how to make a simple game with Moai!

In this tutorial you’ll learn how to create a simple 2D game using Moai, a Lua-based game development platform.

With Moai, you don’t need to fear being locked in to one platform — you can let everyone enjoy the fruits of your labors! It supports iOS, Android, Mac, Windows, Linux, and Chrome client applications.

Unlike other Lua-based SDKs such as Corona, Moai is completely free. In addition, it’s open source, and it’s easy to extend by adding other 3rd party Lua libraries. Finally, it’s full of features – offering more than many other SDKs.

On the other hand, it’s slightly more complicated than Corona, and takes a bit more time to learn how to use it effectively. But don’t worry – that’s what this tutorial is for! :]

In this tutorial, you’ll create a simple “Animal Feeding” game. Basically, food items will scroll across the screen, and it’s your job to get the correct food items to the correct animals.

In the process, you’ll learn all about Moai, including how to use sprites, animation, sound, collision detection, and text rendering!

This tutorial assumes you have some basic familiarity with Lua. If you’re completely new to Lua, the Lua Tutorial or official Lua documentation is a great resource to get you up to speed on Lua and what it has to offer.

What is Moai?

Moai SDK is a 2D game framework created by an indie games development studio – Zipline Games. It has four main killer features:

  • It’s cross-platform. Moai makes it easy to deploy your app to multiple platforms,
  • It supports rapid development. You code Moai apps with Lua, a quick and easy scripting language. Moai also comes with native Mac and Windows clients, which allows you to test your code quickly without needing to deploy to a device.
  • It gives full source code access. Moai comes with full source code access, so if you’d like you can dig in to see how things work or extend it with your own code!
  • It’s free! The base SDK is completely free, but requires to place the Moai logo within the app (and inform users it’s built with Moai). However, there is an optional service called Moai Cloud which is paid – more on this later.

It is currently used in a lot of games, such as:

The Moai Cloud

Moai Cloud is where the developers of Moai make their money. It’s a RESTful web service that allows you to store your user’s data, leaderboards, and achievements – in a cross-platform and easy way.

When you’re getting started, using Moai Cloud is also free (a “Sandbox” model, with limited storage and monthly bandwidth, probably sufficient for development and beta testing phase). However, as your game grows in traffic you’ll need to upgrade to a paid option.

Of course, you’re not forced to use Moai SDK with Cloud, or Cloud with the SDK – those are separate components that can be mixed however you want.

About the SDK

The most important objects in Moai are:

  • Layers: Like Layers in Cocos2D, these are “blank canvases” that you add your display objects to.
  • Partition: This is a class that you’ll use in your touch-handling code.
  • Props: Think of these as Sprites in other game frameworks.
  • MOAIRenderMgr: You pass your layers to this class so they can be rendered to the screen.

Here’s an illustration showing this at a glance:

Diagram of Moai classes you’ll be using in this tutorial

But the easiest way to understand this is to try it out. So let’s dig in!

Getting Started

First, download the Moai SDK at http://getmoai.com/sdk/moai-sdk-download.html. This tutorial covers the 1.4p0 release from March 2013.

Download and unpack the distribution file. You should see the following folders inside:

Folders inside the MOAI SDK package

Folders inside the MOAI SDK package

Take a look inside the /bin and /samples folders.

Inside /bin, you’ll see subfolders for OS X and Windows – these are the files needed to run the app in those environments.

The samples folder contains a ton of examples – you can try them out in a bit after I show you how to run Moai.

To run the Moai executable from the terminal, you must specify the full path. This will depend on where you unpacked the Moai archive. I recommend you move the Moai folder to a safe and permanent spot on your hard drive.

Enter the command below in Terminal:

$ /Users/my-username/moai-sdk/bin/osx/moai

Of course, replace the path for where you saved your Moai SDK. Bonus points for those of you who typed it in right the first time! :]

You’ll notice that the screen flickered briefly when you ran the above command, but otherwise it should exit right away without an error message if everything went well.

To simplify things, add the following line to the bottom of .bash_profile in your home directory. This will add the Moai executable’s path to your system path. Make sure to adjust the path if you unpacked the Moai archive in a different directory:

export PATH=$PATH:/Users/my-username/moai-sdk/bin/osx

Quit Terminal and re-start for this to take effect.

Now, try it out! Switch to one of the sample directories (like physics/physics-chipmunk) and run main.lua as follows:

cd /Users/my-username/moai-sdk/samples/physics/physics-chipmunk
moai main.lua

You should then see something like the following:

Chipmunk physics demo with Moai.

Chipmunk physics demo with Moai.

If that shows up, congrats – it’s working!

Note: For the rest of this tutorial, instead of the full path /some/folder/moai-sdk/bin/osx/moai, you can just run moai instead. It will be a great time saver!

Ready Player One — Getting Your Project Ready

For the game you’re making in this tutorial, I’ve created a starter project for you. It doesn’t contain any code yet, but it contains all of the files and sounds you’ll need for the game, as well as a nice directory structure.

So Download the starter project and unpack it. Inside you’ll find a project folder named AnimalFeeding. As well, you’ll find a set of image and audio assets along with the main.lua file, which will contain the main game code. The structure is shown below:

Project files

Fire up your text editor of choice and open up main.lua.

Whoa — it’s empty! That will get you nowhere fast! First, you’ll need to display a window on the screen. Every visual application needs a window (or a view in iOS) to display your work.

Paste the following code into the main.lua:

----------------------------------------------------------------
-- screen (window) initialization
----------------------------------------------------------------
-- 1.
local STAGE_WIDTH = 960
local STAGE_HEIGHT = 640
local SCREEN_WIDTH = 960
local SCREEN_HEIGHT = 640
print ( "System: ", MOAIEnvironment.osBrand )
print ( "Resolution: " .. SCREEN_WIDTH .. "x" .. SCREEN_HEIGHT )
 
-- 2.
MOAISim.openWindow ( "Animal Feeding", SCREEN_WIDTH, SCREEN_HEIGHT ) -- window/device size
 
-- 3.
local viewport = MOAIViewport.new ()
viewport:setSize ( SCREEN_WIDTH, SCREEN_HEIGHT ) -- window/device size
viewport:setScale ( STAGE_WIDTH, STAGE_HEIGHT ) -- size of the "app"
 
-- 4. 
local layer = MOAILayer2D.new ()
layer:setViewport ( viewport )
layer:setClearColor (0.53, 0.53, 0.53, 1)
 
-- 5.
local partition = MOAIPartition.new ()
layer:setPartition ( partition )
 
-- 6.
MOAIRenderMgr.setRenderTable ( { layer } )

Here’s a quick rundown of the code above, comment by comment:

  1. Set some local constants (stage and screen width and height) and print them out to the console. If you’re new to Lua, keep in mind that the local keyword gives the variable limited rather than global scope. In this app the scope doesn’t really matter, but it’s a good practice to limit the number of global variables floating around.
  2. Open the window, name it “Animal Feeding” and set width and height to the previously defined 960 and 640.
  3. Create the viewport, which is what allows you to “look” at the scene. The viewport size refers to the actual display size, while the scale defines the coordinates. In this case, the size and scale are the same. If you have a Retina display, you might set the size to 960×640 but the scale to 480×320.
  4. Create a layer, which is sort of like the “root” view or window. A layer needs to be viewed through a viewport, so the viewport from step #3 is linked to the layer. The “clear color” is the background color of the layer, which is set to a nice grey (RGB 0.53, 0.53, 0.53)
  5. A partition is used to catch user interactions, such as clicks and touch events. This will come in use later when you add the interactive bits.
  6. Finally, the Moai Render Manager is set up to handle rendering on the newly created layer.

To run the code, open up a Terminal window and navigate to the folder containing main.lua.

Build and run your code with this command:

$ moai main.lua

You’ll see the following stunning window:

Empty window

Success! A nice blank grey window. You should also notice the output of the two print statements in the terminal:

System: 	OSX
Resolution: 960x640

Now that the window is set up, it’s time to add something more interesting to it.

What’s Invisible and Smells Like Carrots? — Adding Sprites to the Game

You’ll need to add a few things to liven up this game. Start by adding some sprites.

Add this code to the bottom of main.lua:

local quad = MOAIGfxQuad2D.new ()
quad:setTexture ( "gfx/carrot.png" )
quad:setRect ( -84/2, -98/2, 84/2, 98/2 )
 
local prop = MOAIProp2D.new()
prop:setDeck ( quad )
 
layer:insertProp ( prop )

A “quad” is a rectangle — hence the name “quad” — that can hold a texture image. Think of it as an abstract object, much like a class definition.

A “prop” is what might be called a “sprite” in another framework. It is something that is displayed on-screen. If a quad is comparable to a class, then a prop is like a practical, usable instance of that class.

With this setup, you could load a texture into a quad, and then use it multiple times in many props. You can read more about the distinction between the two on the Moai wiki.

Run your project, and now you are rewarded with a mighty-looking carrot in the center of the screen!



Note that the default position of your graphics is smack dab in the center of the screen.

Coordinates in a typical Moai application are a bit different than what you’re used to in Cocos2D or Corona SDK applications. The (0, 0) point is at the center of the window! Positive X values are to the right side and negative to the left; positive Y values are up and negative on the bottom, as shown in the image below:

Default coordinates system has the (0, 0) point in the center of the window. Upper part of the window has positive Y values, lower has negative Y values.

You’re likely thinking “That’s a heck of a lot of code to display a single sprite! Do I really need to type all that code for every graphic on my screen?”

It probably makes sense to set up a reusable function that will do all the graphics housekeeping for your game. The average game will probably have many sprites, so you’ll want to make creating a sprite nice and easy to save you coding time.

Replace the last bit of code you pasted in (from “local quad = “MOAIGfxQuad2D.new ()” down to “layer:insertProp ( prop )”) with the following:

----------------------------------------------------------------
-- Textures and sprites creation
----------------------------------------------------------------
local textureCache = {}
 
local function textureFromCache ( name, width, height )
  if textureCache [ name ] == nil then
    textureCache[name] = MOAIGfxQuad2D.new ()
    textureCache[name]:setTexture ( name )
    textureCache[name]:setRect ( -width/2, -height/2, width/2, height/2 )
  end
  return textureCache [ name ]
end
 
local function newSprite ( filename, width, height ) 
	if width == nil or height == nil then
		-- read width/height from the image
		local img = MOAIImage.new ()
		img:load ( filename )
		width, height = img:getSize ()
		img = nil
	end
 
	local gfxQuad = textureFromCache ( filename, width, height )
	local prop = MOAIProp2D.new ()
	prop:setDeck ( gfxQuad )
	prop.filename = filename
	return prop
end

In the code above, newSprite() takes a filename as an argument with optional width and height of the image. If the width and height aren’t passed in as arguments, the function will figure out the image dimensions once it’s been loaded.

The textureCache object is used by newSprite() to represent the graphics object to display on-screen. As well, setDeck is a Lua hashtable, comparable to an NSDictionary that will hold each texture as it’s loaded. Since textures will be reused a lot, this will help speed things up.

Finally, newSprite sets the filename property of the object and returns the newly created sprite to the caller for later reference.

Now you’re able create a sprite with a single line of code! However, you’ll still need three lines total to add and position the sprite on the layer.

Paste in these lines at the bottom of main.lua:

local sp1 = newSprite ( "gfx/carrot.png", 84, 98 ) -- here we supply width and height
sp1:setLoc( -100, -230 ) -- set location - we move the sprite to the left and down
layer:insertProp ( sp1 )
 
local sp2 = newSprite ( "gfx/bone.png" ) -- no width and height supplied, it will be read from the file size
sp2:setLoc ( 100, -230 ) -- move to the right and down
layer:insertProp ( sp2 )

Run your app, and now your screen should look like the image below:

Carrot, meet bone

You could of course have a super-function that (in addition to loading the image) also sets the position and adds it to a layer. However, in this tutorial, you’ll keep things simple and depend on newSprite() to only load the image and handle the caching.

Now that you have an easy way to throw images up on the screen, it’s time to get those images moving around!

First, remove those six lines that you just pasted in main.lua; you’ll be adding a more generic spawnFoodObject() method in the next section that will make your life a little easier.

Fast Food — Setting Object Position and Movement

You’ve already seen the setLoc method used to set an object’s position. Similarly, there’s a setRot method that takes a rotation angle and a setColor method to change the object’s color and alpha (transparency).

To perform animation, such as moving/rotating/fading out a sprite over a period of time, there is a set of similarly-named methods:

  • moveLoc ( deltaX, deltaY, time ) or seekLoc ( X, Y, time ) – move sprite to a different location
  • moveRot ( deltaAngle, time) or seekRot ( angle, time ) – rotate
  • moveColor ( deltaRed, deltaGreen, deltaBlue, deltaAlpha, time ) or seekColor ( Red, Green, Blue, Alpha, time ) – change the tint of the sprite

moveLoc, moveRot and moveColor change the respective property by adding the delta to the current value, while seekLoc, seekRot and seekColor create transition from the current value to the destination value.

In other words, the “move” methods use the current value as the starting point, and alter the object’s position according to the delta value you passed in. In contrast, the “seek” methods are absolute functions, where the object’s position is based on the passed-in position value alone.

You’ll need one more helper function before getting to animation.

Paste the following code at the bottom of main.lua:

----------------------------------------------------------------
-- various utilities
----------------------------------------------------------------
local rand = math.random
math.randomseed ( os.time ())
-- The multiple rand below is due to OSX/BSD problem with rand implementation
-- http://lua-users.org/lists/lua-l/2007-03/msg00564.html
rand (); rand (); rand ();
local function randomArrayElement ( array )
	return array [ rand ( #array ) ]
end

randomArrayElement() returns a random element of an array. This saves some typing since most games have plenty of random events. In this game, you’ll be using it to spawn random types of food.

Next, paste in this code at the bottom of the file:

local function spawnFoodObject ()
	local foodGfxs = {
		"bone.png",
		"carrot.png",
		"catfood.png",
		"dogfood.png",
		"2catcans.png",
	}
	local foodName = randomArrayElement ( foodGfxs )
	local foodObject = newSprite ( "gfx/" .. foodName )
 
	foodObject:setPriority(99) -- food objects should be "on top" of everything else
	foodObject:setLoc(-520, -230) -- initial position, outside of the screen
 
	local anim = foodObject:moveLoc ( STAGE_WIDTH*1.2, 0, 12, MOAIEaseType.LINEAR )
	anim:setListener ( MOAIAction.EVENT_STOP, function ()
		local x, y = foodObject:getLoc()
		if x > STAGE_WIDTH/2 then
			layer:removeProp(foodObject)
			foodObject = nil
		end
	end )
	layer:insertProp ( foodObject )
end
 
spawnFoodObject ()

Take a run through the comments below, which explain the code block you just added.

You should recognize the calls to randomArrayElement and newSprite here. The randomly-chosen sprite is created and positioned at (-520, -230), which is just outside of the window to the left.

The animation is added next by calling moveLoc on the sprite. The first argument is the deltaX, which is how much the X position should change. STAGE_WIDTH * 1.2 is a safe value which allows the sprite to go from the outside of the left side of the screen to the outside of the right side of the screen.

The second argument is deltaY and is set to 0. This means the sprite only moves horizontally since there’s no change to the Y value. The third argument is the time for the movement, and in this case, the movement should take 12 seconds.

The fourth argument requires a bit more attention. It sets the animation curve to linear, which makes the animation play at a constant speed. If this argument wasn’t provided, the default would be an ease-in ease-out curve, where you would see slow movement at the beginning, then fast, then slow again at the very end. In this particular game, it looks best if the food moves at a constant speed, but it may vary for your game.

setListener allows you to define what should happen when the animation stops. This happens either when the animation time runs out, or when you manually call anim:stop(). At this point the sprite should be off-screen so you remove it from the layer and set its reference to nil so it can be garbage collected.

Run your project, and you should see one of five random food items move across the screen, as in the screenshot below:

That’s great, but you really want spawnFoodObject() to run over and over again to provide an endless parade of food across the bottom of your screen.

Replace the call to spawnFoodObject() in main.lua with the following code:

----------------------------------------------------------------
-- Looped timer factory function
----------------------------------------------------------------
local function newLoopingTimer ( spanTime, callbackFunction, fireRightAway )
	local timer = MOAITimer.new ()
	timer:setSpan ( spanTime )
	timer:setMode ( MOAITimer.LOOP )
	timer:setListener ( MOAITimer.EVENT_TIMER_LOOP, callbackFunction )
	timer:start ()
	if ( fireRightAway ) then
		callbackFunction () 
	end
	return timer
end
local foodSpawnTimer = newLoopingTimer ( 1.5, spawnFoodObject, true)

This is another convenient helper function that makes it simple to create looped timers. That’s good — you’ll be using a lot of those! newLoopingTimer() itself takes a function as an argument, so passing in spawnFoodObject() ensures it will be called as often as you need it to.

Run your project, and you should see an endless moving line of treats, like so:

Is it weird that this makes me hungry? ;]

Cats and Dogs and Bunnies, Oh My! — Adding Characters to the Game

In your game, a customer is an animal that waits to be served. The customer has a plate, and the player has to give the customer a specific food to earn points in the game. There’s a limit of three customers that can be active at a time. Customers are placed in a horizontal line at locations (-300, 200), (0, 200) and (300, 200).

As customers are served, they’ll disappear off the screen. If there is an empty spot for a customer, a new one should be spawned every 5 seconds. Aha! Another use for the newLoopingTimer() timer factory! It’s already proving useful! :]

Paste the following code at the bottom of main.lua:

local listOfCustomers = {} -- we'll keep list of active customers here
function horizontalPositionForCustomerNumber ( num )
	return 300 * num - 600
end
local function spawnCustomer ()
	if #listOfCustomers >= 3 then
		return
	end
	-- customerData is an array of arrays:
	-- each one has 3 elements: first is sprite name of that customer
	-- second one is another array: of foods this type of customer accepts
	-- third one is an audio file this customer can make
	local customerData = {
		{"cat.png", {"catfood.png", "2catcans.png"}, "cat.wav"},
		{"dog.png", {"bone.png", "dogfood.png"}, "dog.wav"},
		{"rabbit.png", {"carrot.png"}, "rabbit.wav"},
	}
	local customer = randomArrayElement ( customerData )
	local customerSprite = newSprite ( "gfx/"..customer[ 1 ] )
	customerSprite.soundName = customer [ 3 ]
	customerSprite.requestedFood = {}
	local customerIdx = #listOfCustomers + 1
	listOfCustomers[customerIdx] = customerSprite
	customerSprite:setLoc(horizontalPositionForCustomerNumber ( customerIdx ), 200 )
	layer:insertProp ( customerSprite )
 
	-- plate
	local plate = newSprite ( "gfx/plate.png" )
	plate:setParent ( customerSprite )
	-- plate should be positioned below the customer
	plate:setLoc ( 0, -140 ) 
	layer:insertProp ( plate )
 
	-- random 2 food pieces (from the accepted by the customer)
	for i=1,2 do
		local foodPiece = newSprite ( "gfx/" .. randomArrayElement ( customer [ 2] ))
		foodPiece:setParent ( plate )
		foodPiece:setLoc ( i*100 - 150, 0 )
		layer:insertProp ( foodPiece )
		foodPiece:setScl ( 0.5, 0.5 )
		customerSprite.requestedFood [ i ] = foodPiece
	end
 
	-- those will need to be nil'ed when removing the customer
	customerSprite.plate = plate
	plate.customerSprite = customerSprite
end
local customerSpawnTimer = newLoopingTimer ( 5.0, spawnCustomer, true)

This method creates a new customer (as long as there aren’t 3 or more customers already). It also creates two random food pieces that that customer will accept.

Just like randomly spawning food objects, customer objects are spawned and stored in an array of possible customers. The customerData table keeps track of the sprites, which foods each customer will accept, and some sound effects.

Note :setParent(); it allows you to position an element relative to its parent. When the parent moves, the “child” will be moved accordingly so that its relative position remains unchanged.

If you run this code, you should see a new customer show up every five seconds until all three spots are filled, just like in the screenshot below:

Gameplay – food moves and customers appear…


Feeding the Hordes — Adding Interaction to the Game

What’s a game without some player interaction? Things appear on-screen and move around, but you can’t actually feed the animals yet.

Time to add some input handlers!

Since Moai is meant to be cross-platform, there are very similar ways to handle both mouse and touch events from code. This is easy to do for the simple case — but of course there’s no way to get the mouse over event for a touch event, or a multi-touch event on a regular mouse! :]

Paste in the mouse/touch handling code at the bottom of main.lua:

----------------------------------------------------------------
-- Input (touches and mouse) handling
----------------------------------------------------------------
-- location of the mouse cursor (or touch point)
local mouseX, mouseY
-- this is to keep reference to what's being dragged
local currentlyTouchedProp 
 
local function dragObject ( object, x, y )
	print ("Dragging")
end
local function pointerCallback ( x, y )
	-- this function is called when the touch is registered (before clickCallback)
	-- or when the mouse cursor is moved
	mouseX, mouseY = layer:wndToWorld ( x, y )
	print ("mouse moved")
end
 
function clickCallback ( down )
	-- this function is called when touch/click 
	-- is registered
	print ("Click!")
end

These functions are generic and deal with (x,y) positions and some kind of “click”. It doesn’t really matter whether it’s a touch event or a mouse event — you only need one set of code to handle both! pointerCallback will keep track of the (x,y) position in general; clickCallback will handle touches and mouse clicks, and dragObject will be called as the player clicks and drags with either their mouse or their finger.

Now that your input handlers are in place, it’s time to register the callbacks.

Paste the following code at the bottom of main.lua:

-- Here we register callback functions for input - both mouse and touch
if MOAIInputMgr.device.pointer then
	-- mouse input
	MOAIInputMgr.device.pointer:setCallback ( pointerCallback )
	MOAIInputMgr.device.mouseLeft:setCallback ( clickCallback )
else
	-- touch input
	MOAIInputMgr.device.touch:setCallback ( 
		-- this is called on every touch event
		function ( eventType, idx, x, y, tapCount )
			pointerCallback ( x, y ) -- first set location of the touch
			if eventType == MOAITouchSensor.TOUCH_DOWN then
				clickCallback ( true )
			elseif eventType == MOAITouchSensor.TOUCH_UP then
				clickCallback ( false )
			end
		end
	)
end

This bit of code checks the device type. On mouse devices, it simply sets a callback that is called when the pointer is moved and when the left mouse button is clicked. On touch devices, it sets a callback function for a touch event, calls pointerCallback() — just as for mouse movement — and then finally it calls clickCallback().

Note: If you develop a real cross-platform game, it’s a good idea to implement the UI best practices for each particular platform. For example, when using a mouse, people expect that interactive elements will change their appearance when the cursor is over them to indicate that they are interactive.

In iOS, you can use platform-specific code to handle multitouch input — if it makes sense in your game — and figure out a different way to implement the same behaviour in your game using a mouse.

Just because an SDK allows you to write one set of code for multiple platforms doesn’t mean that you shouldn’t implement platform-specific behaviors. However, that is out of scope for this particular tutorial.

Run your app, play with the game, and check the Terminal to see if the print statements are being created on the console. You should see events like the following:

mouse moved
mouse moved
mouse moved
Click!
Click!
mouse moved
Click!
Click!
Click!
mouse moved
mouse moved

There’s something gratifying about seeing your app handle events — even if it’s just being dumped to Terminal! :]

Now you need to do something useful with your touch handlers, instead of just dumping them out to the console. The most important handler is the click function, as it handles all the touches and, depending on the context, further dispatches the event.

Replace all of clickCallback() in main.lua with the following code:

function clickCallback ( down )
	-- this function is called when touch/click 
	-- is registered
	local pick = partition:propForPoint ( mouseX, mouseY )
	local phase
	if down then
		phase = "down"
	else
		phase = "up"
	end
 
	-- if the touch/click is currently "locked" on some object
	-- this object should be treated as touched, 
	-- not something that's above it, for example
	if currentlyTouchedProp then
		pick = currentlyTouchedProp
	end
 
	event = {
		target = pick,
		x = mouseX,
		y = mouseY,
		phase = phase,
	}
	print ( phase, mouseX, mouseY )
	if down then
		if pick then
			currentlyTouchedProp = pick
			local x, y = currentlyTouchedProp:getLoc ()
			-- we store the position of initial touch inside the object
			-- so when it's dragged, it follows the finger/cursor smoothly
			currentlyTouchedProp.holdX = x - mouseX
			currentlyTouchedProp.holdY = y - mouseY
		end
		if pick and pick.onTouchDown then
			pick.onTouchDown ( event )
			return
		end
	else
		currentlyTouchedProp = nil
		if pick and pick.onTouchUp then
			pick.onTouchUp ( event )
			return
		end
	end	
end

Players should be able to drag the food items to plates, so the click handler here needs to keep track of when the mouse button click/touch begins (touchDown) and when the button/touch ends (touchUp).

The object that is clicked on is stored in currentlyTouchedProp so it can be referenced later to check if it’s the correct food for the customer. As well, this reference is used so that the object can follow the mouse cursor or the finger on the screen.

In order to handle the drag event and move the sprites on the screen, replace the contents of dragObject() in main.lua with the following:

local function dragObject ( object, x, y )
	object:setLoc ( x + object.holdX, y + object.holdY )
end

As an item is dragged to a new (x,y) position, you set its location to that same (x,y) position. holdX and holdY are just offsets, so it doesn’t matter which point you use to drag the object. For example, dragging the object having clicked on the center should work just as well as dragging by having clicked on the edge of the object.

Next, replace pointerCallback() in main.lua with the following code:

local function pointerCallback ( x, y )
	-- this function is called when the touch is registered (before clickCallback)
	-- or when the mouse cursor is moved
	mouseX, mouseY = layer:wndToWorld ( x, y )
	if currentlyTouchedProp and currentlyTouchedProp.isDragable then
		dragObject(currentlyTouchedProp, mouseX, mouseY)
	end
end

If there’s no dragging going on, you normally don’t care if the mouse moves around. currentlyTouchedProp will only need to be set if you’re in the middle of a drag event, at which point dragObject() will need to be called.

For a sprite to respond to touches and clicks, it needs an .isDragable = true attribute on the sprite. Food will be dragged to the animals, so you need to add .isDragable = true to the implementation of spawnFoodObject.

Update spawnFoodObject as below:

	-- this line should already be here:
	foodObject:setLoc(-520, -230) -- initial position, outside of the screen
 
	-- add this one line below!
	foodObject.isDragable = true
 
	-- this line should already be here as well:
	local anim = foodObject:moveLoc ( STAGE_WIDTH*1.2, 0, 12, MOAIEaseType.LINEAR )

Build and run to see how it looks so far. You can drag items, but after you drag them they continue to scroll:

Dragging problems

Dragging problems

Unfortunately, the food will continue to animate (conveyor belt style) even after you try to drag it! Hmm, the game is supposed to be challenging — but not that challenging! :]

Add the following lines to spawnFoodObject() in main.lua just before the final end that closes the function:

	foodObject.onTouchDown = function ( ev )
		anim:stop ()
	end

Run your app!

Try to drag some food items around. Ahh, that’s better — the horizontal animation stops playing whenever you drag the food item around the screen. Try dragging and dropping the food to make sure that those handlers are working correctly! Your screen should look similar to the following screenshot:

Picky Eaters — Adding Drag and Drop Logic to the Game

Being able to drag the food across the screen is a huge milestone in your game. All you need now is a way to know that the food object was released above an animal, and check if it’s the correct kind of food. You won’t earn anything for feeding a rabbit a can of cat food — except probably a dirty look from the rabbit! :]

Add the following code just before spawnFoodObject() in main.lua:

local function foodReleasedAfterDrag ( event )
	local foodObject = event.target
	-- we don't want further interactions with this foodObject
	foodObject.onTouchDown = nil
	foodObject.onTouchUp = nil
	foodObject.isDragable = false
 
	-- check if it's colliding with either customer or his plate
	-- and if this customer requested that food
	successfulFood = nil
	for custIdx, cust in pairs ( listOfCustomers ) do
		if cust:inside ( event.x, event.y, 0 ) or cust.plate:inside ( event.x, event.y, 0 ) then
			for foodIdx, food in pairs ( cust.requestedFood ) do
				if food.filename == event.target.filename then
					-- it's this customer
					print ( "Customer fed!" )
					successfulFood = food
					layer:removeProp ( food )
					table.remove ( cust.requestedFood, foodIdx )
					if #cust.requestedFood == 0 then
						-- all food is delivered
						print ( "Customer is full!" )
					end
					break
				end
			end
		end
	end
 
	-- no matter what, food should disappear
	local fadeOut = foodObject:seekColor(0, 0, 0, 0, 1, MOAIEaseType.LINEAR)
	fadeOut:setListener ( MOAIAction.EVENT_STOP, 
		function ()
			layer:removeProp(foodObject)
			foodObject = nil
		end 
	)
end

Once a food item is dragged somewhere, isDragable is set to false which freezes it in place. The function then loops through every customer to check if the food was dropped on the customer or its plate; if the food is something the customer wants, you’ll see a “Customer fed!” message in the terminal. If the customer gets both food items that they wanted, you’ll see a “Customer is full!” message.

Whether or not the food was what the customer wanted, once the food sprite is dropped it will fade out and disappear.

foodReleasedAfterDrag() also uses the listOfCustomers array, which is defined elsewhere in the file. Find the declaration for the array in main.lua – it should look like this:

local listOfCustomers = {}

And move it to the top of the file.

Note: It’s generally a good idea to keep all the local variables that are in the scope of whole file or module at the top of that file. It ensures that you won’t define it in some place, then try to use it some earlier point in the code. This can easily lead to errors – most of them will be trivial to find and fix, but some may be much harder to track down!

Next, foodReleasedAfterDrag() needs to be called when the object is dragged and the mouse button is released, or the user’s finger is lifted up.

Paste the following code at the bottom of the spawnFoodObject function, just above the closing “end” statement:

	foodObject.onTouchUp = function ( event )
		foodReleasedAfterDrag ( event )
	end

Run your game and try to feed the animals! You should see the “Customer fed!” message show up in the console if you give the correct food to the correct animal, as in the screenshot below:

Now there’s only a few more pieces to implement. When customers get both of the food items they want, they should disappear, the other customers should be rearranged, and it would be nice if each customer barked, squeaked, or meowed their thanks for being fed! :]

Meow, Bark, Squeak — Adding Sound to your Game

First of all, there are two ways you can play sound in Moai.

The first method is Untz system, which is the one you’ll use in this tutorial. It runs well on the Mac, as well as on iOS and Android devices. The other way is to use FMOD toolkit – if you want to target native Chrome client and use sound in your app, have a look at this.

FMOD does require a license for commercial use; however, it can be used free of charge for non-commercial products.

As usual, the lower-level usage will be wrapped into a single function. Paste the following code near the top of main.lua, right before the “Textures and sprites creation” section:

----------------------------------------------------------------
-- audio play code
----------------------------------------------------------------
-- initialize audio system
MOAIUntzSystem.initialize()
local soundCache = {}
local function playAudio ( filename, volume, isLoop, callbackFunction )
	function threadFunc ()
		if soundCache [ filename ] == nil then
			local sound = MOAIUntzSound.new ()
			sound:load ( filename )
			soundCache [ filename ] = sound
			sound = nil
		end
		local sound = soundCache [ filename ]
		sound:setVolume ( volume )
		sound:setLooping ( isLoop )
		sound:play ()
		if callbackFunction ~= nil then
			-- this allows to wait until execution is done and after that
			-- call the callbackFunction () 
			while sound:isPlaying () do 
				coroutine:yield () 
			end
			callbackFunction ()
		end
	end
	thread = MOAICoroutine.new ()
	thread:run ( threadFunc )
end

Here you can see another implementation of a simple cache for loading and playing sounds similar to the texture cache for sprites.

playAudio() accepts a filename as an argument, and either loads the sound file from disk or from the cache. volume controls the volume level, and isLoop sets whether the sound should loop — or just play once.

callbackFunction is interesting – if you pass a callback function as an argument to playAudio() it will be called once the sound has finished playing. Pretty handy!

Note that the sound playback will happen on its own thread. You don’t want the sound playback to stop the game action, so running the playback on a thread means the audio tasks will happen in the background.

With playAudio() in place, playing a sound is easier than saying “Meow”! :]

Find foodReleasedAfterDrag() in main.lua and locate the “Customer is full!” print statement. Add the following lines so the code looks like this:

	-- all food is delivered
	print ( "Customer is full!" )
 
	-- Add these following lines:
	-- make a sound
	playAudio ( "audio/" .. cust.soundName, 1, false, function ( )				
	end )

Run your game! Play through, and you’ll note that once a customer gets their two food items, you should be greeted by some friendly animal noises giving you thanks for running such a wonderful snack bar! :]

You’ve Been Fed, Now Shoo! – Removing Sprites

Now for the final bits of game logic — removing customers once they’re full, and rearranging the customers when one disappears.

Paste the following code in just underneath the playAudio() function from a few steps back:

local points = 0
local function rearrangeCustomers ()
	for customerIdx, cust in pairs ( listOfCustomers ) do
		local x, y = cust:getLoc ()
		cust:seekLoc ( horizontalPositionForCustomerNumber ( customerIdx ), y, 1 )
	end
end
 
local function removeCustomer ( cust )
	layer:removeProp ( cust.plate )
	layer:removeProp ( cust )
	cust.plate.customerSprite = nil
	cust.plate = nil
 
	for custIdx, c in pairs ( listOfCustomers ) do
		if c == cust then
			table.remove ( listOfCustomers, custIdx )
			break
		end
	end
	rearrangeCustomers ()
	points = points + 1
	print ( " Points: ", points )
end

removeCustomer() does what it says, and removes a customer. The sprite and plate images are removed and then rearrangeCustomers() is called so that customers are lined up from left to right with no gaps in between.

You’ll also notice a new points variable. When a customer is full and gets removed, the player will get one point.

You can now call removeCustomer() once the animal’s sound finishes playing.

Find foodReleasedAfterDrag() – where you added the playAudio() call – and add the call to removeCustomer() so the code looks like this:

	-- make a sound
	playAudio ( "audio/" .. cust.soundName, 1, false, function ()
		removeCustomer ( cust )
	end )

Run the game! You’ll note that a customer will now disappear after they’re fed and the sound has played.

Won’t These Animals Ever Stop Eating? — Displaying the Score On-Screen

The score is printed out to the console, but that isn’t particularly useful to the player. Surely it’s better to display it on the screen where they can see it!

There’s a few steps to accomplish this. First you need a MOAIFont object loaded up with the appropriate font file. Then you create a MOAITextBox object that uses the specified font to render text on screen. MOAITextBox can be added to a layer just like a sprite.

Paste in this code at the top of main.lua, just before removeCustomer:

----------------------------------------------------------------
-- TextBox code
----------------------------------------------------------------
local charcodes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
            .."0123456789 .,:;!?()&/-"
 
local font = MOAIFont.new ()
font:load ( "arial-rounded.TTF" )
font:preloadGlyphs ( charcodes, 7.5, 163 )
 
infoBox = MOAITextBox.new ()
infoBox:setFont ( font )
infoBox:setString ( "Animals fed: 0" )
infoBox:setRect ( -STAGE_WIDTH/2, 0, 0, STAGE_HEIGHT/2 )
infoBox:setYFlip ( true )
 
layer:insertProp ( infoBox )

arial-rounded.TTF is already included in the starter bundle you downloaded. The call to preloadGlyphs() helps speed up text rendering since you’re letting Moai know which characters you’ll be using via the charcodes string.

Finally, it sets up an infoBox variable, which is basically a label on the screen that can display some text with the above font.

Now that you have an infoBox available, you can go back to removeCustomer() and display the current score on the screen.

Update the last line of removeCustomer() in main.lua to read as follows:

	infoBox:setString ( "Animals fed: " .. points )

Run your game and you should see your score increase as you feed the animals, as in the screenshot below:

Hungry customers + food = points!

Hurry up! These hungry animals won’t feed themselves, you know! :]

Taking this Dog and Pony Show On the Road — The Mobile Version

Okay – you have a complete game running on the desktop! Congratulations – you’re done…

“Hey, wait a second,” you’re thinking. “I was promised a cross-platform toolkit here!”

There are just a few more elements to add if you want to run the game on an iOS device. The initial setup of these elements might take a bit of time, but you will only need to do this once per project — and then it’s done!

First, make one change to support Retina displays. At the top of main.lua, replace the SCREEN_WIDTH and SCREEN_HEIGHT definitions to match the following:

local SCREEN_WIDTH = MOAIEnvironment.verticalResolution or 960
local SCREEN_HEIGHT = MOAIEnvironment.horizontalResolution or 640

MOAIEnvironment is an object that contains information about the current platform. verticalResolution and horizontalResolution will be empty when you run Moai from the command line, as it’s up to you to decide on a window size. On iPhones, for example, it will return an actual number such as 480 on non-Retina devices, 960 on Retina devices, and 1136 on the iPhone 5.

There are two possible ways of handling the next few steps of Xcode setup:

  1. Copy the headers and libs folders from Moai into your project. This increases the project size but means your project is self-contained and has no external dependencies. The final project archive uses this method so that everything is in one place.
  2. Keep the SDK files in their original location (/Users/username/moai-sdk or wherever) and build the project from there. This will make the Moai SDK you unpacked specific to this game, so keep the original archive around for your next game!

In this tutorial you’ll follow method #2 since you already have the Moai SDK and you know where it lives.

From the Moai SDK folder, navigate to the hosts/ios folder. You should see a whole set of project files.

Open up moai.xcodeproj with Xcode.

Pick the iPhone Simulator target with the latest SDK and run the project. You should see a standard Moai app that doesn’t do very much, as you can see below:

The next step is to tell Xcode about the amazing main.lua you just fleshed out, as well as the image and audio asset files.

From the animalFeeding folder you were working in, drag iTunesArtwork.png and the moai folders into the Xcode project, as shown in the image below:

Make sure to select these options:

  • Copy items into destination group’s folder, and
  • Create folder references for any added folders

Also, select all the targets in the “Add to targets” section, as shown below:

Navigate to the Resources/build group in your project navigator, and open moai-target. It contains the list of folders needed for the Lua part of Moai. Replace the contents of the file with this one line:

./moai

That one line tells the build script to copy everything in the moai folder to the built project. The important thing to note here is that even though you’re now developing the app in Xcode, you should still use your regular Lua editor for Lua files.

Next, you need to let Xcode know to include the Untz system for audio playback.

Open up the Build Settings for the project and add an “Other C Flags” value of -DUSE_UNTZ, as seen below:

The last thing to do before running the game in the simulator is to set the app to run in landscape mode, and set the size of the views accordingly.

Open up MoaiAppDelegate.mm and find this line (around line 48):

mMoaiView = [[ MoaiView alloc ] initWithFrame:[ UIScreen mainScreen ].bounds ];

Replace that line with the following code:

CGRect viewBounds = [ UIScreen mainScreen ].bounds;
viewBounds.size.width = [ UIScreen mainScreen ].bounds.size.height;
viewBounds.size.height = [ UIScreen mainScreen ].bounds.size.width;
mMoaiView = [[ MoaiView alloc ] initWithFrame:viewBounds ];

In the code above, you’re flipping the width and height values since the app will be running in landscape.

Now you need to fix your project to landscape mode.

Open up MoaiVC.mm and replace shouldAutorotateToInterfaceOrientation: with the following:

- ( BOOL ) shouldAutorotateToInterfaceOrientation :
  ( UIInterfaceOrientation )interfaceOrientation {
 
    if ( interfaceOrientation == UIInterfaceOrientationLandscapeRight ) {
        return true;
    } else {
        return false;
    }
}

Finally, go to the project Summary tab and select Landscape Right as a Supported Interface Orientations, as seen in the screenshot below:

Supported interface orientations: Landscape Right

That’s it – do a final build and run to see your final app running on a mobile device!

Where to Go From Here?

Here is the final example project that you made in the above tutorial.

Now that you’ve built a simple cross-platform game and know the basics about the Moai SDK, there’s a lot you can do. You’re now familiar with creating simple factory functions that allow you to abstract some commonly used operations, like creating a sprite, playing an audio effect or setting a repeating timer. That will help you with component reuse future projects.

All your code can be compiled to run as an Android, iOS, Windows or Mac application, and with a bit of change to the sound system used, as a Chrome client game. Pretty amazing!

From here you can try the following changes to your game to expand your knowledge and familiarity with Moai:

  • Modify newSprite so it optionally accepts the position and layer where the sprite should be placed.
  • Modify the resolution-independent solution presented here so there’s no stretching of graphics on devices that don’t have an aspect ratio of 3:2.
  • Visit official Moai Forums here.
  • Check their official documentation wiki, especially the Moai SDK Basics articles.
  • Or you can check out the new book, Developing Mobile Games with Moai SDK.

I hope you enjoyed this tutorial on Moai. If you have any questions or comments, please join the forum discussion below!

Pawel 'Kender' Maczewski
Pawel 'Kender' Maczewski

Pawel is a software engineer with background in web and mobile development. Currently he’s mostly freelancing, but also spending some time building his own dream games. When he’s not at his computer he’s enjoying family time.

You can find out more about Pawel on Facebook, Twitter and his website.

User Comments

8 Comments

  • I get a page not found when trying to download the starter and final projects. great site by the way, been following it for a while now. :D
    tokio
  • Oops - sorry about that. I just uploaded them, so they should be working now!

    And thanks, we're really happy you're enjoying the site, thanks for reading :]
    rwenderlich
  • Thanks for the great tutorial, very clear instructions while covering quite a lot.

    I have been trying out various lua game engines having initially got hooked through codea. I have already had a go with marmalade quick which works on many platforms. I quite liked it but I found running box2d seemed to lag, although to be fair it is still in beta.

    Moai is the second choice so this tutorial has been really useful.

    To be honest it is really difficult to choose as there are so many lua engines, Corona, Moai, gideros and more. Any advice would be appreciated ?
    mcdaid
  • Hi, I tried to run the on my Windows machine, but ran into several snags.

    1. Moai 1.4p0 doesn't recognize `MOAIGfxDevice.setClearColor()`, apparently on later versions you need `MOAIGfxDevice.getFrameBuffer():setClearColor()`. Not sure why 1.4p0 is working for you.

    2. Moai 1.4p0 from http://getmoai.com/sdk/moai-sdk-download.html does not know about MOAIUntzSystem. Circumvented by building from source and using the resulting moai-untz.exe.

    3. The game then finally runs, but I get a ton of these:
    TEXTURE: 'D:/z/animalfeeding/gfx/cat.png' is not a power of two (w:95, h:146)
    OPENGL ERROR: GL_INVALID_VALUE
    TEXTURE: 'D:/z/animalfeeding/gfx/plate.png' is not a power of two (w:197, h:170)
    OPENGL ERROR: GL_INVALID_VALUE
    TEXTURE: 'D:/z/animalfeeding/gfx/catfood.png' is not a power of two (w:159, h:149)
    OPENGL ERROR: GL_INVALID_VALUE
    TEXTURE: 'D:/z/animalfeeding/gfx/carrot.png' is not a power of two (w:84, h:98)
    OPENGL ERROR: GL_INVALID_VALUE
    TEXTURE: 'D:/z/animalfeeding/gfx/bone.png' is not a power of two (w:80, h:77)
    OPENGL ERROR: GL_INVALID_VALUE

    And the game looks like http://i.imgur.com/SP4usLl.png

    Are you sure you used 1.4p0 in your testing? Also, it's a newbie question, but I was under the impression textures should always be power of 2?

    Thanks in advance.
    alexninja
  • I just get a window filled with all black, with the following error, but not until I terminate it -

    attempt to index field 'MOAIGfxDevice' (a nil value)

    That's on line 19, the instruction MOAIGfxDevice.MOAIGfxDevice.getFrameBuffer(0.53, 0.53, 0.53, 1)

    Commenting that line out just to see what would happen I get the aforementioned -

    attempt to index global 'MOAIUntzSystem' (a nil value)

    I'm using Moai SDK version 1.4p0, will try building from source next but wondering if anyone else is experiencing this..
    kennedy1037
  • Hi, again, sorry for the double post, I wanted to let you know, I got AnimalFeeding to work!! It runs perfectly on my mac, complete with sound! Thank you for such a well written, easy to understand, well constructed tutorial!!

    A couple of things messed me up setting up moai on my mac fwiw, I'm on OSX 10.8 using Zerobrane Studio 0.38. Zerobrane could not find my moai interpreter, or so it seemed, because it kept saying "Can't find moai executable in any of the folders in PATH or MOAI_BIN: /usr/bin, /bin..." Turns out when you go into Edit / Preferences / Settings:system to tell Zerobrane where your moai interpreter lives, you have to exit out of Zerobrane and restart it for it to register that location. I didn't have to do that running Zerobrane under windows, it accepted my changes to user.lua immediately.

    The other thing I learned, and I had seen this documented somewhere but I'd forgotten about it - in that user.cfg file where you indicate the path, the word "path" is misleading slightly, to me anyway. You need to also include the filename of the executable that lives in that directory, not just the directory, so, fwiw mine is:

    path.moai = "/usr/bin/moai/bin/osx/moai" as opposed to just pointing at the folder with a "/usr/bin/moai/bin/osx"

    When I was going back through the above tutorial and entering the code in chunks on my win7 machine, everything worked great until the part about adding sound, and on my win7 machine, that was when the program stopped generating graphics of any kind, and started giving me just a plain grey screen and an error message "attempt to index global 'MOAIUntzSystem' (a nil value)." Up until that point everything worked perfectly.

    There appears to be some bizareness with moai and win7, I have two Windows 7 machines, one runs moai in a dimished capacity with crashes like on the untzsystem, and the other win7 machine immediately gives me the general windows error "moai.exe has stopped working" whenever you try to run it. And that machine, my asus laptop, did in fact run moai with Zerobrane for a few weeks with no issues, then started crashing one day, and after oh so many hours of tinkering and googling and reinstalling, it still crashes.

    Thank you again for such a great tutorial!
    kennedy1037
  • Hi, alexninja !

    (Sorry for my bad English)

    a) I am a newbie, but in another forum, they told me that to hide the message about the size of the images should include the following line at the beginning of main.lua:

    MOAILogMgr.setLogLevel (MOAILogMgr.LOG_NONE)

    It Works for me.

    b) Could you send me your file moai-untz.exe moai-1.4p ? I don't know how to compile (I do not understand much about C or about Visual Studio).

    Thank you very much !
    Paulo
    psdias
  • i am also having the same screen as alexninja, and hi! kennedy1037 this line just hiding that error not solving the problem: MOAILogMgr.setLogLevel (MOAILogMgr.LOG_NONE) please help if somebody can..
    Thanks in advance :)
    FuriousMax

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How To Make a Tower Defense Game with Swift.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in December: The Great CALayer Tour

Sign Up - December

Our Books

Our Team

Tutorial Team

... 57 total!

Update Team

  • Zouhair Mahieddine

... 14 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

... 4 total!