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 2 of 6 of this article. Click here to view the first page.

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? ;]