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.
Heey I tried to run your code into iPad or Iphone 4 and don’t work as work in iphone 3 :S
the grid is wrong in those devices.
Any suggestions?
thanks
Seems to work fine for me when I test in the simulator using the any of the modes: iPad, iPhone, and iPhone4. I haven’t tested on a real device. If you come up with some improvements feel free to post them.
In simulator I have the same problem that in devices :S really don’t know what it can be