Corona – Tower Defense Game Experiments 5

Destroying Aliens with bullets

In the last post I created bullets that fire from a defense at advancing enemies. I still need to check if an enemy is hit, and if so reduce it’s life, and remove it if it has reached 0 life.

I see many example that would use physics for the collision detection for this. I feel that physics may be over kill for what I am doing here. it’s also my guess that using physics would add a lot of processing overhead. Physics also brings up other problems that need to be solved. All in all I physics, in this case will create more problems than it solves. So, I’m going to opt to use a hit test and manage bullets and alien enemies in arrays.

A few notes on code styles. The code is now getting longer and so I decided to make a few style changes to help me keep organized. Constants, these are variables that will not change value. These will be UPPER_CASE. Lua doesn’t have actual constants, I’m using upper case as a reminder.

Hit Test – Bullets vs Aliens

For each bullet fired, I will need to check if that bullet hits an enemy. To determine a hit take the x and y location of the bullet and check if it is inside the bounds of the enemy. The bounds of an object is represented by four values that define the edges of the rectangle surrounding the object. Use display.contentBounds to get the bounds object. This object will have properties of:

  • xMin (defines the left edge)
  • xMax (defines the right edge)
  • yMin (defines the top edge)
  • yMax (defines the bottom edge)

To determine if a point, an x and y pair, is within the bounds of an object use an if statement to compare the bounds properties and the x and y of the point. Check if the x value is greater than xMin and less than xMax, while the y value is greater than yMin and less than yMax. Something like this:

if x > bounds.xMin and x < bounds.xMax and y > bounds.yMin and y < bounds.yMax then
... HIT! ...
end

Checking for a single hit is simple and straight forward. With multiple bullets and enemies on the screen at the same time the problem is a more complicated. For each bullet we need to check for a hit against each enemy. To organize this, all bullets need to be in an array and all enemies in another array. In the future I may want enemies to attack defenses so, I made an array to hold defenses also.

local defense_array = {}
local alien_array = {}
local bullet_array = {}

With the bullets in an array and the enemies in another array we can look at each bullet and perform a hit test against each enemy with that bullet.

Making Bullets

I used the same strategy for bullets that was used for other display elements tiles, aliens and defenses. I need a function to make bullets and another to remove them.

BULLET_SPEED

I used a transition to move the bullets up the screen. The time it takes a bullet to move up the screen will vary by the distance the bullet has to travel. To get the bullets to move at a consistent rate I created the variable BULLET_SPEED. This is a “constant”, that is it’s value will NOT change while the program runs. Corona and Lua do not support actual constants, but this is the intent for this variable, so I will use a style to mark it as such.

I set the speed in pixels per millisecond (1/1000 of a second). I decided on a speed of 400 pixels per second. In milliseconds this becomes 1000 / 400.

local BULLET_SPEED = 1000 / 400

To create transition that moves at this constant rate multiply the distance by BULLET_SPEED and set this value as the time of the transition.

Bullets will be black circles, for now.

Here are two functions to make and remove bullets.

local function remove_bullet( bullet )
	local index = table.indexOf( bullet_array, bullet )
	transition.cancel( bullet.transition )
	table.remove( bullet_array, index )
	display.remove( bullet )
end

local function make_bullet( x, y )
	local bullet = display.newCircle( 0, 0, 5 )
	bullet:setFillColor( 0, 0, 0 )
	bullet.x = x
	bullet.y = y
	table.insert( bullet_array, bullet )

	local bt = y * BULLET_SPEED
	bullet.transition = transition.to( bullet, {y=0, time=bt, onComplete=remove_bullet} )
end

To detect a hit I need to look at the position of each bullet and alien and apply a hit test each frame. I added an enterFrame event and handler. I also separated the hit test into it’s own function since it might be useful for other purposes in the future. Note the hit test returns true on a hit or false. This way you can place a hit test inside of an if statement, as in the code below:

local function hit_test( x, y, bounds )
	if x > bounds.xMin and x < bounds.xMax and y > bounds.yMin and y < bounds.yMax then
		return true
	else
		return false
	end
end 

local function on_enterframe( event )
	for b = 1, #bullet_array, 1 do
		local bullet = bullet_array[b]

		if b > #bullet_array then
			return
		end

		for a = 1, #alien_array, 1 do
			local alien = alien_array[a]

			if hit_test( bullet.x, bullet.y, alien.contentBounds ) then
				if alien.life > 0 then
					alien.life = alien.life - 1
				else
					remove_alien( alien )
				end
				remove_bullet( bullet )
				break
			end
		end
	end
end

Runtime:addEventListener( "enterFrame", on_enterframe )

Here’s a full code listing so far.
Continue reading Corona – Tower Defense Game Experiments 5

Corona – Tower Defense Game Experiments 4

Bullets

In the last couple posts I modeled some simple ideas for creating a Tower Defense game like Plants vs Zombies. So far I have a tiled background, enemies that advance down the screen, and the ability to add a defense at any particular tile.

At this point the defense and the enemies still do not interact. The next step is to give the defense elements the ability to fire bullets at the advancing enemies.

Bullet Factory

Throughout the process I have been using a system based on “factory functions” to create new objects. I like this method it keeps me organized. I’ll use it again to create bullets. Though Lua is not an OOP language factory functions have some the feeling of using new to create an instance.

The bullets will be simple circles. I will use a transition.to() to move the bullets up the screen.

The make_bullet() function takes the starting x and y position of the bullet.

local function remove_bullet( bullet )
	display.remove( bullet )
end

local function make_bullet( x, y )
	local bullet = display.newCircle( 0, 0, 5 )
	bullet:setFillColor( 0, 0, 0 )
	bullet.x = x
	bullet.y = y

	bullet.transition = transition.to( bullet, {y=0, time=1000, onComplete=remove_bullet} )
end

Calling functions from timer.perfromWithDelay()

Now it’s time to send the bullets up the screen. To make this happen I’ll use a timer. Each defense element will need a timer. I’m adding a reference to each timer as it is created, to the defense element the timer is created with. This way if a defense element is destroyed in the future that element will be able to cancel it’s timer.

defense.timer = timer.performWithDelay( 1000, function() make_bullet( defense.x, defense.y ) end, -1 )

The second parameter is a function, that is called with each timer event. We need to call make_bullet(x,y), this function requires the x and y location of the bullet. We can’t write this as:

-- WRONG!
defense.timer = timer.performWithDelay( 1000, make_bullet( defense.x, defense.y ), -1 )

Here we would be invoking make_bullet( defense.x, defense.y ) at the time we created he timer. The effect would be one bullet fired, rather than firing a bullet with each timer event. The timer in this case would act on what was RETURNED from make_bullet(), which would be nil.

In the correct format above I wrapped the call to make_bullet( defense.x, defense.y ) in a function. This is interesting because defense is a local variable and lost when make_defense() ends. The handler function defined inside the timer function encloses the variable defense (defense.x and defense.y) effectively preserving the values they had at the time that this function was defined.

Since this project has gone through a few changes, I’ll post the full source code I have been using up to this point for reference.

Continue reading Corona – Tower Defense Game Experiments 4

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.