How To Make a Breakout Game with Corona

Greg Pugh

This is a post by Tutorial Team Member Greg Pugh, author of the Colin Turtle children’s eBook app series. You can also find him on .

Learn How to Make a Zombie Breakout Game with Corona SDK!

If you like to play video games, you’ve most likely played some variant of the classic game Breakout.

The goal of Breakout is to move a paddle on the bottom of the screen with the goal of bouncing a square ball to break through a wall of blocks and advance through the levels.

In this tutorial, you will create a game very similar to Breakout, except instead of breaking blocks, you’ll be killing zombies! :]

To build this game, you will use a game framework called Corona. Corona is a cross-platform software development kit, which means you can write your app once and then export it to multiple platforms such as iOS and Android.

Additionally, the application code is written in Lua, a simple and lightweight scripting language, which can be considerably easier to learn than compared to Objective-C or Java.

This tutorial is for complete beginners to Corona, or to Corona developers who’d like to brush up a bit more on the fundamentals.

So without further ado, let’s start breaking some zombies! :]

Install Some Refreshing Corona

The first thing you’ll need is the Corona SDK, so download the Corona SDK here.

Note: For this tutorial, you can just use the unlimited free trial version of the SDK. You’ll only need to purchase a license for Corona if you decide you want to distribute your app to the various app stores. You can read more about the Corona subscription pricing when you’re ready.

Once downloaded, installing the SDK is a fairly straightforward process if you’re on a Mac. Simply open the DMG, accept the software license agreement, and then drag the Corona folder to your Applications folder. That’s it!

If you’re on a Windows machine, the installer will take you through the installation process.

Open the Corona SDK Simulator to make sure that everything works. On a Macintosh, it will likely be located in Applications > CoronaSDK. If this the first time you run the simulator, you will need to accept the terms of the license agreement in order to continue.

At this point, you will be prompted to login to your Corona Developer account. If you don’t have one, don’t sweat it! :] Take the opportunity now to create one. This is a one-time operation, and you won’t have to perform this step again once you’ve created an account and logged in.

Once logged in, you should see the following screen:

If it looks like everything has been installed correctly, then you’re ready to start using Corona to fend off the zombie apocalypse! :]

Which Text Editor is Best?

Choosing which text editor to use will be a matter of personal preference and willingness to pay for software. For Mac users, you can use TextEdit that comes on OSX. PC users can use Notepad that comes pre-installed with Windows.

There are three editors that integrate nicely with Corona on the Mac:

  • Sublime Text 2. Sublime Text 2 is a new and popular text editor for the Mac with autocomplete support for Corona (with a plugins). [Download Link] [Corona Plugin].
  • TextMate. TextMate is an older but still very popular text editor for the Mac also with autocomplete support for Corona (with a plugins). [Download link] [Corona Plugin].
  • CoronaComplete. A full featured IDE for Corona. [Download link]

Each software has their own advantage and disadvantages, so feel free to try them all to see which suits your style the best!

Note: To develop an app in Corona, you’ll use your own text editor and the Corona Simulator; you won’t be using Xcode or Eclipse as you would for regular iOS and Android apps.

Pre-Zombie Starter

Download the artwork, sound effects, and starter configuration files you’ll need from the resources for this project and unzip them to your desktop, or a folder of your choice.

The top-level folder from the resources archive, ZombieBreak, will be the project folder for your Corona application – this is where you will be saving your code files.

Feel free to take a peek at the beautiful artwork and cool sound effects inside!

Main is the Name of the Game

Start the Applications\Corona SDK\Corona SDK Simulator, and then navigate to File > Open. Browse to the ZombieBreak folder, and open the main.lua file. If Corona prompts you which device to simulate, choose iPhone.

To view the app running in the simulator, navigate to “Window > View As” in the Corona SDK Simulator menu to view your application running on different devices. For the purposes of this tutorial, use the iPhone simulator, as below:

And there you have it, your first Corona app!

Oh, wait — you don’t see anything but a black screen? Well, maybe it’s nighttime and the zombies are wearing black. :]

Okay, fair enough — time to add some graphics and gameplay to your app!

Zombies in the dark?

Files, Files, Everywhere

Take a look at main.lua — you’ll notice it’s empty! That would explain the blank screen when you first launched the app, wouldn’t it? :]

There are two other configuration files in the ZombieBreak folder: build.settings which contains settings that are used when the app is built; and config.lua which contains settings used when the app is run.

Both files contain good starter settings for your Zombie game, so you don’t need to alter them at this point. However, if you’re curious to learn more, check out the Configuration Options page at the Corona SDK documentation site.

Head right in to the next section to start working with code in Lua and Corona!

I Know the Status…Gone!

One of the first things you will do in this game is hide the status bar. This game is going to be so amazing that the player won’t care what time it is or how much battery life is left! :]

Paste the following lines of code into main.lua and save.

-- Hide Status Bar
display.setStatusBar(display.HiddenStatusBar);

If the simulator is still running, it should tell you that main.lua has changed, and offer to relaunch the app in the simulator to get your new code up and running. However, you can also navigate to “File > Relaunch” to do this step manually. You’ll need to remember to do this each time you update the code to see your work in action! :]

Comments in Lua are prefixed with “- -“, as seen above. Although it’s tempting to skip putting comments in your code, especially in a rapid-development language like Lua, you’ll be glad you did when you come back to a project weeks or months later! :]

The call to display.setStatusBar() hides the status bar, as shown below:

Abracadabra Status Bar Be Gone!

Did that seem a little too easy? If so, that’s just the power of Corona doing a lot of the heavy lifting for you!

The next section takes you through the basic physics of this game; you’ll see how Corona neatly handles the sticky bits of game physics for you! :]

But I Failed Physics in High School!

In order to get a handle on the physics required for this game, think back to the old school Breakout game, or have a quick read through the Wikipedia article on the game. Breakout consists of a moving ball, a player-controlled paddle to bounce the ball on, some walls to keep the ball in play, and rows of bricks that are destroyed when the ball comes in contact with them.

Does it sound like there might be physics involved? :] It sure does! Well then, you’d better get started coding your complex physics engine!

Paste the following lines of code into main.lua and save:

-- Physics Engine
local physics = require "physics";
physics.start();
physics.setGravity(0, 0);

Okay, you’ve just coded your complex physics engine. :] Believe it or not, that’s it!

The code above simply tells Corona to include the built-in physics engine, and to set the gravity of your game universe to zero in both the X-direction (side-to-side) and the Y-direction (up and down). There’s no gravity in this game — the physics is solely controlled by the bullet ricocheting off of your paddle, the walls, and of course, your zombies!

You’ll program the movement of the bullet a bit later in this tutorial, but for now, revel in the fact that you just added physics to your game in three lines! :]

Note: The code above creates a local variable named physics — hence the “local” keyword. It’s a good practice to use local instances of variables; otherwise, they will be “global” and require manual cleanup!

Games aren’t much fun without sound effects and music, so luckily the starter package provides some for you to use! The next section will get your app humming. :]

Music Makes the Rhythm

In order to make use of the included sound effect files, paste the following lines of code in main.lua and save:

-- Load Sounds
local bgMusic = audio.loadStream("sounds/ZombieBreakTheme.mp3");
local zombieKill = audio.loadSound("sounds/ow.mp3");

These two lines load the two sound effect files into the game so that they’re ready for playback when required. You haven’t actually instructed the files to play, so you won’t hear anything yet when the app is restarted in the simulator.

Even though Corona handles a lot of things for you, you’ll still need a host of variables to store information particular to the operation of your game. The next section shows you how! :]

Varying Variables

There’s a good number of variables that will be used in your game. The first two you’ll set up are _W and _H, which will help when working with on-screen placement of graphics.

Paste the following in main.lua and save:

-- "Constants"
local _W = display.contentWidth / 2;
local _H = display.contentHeight / 2;

_W and _H are technically variables, but in this case, treat them like constant values used to keep track of the device’s screen width and height.

As every device screen will vary in size, it is much more flexible in game development to place objects on the screen using relative placement, as opposed to absolute positioning. When you don’t know what the eventual screen resolution will be, it’s easier to say “10 pixels from the right edge” rather than “pixel 310″.

Knowing the center points rather than the full width and height is also really useful in Corona, as it handles placement based on the center of an object, unlike native iOS, which places objects based on the (0,0) coordinates of the object.

For example, placing an object at (0, 0) in Corona will put the center of the object in the top-left corner, leaving a portion of the object offscreen! :]

Next, you’ll create other variables that will be covered later in the tutorial.

Paste the following in main.lua and save:

-- Variables
local zombies = display.newGroup();
local zombieWidth = 50;
local zombieHeight = 50;
local row;
local column;
local score = 0;
local currentLevel;
local velocityX = 3;
local velocityY = -3;
local gameEvent = "";

A group is exactly what it sounds like: individual objects in your application that you can refer to as a whole. In this case, you created a new group called “zombies”, which will contain…you guessed it, zombies! The benefit to grouping zombies in this manner, as opposed to handling them all separately, will be seen later in the tutorial.

Each zombie will be 50 x 50 pixels in size, and will be placed in rows and columns across the screen. The score will start at 0 and the player will get to change levels as they progress.

The velocity variables velocityX and velocityY will be used to move the bullet on the screen, and the gameEvent variable will be used to keep track of what to do when the player wins or loses.

Now that you have that first set of variables defined, it’s time to get the rest of the variables in your code that manage on-screen activities. Paste the following into main.lua and save:

-- Menu Screen
local titleScreenGroup;
local titleScreen;
local playBtn;
 
-- Game Screen
local background;
local player;
local zombie;
local bullet;
 
-- Score/Level Text
local zombieKillText;
local zombieKillNum;
local levelText;
local levelNum;
 
-- textBoxGroup
local textBoxGroup;
local textBox;
local conditionDisplay;
local messageText;

The variables used above should be fairly self explanatory; the comments above each block indicate the purpose and function of the variables in that set.

Settings…Variables…Where are the Graphics?!

You’re probably getting pretty antsy right about now; a whole bunch of variables, and the only thing you have to show for your efforts is a black screen! :]

Fortunately, it’s time for the main event in your app…functions!

Functions in Lua are comparable to other languages such as C or Ruby, and similar to methods in Objective-C.

Create the main function that will initiate the app by pasting the following code at the bottom of main.lua :

-- Main Function
function main()
	showTitleScreen();
end

When the app starts, main() will call another function called “showTitleScreen”. Let’s solve the mystery of what showTitleScreen does and paste the following code into main.lua:.

-- Show the Title Screen
function showTitleScreen()	
 
	-- Place all title screen elements into 1 group
	titleScreenGroup = display.newGroup();
 
	-- Display background image
	titleScreen = display.newImage("images/titleScreen.png", 0, 0, true);
	titleScreen.x = _W;
	titleScreen.y = _H;
 
	-- Display play button image
	playBtn = display.newImage("images/playButton.png");
	playBtn.x = _W;
	playBtn.y = _H + 50;
	playBtn.name = "playbutton";
 
	-- Insert background and button into group
	titleScreenGroup:insert(titleScreen);
	titleScreenGroup:insert(playBtn);
 
	-- Make play button interactive
	playBtn:addEventListener("tap", loadGame);
end

Okay, so what does the showTitleScreen function do?

First, it sets up the variable titleScreenGroup as a group to hold every element that will be on the title screen. In this case, the elements of titleScreenGroup are just a background image (titleScreen) and a play button (playBtn).

The background image is placed in the center of the screen, and the play button is placed slightly below center. The positioning of the on-screen elements is made easy thanks to the handy _W and _H variables that were set up earlier.

The titleScreen and playBtn elements are then inserted into the titleScreenGroup.

Of course, there needs to be something to call the main() function and launch the game! Paste in the code below at the very bottom of main.lua.

-- Run the game
main();

You can define a function at any place in your code, and those functions can call other functions defined at any place in your code too. However, calling a function directly as we just did above for main() must be done after the function is defined. So in this example, that last bit of code to run the game must be at the very bottom of main.lua!

Note: Unlike C, C++, and even Objective-C, the main() function is not called automatically as you might expect. Remember to add that call to main() yourself when writing all those apps with Corona SDK! The line main(); should always be at the very bottom of main.lua; any code added from here on out must be strictly above this line!

Now, save the file to see the new result in the Corona SDK simulator:

Hooray…finally some artwork!

Every game needs some level of interaction with the user; or else it wouldn’t be much of a game! :] The next section will add some interaction to your app!

Listening for Interactions

The play button can be made interactive by assigning an event listener to it; let’s face it — what good is a button that doesn’t do anything?

Paste the following code into main.lua — above “main();” — and save:

-- When play button is tapped, start the game
function loadGame(event)
	if event.target.name == "playbutton" then
		audio.play(bgMusic, {loops =- 1});
		audio.setVolume(0.2)
		transition.to(titleScreenGroup,{time = 0, alpha=0, onComplete = initializeGameScreen});
		playBtn:removeEventListener("tap", loadGame);
	end
end

When the “tap” event happens (i.e. the user taps on the screen), Corona calls a function called ‘loadGame’. The loadGame() function starts the background music and adds a transition from the title screen group to the level 1 screen.

Since you no longer need to wait for the user to tap the play button, you use removeEventListener to stop waiting for that.

Reload the app in the simulator. Now, when you tap or click on the play button, the background music will begins to play in a loop. The volume for the music is set low so that it won’t be distracting to the player.

Note: If you’d like to stop the looping music and don’t want to mute your speakers, navigate to “File > Relaunch” in the Corona SDK simulator and to go back to the title screen. For a permanent fix while you’re coding the app, comment out the two lines of audio code to play no music at all!

And finally, the title screen will disappear and switch to the first level of the game. Sounds pretty great! However, there isn’t anything to display in the Level 1 screen as you haven’t built it yet! :]

Time to fix that! Move on to the section below to build your first level!

Oh, Give Me a Home Where the Zombies Will Roam

Now it’s time to build the first level of Zombie Break! The game will start out in Atlanta, GA, and once the southern zombies are terminated, the player will move onto New York City to wipe out some Yankee zombies.

To start the game, you’ll need to put a few things in place:

  1. Create a player paddle and bullet at the bottom middle of the screen.
  2. Show the city name and score in the heads up display.
  3. Run the level.

Now that you’ve got your checklist of items, paste the following code into main.lua above “main();” and save:

-- Set up the game space
function initializeGameScreen()
	-- Place the player on screen
	player = display.newImage("images/player.png");
	player.x = _W;
	player.y = _H + 140;
	player.name = "player";
 
	-- Place bullet on screen
	bullet = display.newImage("images/bullet.png");
	bullet.x = _W;
	bullet.y = player.y - 30;
	bullet.name = "bullet";
 
	-- Score text
	zombieKillText = display.newText("Zombies Killed: ", 25, 2, "Arial", 14);
	zombieKillText:setTextColor(255, 255, 255, 255);
	zombieKillNum = display.newText("0", 150, 2, "Arial", 14);
	zombieKillNum:setTextColor(255, 255, 255, 255);
 
	-- Level text
	levelText = display.newText("City:", 360, 2, "Arial", 14);
	levelText:setTextColor(255, 255, 255, 255);
	levelNum = display.newText("Atlanta", 400, 2, "Arial", 14);
	levelNum:setTextColor(255, 255, 255, 255);
 
	-- Run level 1 
	changeLevel1();
end

The first part of this function places the player sprite (the paddle) toward the bottom of the screen, and places the bullet right above the paddle. It then displays the game status text in the upper left and upper right, using Corona’s handy display.newText method.

At this point, you’re ready to set up the contents of the first level. That bit of code should do a couple of things:

  1. Set a background image in the center of the screen, just like you did for the title screen.
  2. Make the player paddle interactive so the game starts when the user taps the paddle.

Paste the following code into main.lua above “main();” and save.

function changeLevel1()
	-- Level 1 background image will be Atlanta
	bg1 = display.newImage("images/atl.png", 0, 0, true );
	bg1.x = _W;
	bg1.y = _H;
	bg1:toBack();
 
	-- Start
	player:addEventListener("tap", startGame)
 
	-- Reset zombies
	gameLevel1();
end

This adds the background sprite in the center of the screen, and moves it to the back with the toBack function. It then adds an event listener waiting for the player to tap the scren – when that occurs, startGame will be called.

Run the app in the simulator! You’ll see the following setup:

Now when you tap the play button, you’ll see a background image, the player’s paddle, the bullet, the count of how many zombies have been killed, and the name of the city. There aren’t any zombies to kill yet, but it’s starting to look like a game! :]

However, the game needs some interaction with the various parts — although “interaction” seems like a pretty tame word for killing a zombie with a bullet! :] Looks like this game needs to make use of that physics engine you added some time ago.

Let’s Get Physical

Now that you have the player and bullet on screen, you’ll need to give them some physics properties. But wait a second…didn’t you already enable physics in the game in the very beginning?

That’s correct, you did. However, just because the physics routines exist, that doesn’t mean that they apply to anything on the screen – unless you write some code! :]

Paste the following into main.lua above “main();” and save:

-- When the game starts, add physics properties to player and bullet
function startGame()
	physics.addBody(player, "static", {density = 1, friction = 0, bounce = 0});
	physics.addBody(bullet, "dynamic", {density = 1, friction = 0, bounce = 0});
	player:removeEventListener("tap", startGame);
	gameListeners("add");
end

In the code above, you tell the player object and bullet object how dense they are, if they have any friction and if they’ll add any bounce to the collision. The call to “removeEventListener” on the player is there so the game doesn’t listen for a user tap once the game has already started.

Note: It’s good practice to remove event listeners when they aren’t needed to free up memory and device resources.

Now that you have most of the important elements on screen, the next section will get things moving around!

Working Hard or Hardly Working?

Take a moment and think about what the requirements of how the bullet will interact inside the game. The bullet needs to move around the screen, bounce off walls, and kill zombies.

Paste the following above “main();” in your main.lua file and save:

-- Bullet properties
function updatebullet()
 
	-- Movement
	bullet.x = bullet.x + velocityX;
	bullet.y = bullet.y + velocityY;
 
	-- If bullet hits the ceiling or left or right wall, bounce off of it
	if bullet.x < 0 or bullet.x + bullet.width > display.contentWidth then  
		velocityX = -velocityX;
	end
 
	if bullet.y < 0  then 
		velocityY = -velocityY;
	end
 
	-- If the bullet hits the bottom wall, the player has lost the game
	if bullet.y + bullet.height > player.y + player.height then 
		textBoxScreen("MY BRAINS!!", "Try Again") gameEvent = "lose";
	end
end

The local variables velocityX and velocityY will control the direction of the bullet. velocityX starts out at 3, so the bullet will move to the right; velocityY starts at -3 so it will move up the screen. Both of these velocities together means the bullet will move diagonally up and to the right.

However, the bullet shouldn’t go flying off of the screen! :] You’ll need to constrain it to the display width and height. If the bullet hits the top, left, or right sides of the screen it should ricochet. If the bullet hits the bottom of the screen, that means the player didn’t successfully bounce the ball off the paddle — and the player loses the game.

The updateBullet function looks great — now you just need to call it somewhere. But where?

Listen Up, Buster

Since the bullet needs to move around continuously (at least until the player wins or loses), you want the updateBullet function to be called over and over.

You can do this by asking Corona to call the updateBullet every time it redraws the frame. Our Zombie game runs at 60 frames per second, so Corona would call updateBullet for us automatically 60 times per second.

Paste the following code into main.lua above “main(); and save:

-- Listen for bullet and player collisions and user dragging player
function gameListeners(event)
	if event == "add" then
		Runtime:addEventListener("enterFrame", updatebullet);
		-- Bookmark A: You'll be adding some code here later
	-- Remove listeners when not needed to free up memory
	elseif event == "remove" then
		Runtime:removeEventListener("enterFrame", updatebullet);
		-- Bookmark B: You'll be adding some code here later too
	end
end

Runtime is a special top-level object in Corona. Just as you added an event listener to buttons for when the user taps on them, you can add an event listener to the runtime when a frame is about to be drawn.

Note the two spots in the code above marked “Bookmark A” and “Bookmark B”; you’ll be adding elements to the gameListeners function as you go along, so keep an eye out.

Relaunch the app in the simulator! :]

After you tap on the “Play” button, tap on the paddle, and the bullet will go bouncing around the screen. You’ve added quite a bit of complicated physics here, without adding a lot of code!

Looks like you’re fully armed — now you just need something to shoot at! :]

Where Are All The Zombies?!

You came to Atlanta looking to participate in the zombie apocalypse — but where are the zombies? What a bust!

Time to add the zombies! First, here’s a quick checklist of how the zombies should behave — besides die when you shoot them, of course! :]

  1. Add zombies to level 1.
  2. Place them on top of the background image.
  3. Place them in rows and columns to make a wall of zombies.
  4. Enable physics on them so when they are hit, they make the bullet bounce off of them.

Paste the following into main.lua above the “main();” line and save:

function gameLevel1()
 
	currentLevel = 1;
 
	-- Place the zombies on the top layer
	zombies:toFront();
 
	-- Number of zombies on level 1
	local numOfRows = 2;
	local numOfColumns = 2;
 
	-- Zombie position on screen
	local zombiePlacement = {x = (_W) - (zombieWidth * numOfColumns ) / 2  + 20, y = 70};
 
	-- Create zombies based on the number of columns and rows we declared
	for row = 0, numOfRows - 1 do
		for column = 0, numOfColumns - 1 do
			local zombie = display.newImage("images/zombie.png");
			zombie.name = "zombie";
			zombie.x = zombiePlacement.x + (column * zombieWidth);
			zombie.y = zombiePlacement.y + (row * zombieHeight);
 
			-- Add physics properties to zombies
			physics.addBody(zombie, "static", {density = 1, friction = 0, bounce = 0});
			zombies.insert(zombies, zombie);
		end
	end
end

Most of the code above is just positioning the zombies on the screen. After you’ve set how many rows and columns you want, there’s a loop to make each zombie appear. You don’t want the zombies overlapping each other though, so the zombieWidth and zombieHeight variables from the beginning are used to space them out.

Reload the game in the simulator, and you should see some hordes of zombies descend on Atlanta!

Hooray, there are zombies to kill! If you’d like more or fewer zombies to appear on level one, simply change the number of rows and columns from 2 to whatever you’d like.

If you’re like most people, you won’t want to stand around in the face of a zombie apocalypse — you’ll want to move around a little! :] Move on to the next section to add some player movement!

I Like to Move It, Move It

The user will control the paddle by dragging it around with touch events, so you’ll need to connect some kind of touch event handler.

Paste the following code above “main();” in main.lua and save:

-- Player movement on user's drag
function movePlayer(event)
	if event.phase == "began" then
		moveX = event.x - player.x;
	elseif event.phase == "moved" then
		player.x = event.x - moveX;
	end
 
	if((player.x - player.width * 0.5) < 0) then
		player.x = player.width * 0.5;
	elseif((player.x + player.width * 0.5) > display.contentWidth) then
		player.x = display.contentWidth - player.width * 0.5;
	end
end

In the code above, the paddle should only move from side to side (in the X direction), so you’ll only set the player.x variable and leave player.y alone. The paddle should also stay within the boundaries of the screen, so there are checks against going too far to the left and to the right.

Now you need to find the appropriate place to call movePlayer. That sounds like another event listener! You need to call movePlayer when the user touches and drags the paddle around, so it would make sense to call it from the gameListener function you added above to control the movement of the bullet.

Remember those bookmarks you added previously? Here’s where you’ll use them!

Paste the following line above Bookmark A in the gameListeners function:

		player:addEventListener("touch", movePlayer);

And then paste this line above Bookmark B in the gameListeners function:

		player:removeEventListener("touch", movePlayer);

Now, relaunch the app in the simulator! Start the game, and you should be able to drag the paddle around. You might notice that the bullet doesn’t bounce off the paddle yet — you won’t kill too many zombies that way! :]

Time to add some collision physics to your paddle!

Rubber Bullets

Paste the following function under the movePlayer function in main.lua and save:

-- Determines bullet movement by where it hits the player
function bounce()
	velocityY = -3
	if((bullet.x + bullet.width * 0.5) < player.x) then
		velocityX = -velocityX;
	elseif((bullet.x + bullet.width * 0.5) >= player.x) then
		velocityX = velocityX;
	end
end

This is another instance where the velocityX and velocityY variables come into play. The bullet needs to change its Y direction to move up towards the zombies. However, the X direction (side to side) will depend on whether the bullet hits the left or right side of the paddle.

Time to add our event listener! :] Paste this line above Bookmark A in the gameListeners function:

		player:addEventListener("collision", bounce);

And paste this line above Bookmark B in the gameListeners function:

		player:removeEventListener("collision", bounce);

Relaunch the app in the simulator and start the game! :]

You should now be able to drag the paddle back and forth across the screen, and the bullet should bounce off. Those pesky zombies are still invincible though!

Enough with the rubber bullets — time to get some real firepower behind that bullet!

Killing Zombies and Padding Stats

Remember how you used the velocityX and velocityY variables for the bullet and paddle collisions? You’ll need to do that for the zombies as well!

Add the following code to main.lua before “main();”.

-- Zombies are exterminated, remove them from screen
function zombieDestroyed(event)
 
	-- Where did the bullet hit the zombie?
	if event.other.name == "zombie" and bullet.x + bullet.width * 0.5 < event.other.x + event.other.width * 0.5 then
		velocityX = -velocityX;
	elseif event.other.name == "zombie" and bullet.x + bullet.width * 0.5 >= event.other.x + event.other.width * 0.5 then
		velocityX = velocityX;
	end
 
	-- Ricochet the bullet off the zombie and remove him from the screen
	if event.other.name == "zombie" then
		-- Bounce the bullet
		velocityY = velocityY * -1;
		-- Zombie says "ow" when hit by a bullet
		audio.play(zombieKill);
		-- Remove zombie instance
		event.other:removeSelf();
		event.other = nil;
		-- One less zombie
		zombies.numChildren = zombies.numChildren - 1;
 
		-- Score
		score = score + 1;
		zombieKillNum.text = score;
		zombieKillNum:setReferencePoint(display.CenterLeftReferencePoint);
		zombieKillNum.x = 150;
	end
 
	-- Check if all zombies are destroyed
	if zombies.numChildren < 0 then
		textBoxScreen("City: Zombie Free", "Next City");
		gameEvent = "win";
	end
end

In the code above, when a Zombie-bullet collision occurs, the bullet needs to bounce off properly by modifying the velocityY variable.

If zombies could talk while being shot, they’d probably say “Ow!”, so add that sound effect in with the collision.

The zombie should then disappear forever by removing itself from the collection of zombies, never to be heard from again.

As well, when the bullet hits a zombie, add 1 to the zombies killed score; if all the zombies are gone, show text saying the city is now zombie free!

You’re almost done — you just need to add this function to the listener events! Paste this line above Bookmark A in the gameListeners function:

		bullet:addEventListener("collision", zombieDestroyed);

And paste this line above Bookmark B in the gameListeners function:

		bullet:removeEventListener("collision", zombieDestroyed);

That’s it! Just for completeness, here’s what the final gameListeners function should look like:

-- Listen for bullet and player collisions and user dragging player
function gameListeners(event)
	if event == "add" then
		Runtime:addEventListener("enterFrame", updatebullet);
		player:addEventListener("touch", movePlayer);
		player:addEventListener("collision", bounce);
		bullet:addEventListener("collision", zombieDestroyed);
	-- Remove listeners when not needed to free up memory
	elseif event == "remove" then
		Runtime:removeEventListener("enterFrame", updatebullet);
		player:removeEventListener("touch", movePlayer);
		player:removeEventListener("collision", bounce);
		bullet:removeEventListener("collision", zombieDestroyed);
	end
end

Note:The order of the four event listeners doesn’t matter, so don’t worry if you pasted them in a different order.

Relaunch the game in the simluator and give it a try!

Now when you click on the player paddle, the bullet shoots outwards and upwards, you can drag the paddle back and forth, the bullet bounces off the walls, it kills the zombies and you get a high score! All of your hard work has paid off!

Uh…wait a second. When you win nothing happens — and when you lose nothing happens! Somewhat less than gratifying, isn’t it? :]

Why did this happen? You’re calling the textBoxScreen function to display a message when you win or lose, so where is it?

Right — you never actually created that function yet, you just referred to it in the code! Tapping on the resulting text box should also take you to the next level. You’ll fix that in the next section!

I’m Back in the New York Groove

Before you create the text box that will take you to New York City after you kill all of Atlanta’s zombies, first create the New York City level.

Paste the following into main.lua above the “main();” line:

-- Level 2 zombies
function gameLevel2()
 
	currentLevel = 2;
	bg1.isVisible = false;
 
	-- This code is the same to gameLevel1(), but you can change the number of zombies on screen.
	zombies:toFront();
	local numOfRows = 2;
	local numOfColumns = 8;
 
	-- Zombie position on screen
	local zombiePlacement = {x = (_W) - (zombieWidth * numOfColumns ) / 2  + 20, y = 100};
 
	-- Create zombies based on the number of columns and rows we declared
	for row = 0, numOfRows - 1 do
		for column = 0, numOfColumns - 1 do
			local zombie = display.newImage("images/zombie.png");
			zombie.name = "zombie";
			zombie.x = zombiePlacement.x + (column * zombieWidth);
			zombie.y = zombiePlacement.y + (row * zombieHeight);
 
			-- Add physics properties to zombies
			physics.addBody(zombie, "static", {density = 1, friction = 0, bounce = 0});
			zombies.insert(zombies, zombie);
		end
	end
end

The gameLevel2() function is almost identical to gameLevel1(). The currentLevel is now 2 rather than 1, and the Atlanta city background needs to be hidden away. In true Breakout fashion, this level is slightly more difficult, with 2 rows and 8 columns’ worth of zombies.

Before advancing to level 2, you’ll need to clean up the screen and reset the bullet and paddle.

Paste the following into main.lua above the “main();” line and save.

function cleanupLevel()
	-- Clear old zombies 
	zombies:removeSelf();
	zombies.numChildren = 0;
	zombies = display.newGroup();
 
	-- Remove text Box
	textBox:removeEventListener("tap", restart);
	textBoxGroup:removeSelf();
	textBoxGroup = nil;
 
	-- Reset bullet and player position 
	bullet.x = _W;
	bullet.y = player.y - 30;
	player.x = _W;
 
	score = 0;
	zombieKillNum.text = "0";
end

This function cleans up everything you created so far – it removes all the zombies, removes the text box and listener, and resets the bullet position, player position, and score.

Now you’ll need the code to advance the player to level 2.

In level 2, you’ll display a background image of New York City, which will be centered on the screen. In the event that the player was on level 2, lost, and reset the level to try again, you’ll need to remove any left over zombies from memory. Finally, place the player and bullet on the screen in their original starting point.

Does this sound really similar to the level 1 init code? It sure does! :]

Paste the following into main.lua above the “main();” line and save.

-- New York City (Level 2)
function changeLevel2()
 
	-- Display background image and move it to the back
	bg2 = display.newImage("images/nyc.png", 0, 0, true);
	bg2.x = _W;
	bg2.y = _H;
	bg2:toBack();
 
	-- Reset zombies 
	gameLevel2();
 
	-- Start
	player:addEventListener("tap", startGame)
end

New York City is waiting for you to solve their zombie problem; you just need a way to get there. Maybe you can fly there on a magic text box! :]

No Texting in the Zombie Apocalypse

It’s now time to create the text box that gives the user feedback and lets them retry a level or move onto the next. Here’s the checklist for the text box functionality:

  1. Display the text box image in the images folder.
  2. Display text that lets the player know if they won or lost.
  3. Display text that lets the player retry the level they lost, or move onto the next level if they won.
  4. Act as a button to reset or change levels.

Sounds easy enough! But it would be even easier to put all of those elements into their own group. Perhaps the textBoxGroup variable you created in the beginning would be helpful right about now! :]

Paste the following into main.lua and save:

function textBoxScreen(title, message)
	gameListeners("remove");
 
	-- Display text box with win or lose message
	textBox = display.newImage("images/textBox.png");
	textBox.x = 240;
	textBox.y = 160;
 
	-- Win or Lose Text
	conditionDisplay = display.newText(title, 0, 0, "Arial", 38);
	conditionDisplay:setTextColor(255,255,255,255);
	conditionDisplay.xScale = 0.5;
	conditionDisplay.yScale = 0.5;
	conditionDisplay:setReferencePoint(display.CenterReferencePoint);
	conditionDisplay.x = display.contentCenterX;
	conditionDisplay.y = display.contentCenterY - 15;
 
	--Try Again or Congrats Text
	messageText = display.newText(message, 0, 0, "Arial", 24);
	messageText:setTextColor(255,255,255,255);
	messageText.xScale = 0.5;
	messageText.yScale = 0.5;
	messageText:setReferencePoint(display.CenterReferencePoint);
	messageText.x = display.contentCenterX;
	messageText.y = display.contentCenterY + 15;
 
	-- Add all elements into a new group
	textBoxGroup = display.newGroup();
	textBoxGroup:insert(textBox);
	textBoxGroup:insert(conditionDisplay);
	textBoxGroup:insert(messageText);
 
	-- Make text box interactive
	textBox:addEventListener("tap", restart);
end

This function places a text box sprite near the center of the screen, and sets up some labels on it for the title and message (passed in). It also sets up an event listener to wait for taps (which will call the restart function when one occurs, which you haven’t written yet).

Relaunch the app in your simulator, and make your way to level 2!

Now you’ll see two different feedback text boxes depending on whether you lost or beat the level.
But wait — there’s no way to get rid of the textbox once it’s on screen! It sounds like you’ll need one more function to wrap up this game.

One Function to Rule them All

The final chunk of code you’ll need will determine what to do if the player has won or lost. It’s checklist time:

  1. If the player wins level 1, go to level 2.
  2. If the player wins level 2, tell them they completed the game.
  3. If the player loses on level 1, reset the score and replay level 1.
  4. If the player loses on level 2, reset the score and replay level 2.
  5. If the player completes the game, congratulate them and clean up event listeners.

Judging by the checklist, it’s fair to assume you’ll need a few if/then statements. Paste the following into main.lua above “main();” and save:

-- See if the player won or lost the level
function restart()
	-- If the player wins level 1, then go to level 2
	if gameEvent == "win" and currentLevel == 1 then
		currentLevel = currentLevel + 1;
		cleanupLevel();
		changeLevel2();
		levelNum.text = tostring("NYC");
 
	-- If the player wins level 2, tell them they won the game
	elseif gameEvent == "win" and currentLevel == 2 then	
		textBoxScreen("  You Survived!", "  Congratulations!");
		gameEvent = "completed";
 
	-- If the player loses level 1, then make them retry level 1 and reset score to 0
	elseif gameEvent == "lose" and currentLevel == 1 then
		cleanupLevel();
		changeLevel1();
 
	-- If the player loses level 2, then make them retry level 2 and reset score to 0
	elseif gameEvent == "lose" and currentLevel == 2 then
		cleanupLevel();
		changeLevel2();
 
	-- If the game has been completed, remove the listener of the text box to free up memory
	elseif gameEvent == "completed" then
		textBox:removeEventListener("tap", restart);
	end
end

This checks to see if the player has won or lost and sets does the appropriate thing (usually changing levels, or restarting the game).

Relaunch the app and start killing those zombies!

And there you have it, a zombie breakout game in two major cities! Look at you, you’re now a professional zombie hunter! :]

Where to Go From Here?

Here is the completed source code for the tutorial.

There’s a lot more you can do to make this game more exciting:

  • Add more levels and cities with more zombies.
  • Add different types of zombies.
  • Add animated sprite zombies.
  • Add power ups to the bullet.

In addition, if you want to learn more about Corona, there are two more tutorials about Corona on this site that you might enjoy:

Hopefully you will create your own fun Corona games in the future! And if you have any questions or comments about this tutorial or Corona in general, please join the forum discussion below!


This is a post by Tutorial Team Member Greg Pugh, author of the Colin Turtle children’s eBook app series. You can also find him on .

Greg Pugh

Greg is a mobile app developer, animator, and Flash developer for his company GP Animations. He's also a children's book author and illustrator. When Greg isn’t writing children’s books, he’s playing guitar, watching hockey, and enjoying a dram of scotch with a cigar.

User Comments

11 Comments

  • Thanks for the article! I plan to use Corona for my my next game and this will help a lot!
    samuel
  • Why is it that the walls on the left and right don't seem to match up with the edge of the screen when you look at this on an android phone in the simulator?
    livewire1407
  • livewire1407 wrote:Why is it that the walls on the left and right don't seem to match up with the edge of the screen when you look at this on an android phone in the simulator?


    This is because the app is created for an iPhone-sized screen, with "letterbox" chosen in the config.lua file. The reason the walls of the game don't line up with the edges of the Android display is because the app is being letterboxed.

    In config.lua, you can change scale="letterbox" to scale="zoomStretch" to see the difference. While not the best choice for the artwork, as it was designed for an iPhone, it will solve the problem without too many concerns. If you were to use "zoomEven" instead, the paddle would be pushed off of the thinner Android screen.
    GPAnimations
  • @Greg There is an error in the code playBtn:addEventListener("tap", loadGame); . You never created a loadGame function and therefore corona will throw an error and won't compile. I fixed it by simply adding
    function loadGame()
    --code here
    end
    gabethegeek
  • So, it's not really an error, but you add the code without creating the function an tell us to compile before actually creating the function in the next step. So I think you should reverse those steps so people ill not be confused.
    gabethegeek
  • sir please give a sample how to create a bonus in brick breaker.and please give me because i'm doing a thesis now please help me.because this 1st week of february final defense of my game.please give now.and i'll very thankful to you.
    cyrille
  • sir please give a sample how to create a bonus in brick breaker.and please give me because i'm doing a thesis now please help me.because this 1st week of february final defense of my game.please give now.and i'll very thankful to you.
    cyrille
  • test
    trev
  • Hi,
    This is a very well explained tutorial.
    I am a newbie, I have completed the Hello World and similar beginner tutorials.
    I decided to follow this game due to the excellent explanation.
    However after completing all the copy and paste (along with my extended comments so I can follow it later)
    it appears the code is not compatible with corona 2013.2100 build which uses corona 2 sdk

    There is one line which causes problems on first zombie kill and that is the call to
    zombieKillNum:setReferencePoint(display.CenterLeftReferencePoint);
    in function zombieDestroyed().

    The following error shows in corona simulator output:
    ERROR: object:setReferencePoint() is only available in v1Compatibility mode. Use anchor points instead.

    If this is commented out it breaks again with the same error:
    conditionDisplay:setReferencePoint(display.CenterReferencePoint);
    &
    messageText:setReferencePoint(display.CenterReferencePoint);
    in textBoxScreen function.

    It would be great for learning if the author or someone with better knowledge than me could advise me how to modify the code for anchor points
    on these lines.

    Thank you in anticipation of any reply.
    trev
  • This tutorial was written well before Corona a Labs introduced Graphics 2.0. To fix any errors, you'll need to add:

    graphicsCompatibility = 1

    To config.lua. Please see the following documentation for more information: http://docs.coronalabs.com/guide/graphi ... on_v1.html
    GPAnimations
  • please!!!HELP i'm trying to make a simular game and i in the line "score = score + 1" corona says me that it is the "nil value" ,what should I do ???
    vic02

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How to Make a Simple 2D Game with Metal.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Bill Kastanakis

... 53 total!

Update Team

... 14 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!