Corona – Tower Defense Game Experiments 3


Defense

The last two posts made a simple game with a tiled playing field and enemies that cross the screen. Now I’d like to add some defense. The goal of the defense elements is to keep enemies from crossing the screen and reaching the other side.

The first step is to create a factory function to generate defensive elements for the game. As a first step we’ll start with a single type of defense and use a red rectangle for art. Here’s a simple function that generates a red square and places at x and y coordinates passed to the function.

local function make_defense( x, y )
	local defense = display.newRect( 0, 0, 32, 32 )
	defense:setFillColor( 200, 0, 0 )
	game_group:insert( defense )

	defense.x = x
	defense.y = y

	return defense -- Return the new defense
end

This function returns the newly created defense. This allows any other function that calls make_defense() to work with the newly created defense, saving a reference, or adding or editing properties of the defense.

Touching the grid

Our defenses should end up on the tile grid. We can use the x and y of the grid square to position the new defense. The grid square can also keep track of it’s contents which we can use to make sure we don’t add more than one defense per square.

Add an event listener to each grid square inside the make_grid() function:

tile:addEventListener( "touch", touch_tile )

Now define touch_tile(). This will have to go above make_grid(), and the make_defense() function will have to be placed above touch_tile().

local function touch_tile( event )
	local phase = event.phase
	if phase == "began" then
		local tile = event.target
		local tile_x = tile.x
		local tile_y = tile.y

		make_defense( tile_x, tile_y )
	end
end

Here we check the event phase. If the phase is “began” then we’ll add a defense at this tile. First get a reference to the tile then get that tile’s x and y. Last call make_defense and pass the tile coordinates.

Testing, at this stage tapping a tile should display a red square centered on that tile.

There are a few problems. Defense elements will appear above some enemies and below others. Each new display item is added to the top of the display list, and appears in front of already existing display items in a group. Items in a group stack at the position of their group inside the parent element.

Using groups to layer elements

As a first idea let’s add a group for each type of element: tile, defense, and alien. We’ll place all of these groups into game_group. Then add each element, tile, alien and defense, as it is created to it’s name group. This will make all like elements stack together.

local game_group = display.newGroup()
local defense_group = display.newGroup() 	-- make a group to hold each class
local alien_group = display.newGroup()		--
local tile_group = display.newGroup()

game_group:insert( tile_group )		-- Add each group to the main group
game_group:insert( defense_group )
game_group:insert( alien_group )

You’ll need to edit the make_grid(), make_alien(), and make_defense() functions so these add the elements they create to the proper group.

Testing again, this time all of the defense elements will always be above the background tiles and below the enemies.

Creating a single defense in each grid square

There is a problem that a new defense is created with each touch on a tile. This won’t be visible, since the defense rectangles are stacked on above another, but it will be happening.

To fix this assign each tile a variable to keep track of whether it has a defense or not.

tile.has_defense = false

Next add an if statement to the touch_tile() handler.

if tile.has_defense == false then	-- Check that tile has no defense
	tile.has_defense = true		-- mark this tile as now having a defense
	local tile_x = tile.x
	local tile_y = tile.y

	make_defense( tile_x, tile_y )
end

Now, if a tile is touched, we first check it for a defense. If no defense is present, tile.has_defense = false, then we add a defense and set tile.has_defense to true. On the next touch no defense would be added.

Corona – Tower Defense Game experiments 2

Quote from a fan

In the last post I set an outline for a tower defense game based on Plants vs Zombies. My child says: “Plants vs Zombies is awesome, and I defeated Zombies until I got the hypnoshroom!”. So it must be pretty fun.

Theme

Plants vs Zombies has a great theme, and many interesting features and characters. The game play is simple, but wrapped in great art and concept. This is the key to any successful game.

Destroy Enemies

The next step is create enemies that we can destroy. In plants vs zombies, enemies can be hit multiple times before they are destroyed. This is an interesting game idea. It allows us to create enemies that can take more or fewer hits than other enemies. It also allows us to create weapons that deal more or less damage to enemies. These types of options allow players more interesting game play. It’s all about choice. It’s also about balance, but we’re getting ahead of ourselves.

At this stage we need to assign each enemy a value to keep track of how much damage it has taken. We will also need to monitor this value and remove the enemy when it reaches 0. To keep things simple at this stage in this example touching a zombie will apply damage to a zombie.

Dynamic objects

In Corona all objects are Dynamic. In this context Dynamic is a programming term. Dynamic objects allow you to add properties to them. Essentially this means we can create a new variables that objects carry around. In the case of the enemies in our game we’ll add a property called life. This will be a number that sets the number of times the enemy can be tapped before it is removed from from the screen.

When each object is added to the screen we need to add the life property to it. Inside the make_alien() function, after alien is defined add:

alien.life = 5

Now each alien created will also have a variable attached to it named life, and each alien will keep track of it’s own value for life. In this way each alien can have different values for life.

Next add a touch event to the alien:

alien:addEventListener( "touch", touch_alien )

I’m using a touch event here because it will react to a touch down on the screen. A tap event would not be fired until after your finger leaves the screen. This will make our game more responsive.

The last step is to define our handler touch_alien(). Remember, in Lua you need to define this function before we call on it, so it will have to be defined above make_alien(), or we need to add a forward declaration above.

local function touch_alien( event ) -- This handles touches on aliens.
	local phase = event.phase
	if phase == "began" then 			-- Check the phase.
		local alien = event.target		-- Get the target
		alien.life = alien.life - 1		-- reduce alien life total by 1
		if alien.life <= 0 then 		-- if 0 or less
			transition.cancel( alien.transition ) -- cancel transition
			remove_alien( alien )		-- remove alien
		end
	end
end

This function first checks the event phase. If the phase is “began” then it gets a reference to the alien that initiated the event via event.target. I used the variable name alien here for consistency. Next subtract 1 from the alien’s life. Last check the life total. If it 0 or less, remove the transition and remove the alien.

Testing the game. Touching an alien 5 times should remove it from the screen.

 

Corona – Tower Defense Game experiments 1

Plants vs Zombies

My child developed a fascination with Plants vs Zombies on the iPhone. The game looks fairly addictive though, I haven’t actually gotten a chance to play yet. The art is great and the levels and mini games look like fun.

Tower Defense

The game is in the Tower Defense category. In a nutshell enemies attack by moving across the game field. You place “towers” to defend your territory. You can spend points to buy defenses of varying cost and capabilities. The concept an theme behind Plants vs Zombies is Zombies attacking your house. Which you defend by planting plants.

The game is played on a grid with zombies advancing along rows. You play defensive plants in grid squares. This makes for a simple layout.

This project covers many posts. Click the tag tower defense to list all of the posts in the series.

Getting started

To be honest I’m starting this with only vague ideas as to how to get to the end result. So a few test demos are in order. First things first lets make a grid and some enemies that will move across the grid.

For now we’ll keep things simple by using rectangles instead of images for the grid and enemies. I imagine I’ll create some sprites later on. By sticking with simple shapes it will be easier to program and share code also.

Creating a grid

To create a grid we’ll define a few variables. We need to know the number of rows and columns. Rows run horizontally and columns run vertical. We’ll put a rectangle in each grid square. These rectangles can help use located taps and position defenses.

To build the grid we need to know size of each grid square and the margin between squares. To get started I defined some variables to define the grid. I started with squares, so tile_size will cover both height and width of the grid squares.

local tile_rows = 9
local tile_cols = 5
local tile_size = 48
local tile_margin = 1

Plants vs Zombies uses a 6 by 10 grid. Here I have 9 rows by 5 columns. Each square is 48px by 48px with a 1 px margin. Keeping all of these values in variables at the top of the script makes it easy to edit the values and change the size of the grid. We can also uses these values to determine the positioning and animation of enemies.

Container Group

It’s probably a good idea to keep all of the game elements inside of a group. Best to do this now, if we don’t we will most likely find that we need to do it in the future.

local game_group = display.newGroup()

From here all game elements will be added to this group. If everything is in the group we can easily move all of the elements, or scale them. This can be good for things like transitioning between levels, and showing areas outside the playing field (Plants vs Zombies uses this).

Create grid squares

Next write a function to create the grid squares.

local function make_grid()
	for row = 1, tile_rows, 1 do
		 for col = 1, tile_cols, 1 do
		 	local tile = display.newRect( 0, 0, tile_size, tile_size )
		 	tile.x = ( tile_size + tile_margin ) * col
		 	tile.y = ( tile_size + tile_margin ) * row
		 	game_group:insert( tile )
		 end
	end
end

Here I have a function containing two nest loops. The outer loop counts to the number of tile rows with the variable rows. While the inner loop counts to the number of tile columns with the variable cols.

Each tile is a rectangle of tile_size equal to the height and width. Each tile is spaced tile size plus margin. Multiply x by col and y by row to place them all in a grid. Last, each tile is inserted into game_group. For the time being the tiles are white. These could be any color, or replaced with an image in the future. You could also give them a transparent color and place an image under the grid.

make_grid()

Add a call to make_grid() to test this out. Try changing the variables at the top to change the size and spacing of the grid. Position the whole group of grid squares by setting the x and y of the game_group.

Create enemies

In this example enemies will be green rectangles. Enemies will start in one of the 5 columns at the top of the screen and advance down the screen. I’m thinking about making the theme of the game aliens invading from space. So the game play will be vertical. Aliens will be the enemies and humans will be defending.

I started with a function that will return an enemy. The function will create the display object and initialize it by setting any relevant properties. This display object will be a local variable, as such it will only exist inside of this function. Keeping variables local will help reduce memory leaks. As a rule I use the same name for the same element everywhere it might show up as a local variable. You can see this below. The variable alien appears in both functions, but is local in both.

Since the concept for my game is aliens invading I’ll call my functions make_alien() and remove_alien(), make_alien() will make new enemies, while remove_enemy() will remove enemies when needed.

local function remove_alien( alien )
	display.remove( alien )
end 

local function make_alien()
	local alien = display.newRect( 0, 0, 32, 32 )
	alien:setFillColor( 0, 200, 0 )
	local row = math.random( 1, tile_cols )
	alien.x = row * ( tile_size  + tile_margin )
	alien.y = 0
	local target_y = ( tile_size + tile_margin ) * tile_rows
	local t = ( tile_rows + 1 ) * 2000
	alien.transition = transition.to( alien, {y=target_y, time=t, onComplete=remove_alien} )
	game_group:insert( alien )
end

Enemies will move via transition.to(). The onComplete property of the transition table will be used to remove the enemy when they have crossed the board. The onComplete call will provide a reference to the object that finished the transition. So in this case alien in remove_alien( alien ) is the same object that was used in transition.to( alien, {…}).

I assigned each alien a transition property and assigned this property a value pointing to the transition that is handling the movement of this object. If an enemy is removed before the transition is complete we will need to cancel the that transition. I’m expecting enemies to be removed as part of game play.

Create enemies with a timer

The last step in this example is to use a timer to call make_alien() periodically. Aliens will remove themselves when their transition is complete.

local alien_timer = timer.performWithDelay( 2300, make_alien, -1)

The timer defined above, calls on make_alien() every 2300 milliseconds, or 2.3 seconds, and loops for ever.

Test your work. Green squares, Aliens, should appear at the top of the screen and move down the screen. Reaching the bottom of the screen they should disappear.

 

Corona – Factory Functions

Functions

Thinking of functions as reusable blocks of code, factory functions make easy work out of creating objects common to any Corona project.

The key component to making a factory function is the key word return. Functions can return values. When a function returns a value the functions ends immediately and any code following return is not executed. The value returned can be any value. In Lua, unlike many other languages, functions can return multiple values.

Functions can also receive values. The values received, often called arguments or parameters make functions really flexible.

Corona has many built in Factory functions, all under the display class.  These functions all return a display object of varying types, text, rectangle, circle, image etc. If you are just making a simple object thee are good enough. Many times though, you will want to return a more specific type of object.

For example just creating a circle can be handled with the factory function: display.newCricle(left, top, radius), while useful is not usually the end of the process. Most often you will want to create a circle at a particular size, color and location. In these cases you’ll need more than display.newCircle(). For example:

local radius = math.random(10, 30)
local circle = display.newCircle(0,0,radius)
circle.x = math.random(30, 290)
circle.y = math.random(30,450)
local r = math.random(0, 255)
local g = math.random(0, 255)
local b = math.random(0, 255)
circle:setFillColor( r, g, b )

Wow that’s a lot of code to make a circle of random size and color. We don’t want to have to repeat, or have it clog up the rest of our project logic.

Imagine expanding this into a loop that creates 30 circles.

 

for i = 1, 30, 1 do
local radius = math.random(10, 30)
local circle = display.newCircle(0,0,radius)
circle.x = math.random(30, 290)
circle.y = math.random(30,450)
local r = math.random(0, 255)
local g = math.random(0, 255)
local b = math.random(0, 255)
circle:setFillColor( r, g, b )

end

This works just fine, and there’s nothing wrong with it. But it could be improved in a few ways.

Leave well enough alone?

Of course not! Not when it comes to programming. This type of thinking gets you in more trouble than you can imagine. As your program grows being able to, identify and modify previously written code becomes essential. Using a factory function allows you to separate defining an object from creating the object.

Think of defining an object as determining all an object’s starting characteristics. While creating the object, creates a new instance of the object.

Making a circle Factory

Here’s a function that creates circles with random color, size and position.

local function make_circle()
	local radius = math.random( 10, 30 )
	local circle = display.newCircle( 0, 0, radius )
	circle.x = math.random( 30, 290 )
	circle.y = math.random( 30, 450 )
	local r = math.random( 0, 255 )
	local g = math.random( 0, 255 )
	local b = math.random( 0, 255 )
	circle:setFillColor( r, g, b )

	return circle
end

The code above is essentially the original code snippet wrapped in a function. The key feature, and what makes this a factory, is the last line: return circle.

Internally the function creates a new shape object and stores it in the variable circle. This variable local to the function and not accessible outside of the function. Using the return keyword we can pass circle along to some other part of our program.

Making circles with make_circle()

Imagine we want to make 30 circles. With the new factory we now only need to add:

for i = 1, 30, 10 do 
	local circle = make_circle()
end

 Why is this better than the first example:

Separating the code defining the circle objects from the code that actual creates them provides a couple advantages: It’s easier to understand and More flexible.

Easier to understand

When breaking code up like this if you need to change the look of the circle you can edit the make_circle function. Here you are altering only the default definition of the object which isn’t embedded along with other code.

More Flexible

Since we have a function that creates circles we can create circle from other areas of our program without having to duplicate code. Anytime you need a circle just call make_circle().

Color Object

Might as well improve on our system of setting the colors of objects. Rather than defining each of the color components, r, g and b, each time we need a color, we’ll wrap all of these up in a single object/table with properties r, g and b. Here’s a sample function:

local function Color( red, green, blue )
	return {r=red, g=green, b=blue}
end

The function above takes three values, combines them into a table. This table has properties of r, g and b. The function returns the table.

You might use it in this way. Imagine you wanted to create a color object representing the color red.

local red_color = Color( 255, 0, 0 )

You could then use the red_color in this way:

circle:setFillColor( red_color.r, red_color.g, red_color.b )

The beauty of this system is leads to better and more organized code in the future. With the color stored in an organized form we can start writing functions that transform our Color object by adjusting the hue, saturation and lightness. We can also share a Color object with other objects easily.

Random Color

Our system needs a random color. In this case we might as well make another function that will work with out Color object to return a random color.

local function random_color()
	local r = math.random( 0, 255 )
	local g = math.random( 0, 255 )
	local b = math.random( 0, 255 )

	return Color( r, g, b )	
end

Here define some random values for the red, green and blue color components then create a new color object from those values and return it.

Use this to further simplify the circle factory function:

local function make_circle()
	local radius = math.random( 10, 30 )
	local circle = display.newCircle( 0, 0, radius )
	circle.x = math.random( 30, 290 )
	circle.y = math.random( 30, 450 )

	local color = random_color()
	circle:setFillColor( color.r, color.g, color.b )

	return circle
end

Doesn’t look like a huge improvement, but it does more than you might think. By separating our code in this way our program can make colors and circles in a wider variety of circumstances.

Corona – Sprite Explosions

Sprites

Often you’ll want to create an animated object on the screen, have it play through an animated sequence then remove itself from the screen. In this example I’ll created an animated explosion. The explosion appears when you tap the screen, then removes itself once the sequence of frames is complete.

Sprite Sheet

Creating the sprite sheet. I used the explosion generator here: http://www.nuvorm.nl/?p=1. You’ll need to have the Flash player installed to use this. It generates a sequence of frames in a wide image. The iPhone 3 does not support images larger than 1024 and the 4 supports maximum dimensions of 2048. The explosion image generated from nuvorm.nl was 3999px wide. I had to take this into Photoshop and arrange the image into multiple rows.

I calculated the size of each frame by counting the frames and dividing by the width. Turns out I had 43 frames. This meant each frame 93 px wide. The original image was 100 pixels tall. I sized the image 400 px tall. Then I moved the rows of 10 frames to create a new image 10 frames wide by 4 frames tall. When I was done I had created the image below:

Note the last few frames didn’t have much in them so I dropped the last three frames and made the sequence 40 frames long.

Continue reading Corona – Sprite Explosions

Corona – Accelerometer

Accelerometer

The Accelerometer is built into all IOS and other handheld devices, it detects motion of the device, tilting or shaking. Corona handles this with the Accelerometer event.

Remote Test

The accelerometer is difficult to test with, since the Accelerometer events are not available in the simulator. This means that you need to build your project each time you want to test. Which tedious and slow. There’s a solution!

There are several apps that can be used to send accelerometer and other events from a real device to the simulator.

All of these apps are very similar. You install them on a device: iPhone, iPad, iPod Touch etc. Launch and Configure the app. Set up your Corona project to receive data from the device. This consists of adding a few lines of code to your main.lua file.

Continue reading Corona – Accelerometer

Corona – Physics

Corona Physics

Corona incorporates the Box2d physics engine. The physics engine allow you create objects that move and interact like real physical objects. The engine works in flat 2d space.

The Corona implementation of Box2d is simple and intuitive to use. making it incredibly easy to add physics based motion to any project.

Starting physics

To work with physics you’ll to load the physics library and start the physics engine.

Start the physics engine by loading it with:

local physics = require( "physics" )

From here you’ll create physics objects and control the general physics environment through the variable physics defined in the line of code above.

Then start physics with:

physics.start()

Note: You must call start physics, with the line above doing anything else with physics.

Continue reading Corona – Physics

Corona – Arranging Objects

Arrays and Objects

It’s very common to create a number of similar objects. An easy approach is to use a for loop to create the objects and an array to store references to the objects created.

Here’s a quick example with squares. The first few examples will create static object and arrange them a few patterns.

Random arrangement

The example will arrange the objects at random positions on the screen.

-- Define an Array to hold the objects

local square_array = {}

-- A loop to create a number of objects

for i = 1, 10, 1 do

-- Make a new shape object

local square = display.newRect( 0, 0, 30, 30 )

-- Position the object at a random x and y

square.x = math.random( 30, display.contentWidth - 30 )

square.y = math.random( 30, display.contentHeight - 30 )

-- Add the reference to the object in the Array

table.insert( square_array, square )

end

The example defines an array, then executes a loop creating 10 objects and adding them all to the array.

Continue reading Corona – Arranging Objects