How To Make a Simple Game with Moai

This is a tutorial for beginner Moak SDK developers, you’ll learn how to create a new animal-feeding game for iOS from scratch. With Moai, you don’t need to fear being locked in to one platform — you can let everyone enjoy the fruits of your labors! By .

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 7 of this article. Click here to view the first page.

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…

Gameplay – food moves and customers appear…