Professional Documents
Culture Documents
It
supports the vast majority of the features necessary to make almost any 2D-based game, it even
includes a fully-featured physics engine!
As part of learning Cocos2D for Android I followed Ray Wenderlichs tutorials for the iPhone port of
Cocos2D. Of course were dealing with Android here, so here is his tutorial recreated for Android.
Ray deserves all credit for this tutorial the tutorial is originally his, this is merely a port to Android.
Credit should also go to Sketchydroide for his basic template for Cocos2D on Android.
Now you need to copy the cocos2d-android.jar file into the libs folder of your project. If the folder
doesnt exist, create it.
Go back to Eclipse, right-click on your project and select Build Path/Add External Archives.
Browse to where you saved the .jar file and select open.
Next download fps_images.png and put into the assets folder of your project. You are now setup
with Cocos2D!
Initial Setup
Next you need to put some code into your default activity (SimpleGame) so that you can start
making your game. At the top of the class add a protected field:
1 @Override
2 public void onCreate(Bundle savedInstanceState)
3 {
4 super.onCreate(savedInstanceState);
5
6 requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.La
7 getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManage
8
9 _glSurfaceView = new CCGLSurfaceView(this);
10
11 setContentView(_glSurfaceView);
12 }}
13
This sets up the OpenGL surface for Cocos2D to utilise. We set some flags to ensure we always
have a fullscreen view, then display the view to the user.
1
@Override
2 public void onStart()
3 {
4 super.onStart();
5
6 CCDirector.sharedDirector().attachInView(_glSurfaceView);
7
8 CCDirector.sharedDirector().setDisplayFPS(true);
9
CCDirector.sharedDirector().setAnimationInterval(1.0f / 60.0f);
10 }
11
This is the initial setup for Cocos2D. First we tell Cocos2D which surface to render to (the OpenGL
surface we set up earlier). We then ask Cocos2D to display the FPS and to run at 60fps. Note that
the 60fps is our animation interval, not the framerate of the application itself which is often limited by
the device.
1 @Override
2 public void onPause()
3 {
4 super.onPause();
5
CCDirector.sharedDirector().pause();
6 }
7
8 @Override
9 public void onResume()
10 {
super.onResume();
11
12
13 CCDirector.sharedDirector().resume();
14 }
15
@Override
16 public void onStop()
17 {
18 super.onStop();
19
20 CCDirector.sharedDirector().end();
}
21
22
23
These notify Cocos2D with whats going on with the device such as when the user has switched to
another application or the game is being stopped by the OS.
This is all the setup we really need. You can run the game now, but it wont display anything at this
early stage (we havent even set up a hello world scene!).
Adding a Sprite
Sprites are small images in 2D games that move about. These can be characters, projectiles or even
clouds. In this game well have three separate types of sprite: Player, target (enemy), and projectile.
First we need a graphic to use! You can either create your own, or use the tasty graphics provided
by Ray Wenderlichs wife: Player, Projectile, Target. Place your sprite graphics within the assets
folder of your project.
Now we need to place the sprite on the game screen. Cocos2D has an inverted coordinate system
to what youre used to the origin is the bottom left of the screen. So as X increases you head to the
right of the screen. As Y increases you go up the screen. Additionally by default the origin / anchor
point of sprites is in the centre. The coordinate system is the same regardless of the platform you
run Cocos2D on. The following graphic should help you visualise the coordinate system of Cocos2D:
Enough boring theory, lets get some code down! Add a new class to your project, call it
GameLayer and make it extend CCLayer. Add the following static method at the top of the class
declaration:
1
public static CCScene scene()
2 {
3 CCScene scene = CCScene.node();
4 CCLayer layer = new GameLayer();
5
6 scene.addChild(layer);
7
return scene;
8 }
9
Now add a default constructor:
1
protected GameLayer()
2 {
3 CGSize winSize = CCDirector.sharedDirector().displaySize();
4 CCSprite player = CCSprite.sprite("Player.png");
5
6 player.setPosition(CGPoint.ccp(_player.getContentSize().width / 2.0f, winSize.heig
7
addChild(_player);
8 }
9
At this point it may be worth a quick look at the earlier diagram to see exactly why weve chosen the
coordinates we have for the player.
Now before we can see this running, we first need to tell Cocos2D to run our new scene & layer. Go
back to SimpleGame.java and add the following code to the end of the onStart method:
Oh dear, the black character is barely visible on the black background! Never mind, we can modify
the background colour of the layer easily by inheriting from CCColorLayer instead of CCLayer:
Now when you run the application you should have a nice white background.
Moving Targets
While its cool to display a single ninja, because well, ninjas are cool we dont yet have much of a
game. What we really need are some targets for our ninja to throw stars at. While were at it we
might as well make them move to give them a fighting chance. What well do is create the targets off
the screen to the right, then have them move to the left of the screen at varying speeds. Add the
following method after the constructor:
1
2
3 protected void addTarget()
4 {
5 Random rand = new Random();
CCSprite target = CCSprite.sprite("Target.png");
6
7 // Determine where to spawn the target along the Y axis
8 CGSize winSize = CCDirector.sharedDirector().displaySize();
9 int minY = (int)(target.getContentSize().height / 2.0f);
10 int maxY = (int)(winSize.height - target.getContentSize().height / 2.0f);
11 int rangeY = maxY - minY;
int actualY = rand.nextInt(rangeY) + minY;
12
13 // Create the target slightly off-screen along the right edge,
14 // and along a random position along the Y axis as calculated above
15 target.setPosition(winSize.width + (target.getContentSize().width / 2.0f), actual
16 addChild(target);
17
// Determine speed of the target
18 int minDuration = 2;
19 int maxDuration = 4;
20 int rangeDuration = maxDuration - minDuration;
21 int actualDuration = rand.nextInt(rangeDuration) + minDuration;
22
23 // Create the actions
CCMoveTo actionMove = CCMoveTo.action(actualDuration, CGPoint.ccp(-target.getCont
24 CCCallFuncN actionMoveDone = CCCallFuncN.action(this, "spriteMoveFinished");
25 CCSequence actions = CCSequence.actions(actionMove, actionMoveDone);
26
27 target.runAction(actions);
28 }
29
30
The code is rather verbose to make it as easy to read & understand as possible. Near the bottom
weve introduced a new concept: Actions. Actions are a very accessible way of getting sprites to do
things without constant babysitting. You can get sprites to move, rotate, fade, jump, etc. We use
three different actions in this method:
CCMoveTo: The CCMoveTo action moves a sprite from its current position to a new one. In this
case were moving the sprite from just beyond the right edge of the screen to just beyond the left
edge of the screen (remember the origin of a sprite is the centre, hence why we divide the width
by two). The duration is specified in seconds between 2 and 4. As an exercise, try changing the
type from int to float so we arent restricted to whole seconds.
CCCallFuncN: The CCCallFuncN action allows you to specify a callback. The N means this
action allows you to specify a parameter.
CCSequence: This is a rather special action in that it doesnt do anything itself. Instead it lets
you run a sequence of actions, one after the other in a linear fashion. We use this so that we can
move the sprite, then when the movement has finished call our callback.
The eagle-eyed may have noticed that we have a callback, yet the method doesnt yet exist! We
better correct that right now, add the following method to our class:
Now we have code to create and animate targets, but we never call this code! What well do is
spawn a new target every second automatically giving the ninja plenty of shuriken practice. Add the
following line of code to the bottom of the constructor:
1 this.schedule("gameLogic", 1.0f);
Also add the following new method to the class:
Well use CCMoveTo to animate the projectile just like how we animated the targets. The problem is,
CCMoveTo requires a destination to move to, but we cant use the tap location since that means the
projectile would stop in the middle of the screen. What we need to do is use the tap as a direction,
rather than a destination.
This should look familiar to anyone who didnt fall asleep in maths class Pythagoras! Now before
we fall asleep with yet more theory, lets start coding. In the constructor add the following line:
1 this.setIsTouchEnabled(true);
Next add the following method to the class:
1
2
3 @Override
4 public boolean ccTouchesEnded(MotionEvent event)
{
5 // Choose one of the touches to work with
6 CGPoint location = CCDirector.sharedDirector().convertToGL(CGPoint.ccp(event.getX
7
8 // Set up initial location of projectile
9 CGSize winSize = CCDirector.sharedDirector().displaySize();
CCSprite projectile = CCSprite.sprite("Projectile.png");
10
11
projectile.setPosition(20, winSize.height / 2.0f);
12
13 // Determine offset of location to projectile
14 int offX = (int)(location.x - projectile.getPosition().x);
15 int offY = (int)(location.y - projectile.getPosition().y);
16
17 // Bail out if we are shooting down or backwards
if (offX <= 0)
18 return true;
19
20 // Ok to add now - we've double checked position
21 addChild(projectile);
22
23 // Determine where we wish to shoot the projectile to
24 int realX = (int)(winSize.width + (projectile.getContentSize().width / 2.0f));
float ratio = (float)offY / (float)offX;
25 int realY = (int)((realX * ratio) + projectile.getPosition().y);
26 CGPoint realDest = CGPoint.ccp(realX, realY);
27
28 // Determine the length of how far we're shooting
29 int offRealX = (int)(realX - projectile.getPosition().x);
int offRealY = (int)(realY - projectile.getPosition().y);
30 float length = (float)Math.sqrt((offRealX * offRealX) + (offRealY * offRealY));
31 float velocity = 480.0f / 1.0f; // 480 pixels / 1 sec
32 float realMoveDuration = length / velocity;
33
34 // Move projectile to actual endpoint
35 projectile.runAction(CCSequence.actions(
CCMoveTo.action(realMoveDuration, realDest),
36 CCCallFuncN.action(this, "spriteMoveFinished")));
37
38 return true;
39 }
40
41
42
43
What we are doing here is first enabling touch support. Were telling Cocos2D that we are ready to
handle touches for the current layer. Next we add code to handle touches from the user.
First we get the coordinates of the touch itself, and convert to the Cocos2D coordinate system. This
method should work regardless of the orientation of the device.
Next we create the sprite and position it over the ninja. We then work out where the projectile should
move to by extending the tap off the screen. This is done by getting the X and Y offset of the tap to
the projectiles starting position. We then get the ratio of Y to X, and simply scale the touch Y
coordinate to match the scaled up X coordinate (which is just off the screen). The only problem with
a simple algorithm like this is that the projectile must reach the right edge of the screen before its
cleaned up, the projectile could leave the screen (top or bottom) long before it hits the right edge.
There are solutions to this problem, but theyre beyond the scope of this tutorial.
After working out the destination, we need to work out the duration of the movement we cant have
projectiles taking different amounts of time just because of the angle theyre shot at. To solve this
problem we use Pythagoras to work out the distance the projectile needs to travel, then divide that
by the velocity we want. This is because velocity = distance over time, or by re-arranging: time =
distance over velocity.
Finally we run the actions on the projectile. Run the application and you should be able to start
shooting shurikens!
Collision Detection
Its no good if you can shoot shurikens, but they dont actually do anything! To solve this problem we
need to add some collision detection. To keep things simple well use simple bounding box collision
detection rather than the other more exotic methods Cocos2D provides.
To be able to run collision detection we need to be able to keep track of all of the sprites we have.
Add the following fields to the top of the class declaration:
1 target.setTag(1);
2 _targets.add(target);
In the ccTouchesEnded method add the following just below the addChild(projectile) line:
1 projectile.setTag(2);
2 _projectiles.add(projectile);
Finally update the spriteMoveFinished method to remove the sprite from the appropriate array:
1 if (sprite.getTag() == 1)
2 _targets.remove(sprite);
3 else if (sprite.getTag() == 2)
4 _projectiles.remove(sprite);
If you run the project now you shouldnt notice any difference but now were tracking all of our
sprites! This gives us a great deal of extra power, and we shall use this power to add some collision
detection. Add the following method to the class:
1
2
3 public void update(float dt)
{
4 ArrayList<CCSprite> projectilesToDelete = new ArrayList<CCSprite>();
5
6 for (CCSprite projectile : _projectiles)
7 {
8 CGRect projectileRect = CGRect.make(projectile.getPosition().x - (projectile.
9 projectile.getPosition().y - (projectile.
projectile.getContentSize().width,
10 projectile.getContentSize().height);
11
12 ArrayList<CCSprite> targetsToDelete = new ArrayList<CCSprite>();
13
14 for (CCSprite target : _targets)
15 {
16 CGRect targetRect = CGRect.make(target.getPosition().x - (target.getConte
target.getPosition().y - (target.getConte
17 target.getContentSize().width,
18 target.getContentSize().height);
19
20 if (CGRect.intersects(projectileRect, targetRect))
21 targetsToDelete.add(target);
}
22
23 for (CCSprite target : targetsToDelete)
24 {
25 _targets.remove(target);
26 removeChild(target, true);
27 }
28
if (targetsToDelete.size() > 0)
29 projectilesToDelete.add(projectile);
30 }
31
32 for (CCSprite projectile : projectilesToDelete)
33 {
_projectiles.remove(projectile);
34 removeChild(projectile, true);
35 }
36 }
37
38
39
40
This is a brute-force approach to collision detection. Basically we iterate through all of the projectiles
and targets, creating a rectangle for each and then checking if they intersect. If there is an
intersection we remove the sprites from the scene and arrays. We use the toDelete arrays since we
cant manipulate an array while were iterating through it in the manner weve chosen. Before the
collision detection will work, we need to schedule the method to be called every frame. Add the
following line at the bottom of the constructor:
1 this.schedule("update");
Finishing Touches
Were pretty close to a fully working game now (albeit a simple one!). All games have sound effects
of some sort, and this one is no exception! First we should talk about the audio formats Android
supports, since Cocos2D cant do anything about the supported audio formats on the platform its
running on. The current port of Cocos2D on Android doesnt contain a full sound engine yet,
certainly not on par with CocosDenshion offered on the iPhone. For this reason well use basic wav
files for this tutorial. Well also add some additional code logic so you can win/lose.
First youll need to get some audio for the background music and a sound effect for the shurikens.
You can source your own, or download the wav version of the background music, and pew pew
sound effect from Ray Wenderlichs tutorial. Put the wav files in the res/raw folder of your project.
In the constructor add the following code after the addChild(player) line:
1 _projectilesDestroyed = 0;
In the update method, just after removeChild(projectile, true) add the following code:
1
if (sprite.getTag() == 1)
2 {
3 _targets.remove(sprite);
4
5 _projectilesDestroyed = 0;
6 CCDirector.sharedDirector().replaceScene(GameOverLayer.scene("You Lose, boo"));
7 }
else if (sprite.getTag() == 2)
8 _projectiles.remove(sprite);
9
Go ahead and give the game a play!
Where To Now?
This project provides a good basis for further development. Some possible changes were mentioned
as part of the tutorial, others are limited only by your imagination! Check out the test projects that
come with the source of Cocos2D, see if you can integrate some of them into the project here.
Alternatively, you can follow the next tutorial in the series here.
Or maybe follow the tutorial on audio to learn more about background music & sound effects in
Cocos2D for Android.
This is my first tutorial, and I hope youve enjoyed it!