How To Make a Game Like Doodle Jump with Corona Tutorial Part 1
This is a blog post by iOS Tutorial Team member Jacob Gundersen, an indie game developer who runs the Indie Ambitions blog. Check out his latest app – Factor Samurai! I’m willing to bet that when you woke this morning your first thought was “I wish there was a way to create a doodle jump […] By Jake Gundersen.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!Create account
Already a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!Create account
Already a member of Kodeco? Sign in
How To Make a Game Like Doodle Jump with Corona Tutorial Part 1
- What is Corona?
- Corona vs Cocos2D
- Getting Started with Corona
- Creating the Sprite Sheet
- Creating the Level with LevelHelper
- Adding the Player
- Loading the Level
- Adding Collisions and Jumping
- Event Listeners in Corona
- Additional Collisions
- Creating a Shooting Animation
- Adding the Arms
- Where To Go From Here?
This is a blog post by iOS Tutorial Team member Jacob Gundersen, an indie game developer who runs the Indie Ambitions blog. Check out his latest app – Factor Samurai!
I’m willing to bet that when you woke this morning your first thought was “I wish there was a way to create a doodle jump game in 300 lines of code.”
“Oh, and I want it to run on both iOS and Android, and I want the code to be really simple. And I want a cherry on top!”
Well, you’re rather greedy, but you’re in luck. Wish granted! :]
In this 2-part tutorial series, we’ll make this game with a popular and easy-to-use game engine for iOS and Android called Corona. By going through this tutorial series you’ll learn the following:
- How to make a game with Corona
- The pros/cons of Corona vs Cocos2D
- How to make levels with LevelHelper
- And of course, how to make a game like Doodle Jump!
This tutorial is for complete beginners to Corona, although it’s OK if you have some prior experience.
It’s also best if you have prior experience with LevelHelper and SpriteHelper – if you are new to these tools, check out this tutorial first. But don’t worry if you don’t have these tools or don’t have much experience – you can use the premade sprite sheets and levels if you’d like.
OK, so grab a Corona (or two) and let’s begin! :]
What is Corona?
Although most of you on this site are probably familiar with the Cocos2D game engine (or the Corona beer), you might not be familiar with the Corona game engine yet.
So let’s start with what Corona is!
Corona is cross platform game engine that supports Android and iOS. It’s developed by a company named Ansca, and you can use a free trial to try out Corona and develop your game. When you are ready to publish you are required to buy a license, prices range from $199 to $349.
You program in Corona with a programming called Lua, which is a lightweight and easy to use scripting language. There is no IDE for Corona (such as Xcode), instead you often program with a normal text editor. Corona comes with a simulator you can use to test your game as you’re coding it.
Corona has built-in APIs for the normal game programming aspects (sprites, sounds, etc.) as well as various APIs from the Apple including Game Center Leaderboards (new), In App Purchase, and TableViews (new).
There are also a number of third party developer APIs including Open Feint (leaderboards), inMobi (ads) and Flurry (analytics). It does not have access to all of Apple’s APIs. Some of the notable APIs not included in Corona include iAds, Game Center Multiplayer, and access to bluetooth connectivity.
Corona vs Cocos2D
Next let’s cover the differences between Corona and Cocos2D so you guys can understand the differences between each engine.
First of all, Corona and Cocos2D have many similarities:
- Positioning a sprite in Corona is easy, just set the .x/.y properties of the sprite.
- There are scale, rotation, and alpha properties associated display objects.
- There’s a ‘transition.to’ method that animates position, scale, rotation, etc.
- Corona uses the Box2D physics engine, so the methods and properties should be familiar to you if you’ve used Box2D with Cocos2D.
- Like Cocos2D, Corona has sprites, text labels, and primitive drawing methods, but they’re called by different names. Any object that appears on screen is called collectively a ‘display object’, which is similar to a CCNode in Cocos2D. Corona has a display group, which is functionally very similar to a CCLayer in Cocos2D.
There are some significant differences between the two engines:
- A Cocos2D program is written using Objective-C, while a Corona program is written using Lua. The Corona API is heavily influenced by Action Script. Flash developers find Corona a very welcome environment. Corona code can be written according to an OOP model, but the functional programming model is more common.
- Cocos2D is open source allowing the integration of 3rd party libraries written in Objective-C. Corona is a proprietary, closed system. While some 3rd party libraries have been written in Lua for use with Corona, there are currently far fewer 3rd party tools available for Corona.
- And of course, Cocos2D is free while Corona is paid.
So I know what you’re all wondering – which one should you choose when? The best way to answer this is to list the pros and cons of using Corona instead of Cocos2D:
- Rapid development time. Making a game with Corona can often be faster and easier than making a game with Cocos2D. Since it’s based on Lua, adding a variable to an object doesn’t require any more than the assignment (ex: Sprite.newvalue = 0). The Box2d engine is integrated so that a sprite object and a physics body are one object. You don’t have to write interface files, declare instance variables, or even declare variable data types (there are a few exceptions to this).
- Cross platform support. If you make a game with Corona, it works on both iOS and Android out of the box!
- Lack of API support. Corona doesn’t have access to all of Apple’s APIs, or as many 3rd party extensions as Cocos2D. Some of the areas where Corona is still lacking is easy data persistence (there are methods to write/read files, and Sqlite integration, but nothing as slick as reading a plist into a dictionary). Network multiplayer code has to be written from scratch. The coolest new stuff from Apple takes time before it’s integrated, if at all.
- Slower performance. Since Corona is built on top of Lua, your code will not run as fast as it would if it was straight Objective-C.
- It’s not free. Yep, you gotta cough up money to use Corona when you want to ship your game, whereas Cocos2D is gloriously free!
Whether or not you’re convinced Corona is for you, going through this tutorial is still a good idea so you can judge for yourself which you like better! :]
Getting Started with Corona
If you don’t have Corona installed already, start by registering with Anscamobile and downloading a trial of Corona. The trial is fully functional, the only thing you are unable to do is publish to the app store. Visit the Corona subscription page to register/download (click the “download trial” at the bottom).
I’ll be doing all my instructions based on using Corona on a mac. But keep in mind you can also use Corona on a PC. However, you cannot build iOS builds from a PC, only Android builds. If you’d like to know more about how to develop for Android on a PC, you can refer to anscamobile here.
When your finished installing, you should have a Corona folder in your Applications folder. Inside that folder are a number of different executables. I recommend always running Corona by executing the Corona Terminal executable. This will open a terminal window along with the Corona Simulator. The terminal window will give you a log of any error messages or print() statements.
As I mentioned earlier, Corona doesn’t have a dedicated IDE. I write most of my code in a simple text editor. While there is a command line debugger, I haven’t found that very helpful. You can use Xcode as a text editor and there are code highlighting plugins for Corona, but they don’t work in all versions of Xcode.
Open up a new text file in your preferred text editor now and add the following code:
display.newText( "Hello World", 20, 160, "Helvetica", 50 )
As you can probably guess, this displays a line of text saying “Hello World” to the screen, at X=20 and Y=30, in a 50-point Helvetica font.
Save your one line text file. The file name should be ‘main.lua’, this is the entry point for any Corona program.
Open up the Corona Simulator, either by executing the Corona Simulator.app itself, or as I recommended by executing the Corona Terminal. Go to File->Open, navigate to your file. Both of these files are in the Corona folder in your applications folder.
Congratulations, you are now a Corona developer. Can it get any easier than this?!
FYI, in order to build for the device in Corona, you will need an Apple Developer account and a development provisioning profile (not a distribution profile, you can only build with a distribution profile if you’re a paid subscriber). If you load a program in Corona, and go to File->Build->iOS you will get a dialogue box to allow you to build a .app file that you can install on your device through Xcode organizer or iTunes.
Creating the Sprite Sheet
Before we proceed with our Doodle Jump game, we have to build a sprite sheet. Since we’re going to use LevelHelper to create the levels for the game, we’ll use SpriteHelper to create the sprite sheet.
If you don’t have SpriteHelper, don’t worry – you can grab the finished Sprite Sheet from the resources for this project and continue on to the next section.
But if you do have SpriteHelper and want to follow along, kepe reading.
You’ll also need the resources for this project, so go ahead and download and unzip it. Inside the sprites folder you’ll see the art we’re going to use for this game – a free art pack made by Ray’s wife Vicki.
We are going to need to set up a number of physics attributes. I like to set up these attributes in SpriteHelper, but all the properties are available and can be set in LevelHelper later on. In this tutorial, I’ll show you how to set the attributes in LevelHelper.
I’m not going to go into great detail explaining how to use the basic features of SpriteHelper/LevelHelper as those topics were covered in the SpriteHelper/LevelHelper tutorial.
Start up SpriteHelper, and drag all of the sprites into the window. Uncheck crop and hit pack sprites.
Choose File->Save, choose the directory your main.lua is in, and enter ‘cloudSprites’ for the name – the necessary extensions will be automatically added. It will create three files for you – cloudSprites-hd.png, cloudSprites.png, and cloudSprites.pshs (the SpriteHelper project).
Creating the Level with LevelHelper
Next we’ll use LevelHelper to create the level for the game. Again, don’t worry if you don’t have LevelHelper – you can grab the level I made from the resources for this project and continue on to the next section.
If you do have LevelHelper, open it up and click the plus in the Project group. Type in cloudJumper, choose ‘iPhone Portrait (320×480)’ and click ‘Create New Project’.
At the bottom of the LevelHelper window find the Game World Size boxes and change the values to 0, 0, 320, 9600. Also, put these values in the Physic Boundaries boxes as well. Set the gravity to 0, -10.
Click the plus next to the scene chooser drop down on the far right side. Choose the cloudSprites.pshs file you just created in SpriteHelper. You should now have all your individual sprites loaded in the sprite pane.
You can drag the level around by holding the control key down while clicking a dragging. Go to the very bottom of the level.
The first thing we’re going to do is load all the background clouds. Drag the three clouds, bg_cloud1, bg_cloud2, and bg_cloud3 into the bottom section. Select all three sprites. You can do this in the GUI or in the ‘Sprites in Level’ pane.
Set the physics type of the clouds to ‘No Physic.’ Set the Z order property to -2. We want these clouds to always be in the far background.
Using the clone and align tool, make 19 or so clones of all three cloud sprites. You can set the Y offset to -480 to place one set of clouds for each screen. Go through the screens and lay out the clouds, add some variety, make it look good.
When you’re finished, select all the cloud sprites and click the lock button. When a sprite is locked, it cannot be selected from the layout view. This makes it easier to position other sprites on top of it. If you need to edit a locked sprite, select it in the ‘Sprites in Level’ list and click the same button to unlock it.
Now drag the cloud1, cloud2, and cloud3 sprites into the bottom of the level. Give these sprites the following physic attributes and tags:
A few things to note here, all the clouds have a z order of -1. That puts them in front of our background clouds, but behind everything else. The white and grey clouds are both sensors, this is how we’ll jump up through them, and bounce off only on our way back down. The blue cloud isnt’ a sensor, so we’ll actually have to jump around it, making it more difficult.
All the clouds have a category bit of 1. We want arrows and monsters to move through them without colliding, so we’ll set our mask attributes on those objects accordingly. The other attributes to make sure to set are physics type, shape border, and the TAG property. Don’t worry if your tag numbers are different than what is shown here, only the name matters.
If you have questions about what these properties are for, check the SpriteHelper/LevelHelper tutorial.
Once all the properties are set, use the clone tool to create as many of each kind of platform as you need. Lay out the different platforms to create a level that’s fun and interesting.
Here are a few level building tips:
- The level should start easy so the player can get used to the basics.
- The level should never repeat itself.
- The player jumps about 200 pixels max, so make sure that he has something to jump from at least that often.
- The grey clouds are red herrings, they will dissappear when landed on.
- The blue clouds are difficult because the player cannot jump through them, and they are narrow.
- Difficulty should build to a critical point towards the end of the level.
Here are the first several screens of my level:
If you’d like to use my level, it’s in the resources for this project, although you might find it more fun and good practice to make your own!
Also keep in mind the premade level is fully completed so it will already have all the sprites (monsters, player, TAGs) already set, so some of the LevelHelper sections later on in this tutorial will be already completed. If you’d like to follow along, but don’t want to spend a ton of time, just make a small level (maybe 960 pixels tall).
Adding the Player
Now that we have all the clouds included, we can introduce our player. Drag the char_jump2 sprite onto the bottom screen above one of the clouds. Give him the following properties:
The player is going to collide with cloud platforms, monsters, and with the level ending shape, so he needs a mask bit value of 5 (1 and 4).
Finally, we need to save our level and generate the code. Choose File-Save and name the file ‘level1’, the necessary extension will be added automatically. Also, choose File->Generate Code->Corona and that will create the LevelHelperLoader.lua file. This file needs to be regenerated every time we add a new TAG.
Place all these files in the same directory as your main.lua.
Loading the Level
Let’s get started with the code and we’ll return to the level design in a minute. Delete your current line of code in main.lua and replace it with the following:
physics = require("physics") physics.start() display.setStatusBar( display.HiddenStatusBar )
The main.lua file will execute from top to bottom. These first three lines will be executed first when we start the Corona Simulator. Let’s go over these line by line:
- Imports the physics engine code and assigns the engine to the object called physics.
- Starts the physics engine with the default gravity values. Corona uses the Box2D physics engine, so if you’ve used Box2D before most of the calls to physics will be familiar to you.
- Hides the status bar.
Now that are file is started and we have the physics running we are going to load the level we created with LevelHelper.
Let’s try this out. Add a new function to load the level as follows:
require("LevelHelperLoader") local function loadLevel() localGroup = display.newGroup() loader = LevelHelperLoader:initWithContentOfFile("level1.plhs") loader:instantiateObjectsInGroup(physics, localGroup) worldHeight = loader:getWorldBoundariesRect().size.height localGroup.y = -worldHeight + 480 player = newPlayer() end
The first line loads the LevelHelperLoader class (generated earlier from LevelHelper or in your resources folder), which is similar to #import in Objective-C.
The next line creates a function, which in Lua is done with the function keyword. Variables and functions in Lua are global in scope by default, if we want to limit the scope to the current function we would include the local keyword.
Corona calls its visual objects (sprites, drawn objects, layers) ‘display objects.’ Each of our sprites, background clouds, player, our platform clouds, etc, are display objects.
Our first line in the function creates a new display group, which in Corona functions much like a CCLayer in Cocos2D. We’re going to add all our level contents to this display group.
The second line creates the loader object and populates it with the contents of our level. We haven’t yet actually created the these objects yet, but we are ready to in the next line.
In Corona we only need a single call to LevelHelper that will create all our display objects and create the associated physics bodies. Corona physics bodies are automatically combined with the their sprites, this makes creating physics based games really easy.
The instantiateObjectsInGroup() call takes two parameters, our physics engine object and the display group to load all the display objects into. Once we’ve loaded the level into the display group, it will be visible on our screen.
Corona automatically adds any display object created to the screen. There is a parent display group that contains any instantiated display object.
Our display objects are now on screen and part of the display group called localGroup. However, localGroup is currently at y position 0, Corona’s coordinate system starts at the top left of the screen and increments towards the bottom, which means we’ll see the very top section of our level, or very end of the level.
We need to move the display group upwards, which we do in the next two lines. First we get the world size, which we set up in LevelHelper, then we set the localgroup.y to the negative world size, sliding it all the way up, minus one screen height.
Finally we create and initialize the player, and assign it to the player variable with the newPlayer() function call. We haven’t written this yet, so add this right above loadLevel:
local function newPlayer() local p = loader:spriteWithUniqueName("char_jump2") return p end
This just gets the sprite with the unique name “char_jump2” that we added to the level we created with LevelHelper, similar to how we did this in the How To Use SpriteHelper and LevelHelper Tutorial with Cocos2D.
One thing to note is that this sprite already has a physics body associated with it. So later on, we’ll have access to those calls that get and set physics properties.
Now that we have these two functions in place, let’s use them! Add the following line of code right after the function definition:
And that’s it! Save main.lua, and place it a directory along with LevelHelperLoader.lua, cloudSprites.png, cloudSprites-hd.png, cloudSprites.pshs, and level1.plhs. Then load main.lua in the Corona Simulator.
You should then see some clouds and your character fall down off the screen:
Congratulations, you have written your first Corona program complete with character and platforms!
Lets move on and make the character jump off the clouds!
Adding Collisions and Jumping
To add jumping, modify the newPlayer function to add a collision handler like so:
local function newPlayer() local p = loader:spriteWithUniqueName("char_jump2") local function pCollision(self, event) object = event.other if event.phase == "began" then vx, vy = self:getLinearVelocity() if vy > 0 then if object.tag == LevelHelper_TAG.CLOUD then self:setLinearVelocity(0, -350) end end end end p.collision = pCollision p:addEventListener("collision", p) return p end
The second line creates our collision callback function. The second parameter contains information about the collision event (full details here).
Self is the player object, and other is an event type. Event.other is the object that the self collided with, and so we’ll want to use that later, we store it in the object variable.
Next, we also want the jump to fire only once. Collisions can be noisy and fire multiple times, we only want a single jump action per cloud collision, so we are only going to be testing for this on the ‘began’ phase.
Then we test to see if we should jump. We get the linear velocity of the self, or player, and we test that it’s greater than 0, because we only want to jump on our way back down, not as we float up through the cloud.
Finally, we are going to look at what we collided with. We want to jump only off white clouds, so we need to check the tag property of the object we collided with.
If all these conditions are met, we will set linear velocity of the self to -350 or up with a speed of 350. We use setLinearVelocity (instead of applyLinearImpulse) here because no matter how much momentum we have gained falling down, we want the jump to be the same height back up.
Finally, we are going to set the function we just created to be the collision callback for our new p, or player, object. The next two lines accomplish this. With collision callbacks, we have to set the .collision property and add an event listener to the object.
Go ahead and open the project once again in the Corona Simulator. Your player should now be boucing off the white clouds!
I generally keep the Corona Simulator open between builds, and use the key binding Command-R to reload the project after saving the file. Corona will also detect that the main.lua has been updated and prompt you for a relaunch.
Event Listeners in Corona
This might be a good time to give a general explanation of event listeners in Corona.
You’ve actually already seen an example above with the collision event listener, but let’s talk about exactly how they work.
Event listeners are Corona’s callback methods. There are event listeners that handle touch and accelerometer input, changes in orientation, input from the GPS system, collisions, exiting or suspending the application, etc.
There are two kinds of event listeners:
- Events that are broadcast to all objects. These are known as runtime events, and include things like orientation changes or input from the GPS. Another example is the enterFrame listener, which is an event that is called every time a frame is drawn.
- Events that are sent to a single object. These include touch or collision events (like the one you used earlier).
In our case, we created a collision function, the pCollision function. We registered the event listener with the player, so when the player collides with another object, this object will be called. The function has two arguments, the self object, which is the player in this case, and the event object. The event object is created and populated with information by the collision event type.
Different event listeners have different event objects that provide different information. For example, there is also a ‘postCollision’ event type that provides collision forces. The regular collision event object doesn’t have this information.
For more information about Corona’s event types, go here.
Now that are player is jumping off white clouds, lets add the grey and blue clouds. Blue clouds actually require no additional code. They share the CLOUD tag with the white clouds and are static bodies (meaning that you must jump around them rather than through them like the white clouds).
Go into the pCollision function once again and add extra code so it looks like this:
if event.phase == "began" then vx, vy = self:getLinearVelocity() if vy > 0 then if object.tag == LevelHelper_TAG.CLOUD then self:setLinearVelocity(0, -350) elseif object.tag == LevelHelper_TAG.BCLOUD then loader:removeSpriteWithUniqueName(object.uniqueName) end end end
If the tag is BCLOUD, then we want the grey cloud to disappear, so we call loader:removeSpriteWithUniqueName and we pass in the name of the object that we’ve collided with. Remember that here in our code, “object” is a variable where we stored event.other.
Creating a Shooting Animation
Love is in the air – and soon to be arrows as well! :]
But first things first – let’s create an animation for the shooting in LevelHelper.
Switch back to LevelHelper, select the animation tab on the right, and click the ‘New’ button to create a new animation. It will bring up the animation builder dialogue. Add the sprites called front_arm, arm_front_shoot1, arm_front_shoot2, and an additional front_arm to the list by selecting them and clicking the plus button. Untick the loop option. The default values are fine on the remaining attributes.
Click ‘Create Animation’ to save. Rename the animation to ‘shoot’ by double clicking on it.
Drag the animation object onto the grey area, outside of the level. Anywhere is fine. We need this available to our code, but we are going to position the arms around the player in code, so we don’t need then inside the level here. If we don’t drag them into our level, we can’t get a pointer to them using the levelHelper loader object.
Once you’ve dragged it into the level, select the animation object and set the Physics type to ‘No Physic’ to avoid strange physics behavior when we attach it to the hero.
Adding the Arms
Click on the sprite button to go back to the list of sprites. Drag in the ‘back_arm’ sprite outside of the level as well. Set the new animation (which is called ‘front_arm’ because that’s the first sprite in the set) and ‘back_arm’ to ‘No Physic.’
Change the code in the newPlayer function so that it looks like this:
local backarm = loader:spriteWithUniqueName("back_arm") local p = loader:spriteWithUniqueName("char_jump2") local frontarm = loader:spriteWithUniqueName("front_arm") loader:pauseAnimationOnSprite(frontarm)
Here we just get variables for each of the player pieces. In LevelHelper these method calls will get us a variable we can use to refer to these objects. The objects are created automatically and added outside the level by levelHelper, but we need a variable to refer to them.
The loader:pauseAnimationOnSprite(frontarm) line keeps the animation from running when we start the game. We will call for that animation to run each time we shoot, but the default behavior is for the animation to run automatically on startup.
The player will be inside the view but his arms will be outside. To fix that we’ll create an enterFrame function.
An enterFrame event type fires every time the screen is drawn. This function will reposition the arms and bow with our player each frame. It will also cause the player to face the direction he’s moving and wrap around the screen if he falls off the edge.
Add the following code inside the newPlayer() function at the end before the line ‘return p’:
function p:enterFrame(event) backarm.x = player.x backarm.y = player.y frontarm.x = player.x frontarm.y = player.y if self.x < 0 then self.x = 320 end if self.x > 320 then self.x = 0 end px, py = player:getLinearVelocity() if px < 0 then frontarm.xScale = -1 backarm.xScale = -1 self.xScale = -1 elseif px > 0 then frontarm.xScale = 1 backarm.xScale = 1 self.xScale = 1 end end
Most of this code should be easy to follow.
We declare the function with the p: prefix. A function declared with an object:functionName declaration gives us access to a ‘self’ variable which refers to the object we declared the function on. When we create an enterFrame listener on a specific object, it must be called ‘enterFrame’ and is case sensitive.
The last section is recording the linear velocity of the player in the px and py variables. We then use this information to set the xScale property of all three. Like most other engines, a negative value in the scale property flips the sprite.
Next we need to add the enterFrame event listener to the Runtime object:
Runtime is a system object created by Corona. We create global listeners by registering them with this object.
If you save and run now, your player will have arms. Armed and Dangerous . . . Yay!
Where To Go From Here?
Here is the example project with all of the code from the tutorial so far.
Check out part two of the tutorial, where we’ll be shooting arrows, moving the screen, and creating monsters in no time! :]
If you’d like to know more about Corona in the meantime, you can look at the following resources:
- Anscamobile has a number of tutorial videos including, How to create a physics game in 8 minutes.
- Aother great source of Corona video tutorials are done by Dr. Raphael Fernandez, on YouTube.
- There’s also an exhaustive resource of Corona tutorials here.
Also, if you have any questions or comments about this tutorial, please join the forum discussion below!
This is a blog post by iOS Tutorial Team member Jacob Gundersen, an indie game developer and co-founder of Third Rail Games. Check out his latest app – Factor Samurai!
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development — plans start at just $19.99/month! Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.Learn more