(For more resources related to this topic, see here.)

The goal of this article is to learn how to work with sprites and get to know their main properties. After reading this article, you will be able to add sprites to your games.

In this article, we will cover the following topics:

  • Setting up the initial project
  • Sprites and their main properties
  • Adding sprites to the scene
  • Adding sprites as a child node of another sprite
  • Manipulating sprites (moving, flipping, and so on)
  • Performance considerations when working with many sprites
  • Creating spritesheets and using the sprite batch node to optimize performance
  • Using basic animation

Creating the game project

We could create many separate mini projects, each demonstrating a single Cocos2D aspect, but this way we won't learn how to make a complete game. Instead, we're going to create a game that will demonstrate every aspect of Cocos2D that we learn.

The game we're going to make will be about hunting. Not that I'm a fan of hunting, but taking into account the material we need to cover and practically use in the game's code, a hunting game looks like the perfect candidate.

The following is a screenshot from the game we're going to develop. It will have several levels demonstrating several different aspects of Cocos2D in action:

Time for action – creating the Cocohunt Xcode project

Let's start creating this game by creating a new Xcode project using the Cocos2D template, just as we did with HelloWorld project, using the following steps:

  1. Start Xcode and navigate to File | New | Project… to open the project creation dialog.
  2. Navigate to the iOS | cocos2d v3.x category on the left of the screen and select the cocos2d iOS template on the right. Click on the Next button.
  3. In the next dialog, fill out the form as follows:
    • Product Name: Cocohunt
    • Organization Name: Packt Publishing
    • Company Identifier: com.packtpub
    • Device Family: iPhone
  4. Click on the Next button and pick a folder where you'd like to save this project. Then, click on the Create button.
  5. Build and run the project to make sure that everything works. After running the project, you should see the already familiar Hello World screen, so we won't show it here.

    Make sure that you select the correct simulator version to use. This project will support iPhone, iPhone Retina (3.5-inch), iPhone Retina (4-inch), and iPhone Retina (4-inch, 64-bit) simulators, or an actual iPhone 3GS or newer device running iOS 5.0 or higher.

What just happened?

Now, we have a project that we'll be working on.

The project creation part should be very similar to the process of creating the HelloWorld project, so let's keep the tempo and move on.

Time for action – creating GameScene

As we're going to work on this project for some time, let's keep everything clean and tidy by performing the following steps:

  1. First of all, let's remove the following files as we won't need them:
    • HelloWorldScene.h
    • HelloWorldScene.m
    • IntroScene.h
    • IntroScene.m
  2. We'll use groups to separate our classes. This will allow us to keep things organized. To create a group in Xcode, you should right-click on the root project folder in Xcode, Cocohunt in our case, and select the New Group menu option (command + alt + N). Refer to the following sceenshot:


  3. Go ahead and create a new group and name it Scenes. After the group is created, let's place our first scene in it.
  4. We're going to create a new Objective-C class called GameScene and make it a subclass of CCScene. Right-click on the Scenes group that we've just created and select the New File option.

    Right-clicking on the group and selecting New File instead of using File | New | File will place our new file in the selected group after creation.

  5. Select Cocoa Touch category on the left of the screen and the Objective-C class on the right. Then click on the Next button.
  6. In the next dialog, name the the class as GameScene and make it a subclass of the CCScene class. Then click on the Next button.
  7. Make sure that you're in the Cocohunt project folder to save the file and click on the Create button.

    You can create the Scenes folder while in the save dialog using New Folder button and save the GameScene class there. This way, the hierarchy of groups in Xcode will match the physical folders hierarchy on the disk. This is the way I'm going to do this so that you can easily find any file in the book's supporting file's projects.

    However, the groups and files organization within groups will be identical, so you can always just open the Cocohunt.xcodeproj project and review the code in Xcode.

  8. This should create the GameScene.h and GameScene.m files in the Scenes group, as you can see in the following screenshot:

  9. Now, switch to the AppDelegate.m file and remove the following header imports at the top:

    #import "IntroScene.h" #import "HelloWorldScene.h"

    It is important to remove these #import directives or we will get errors as we removed the files they are referencing.

  10. Import the GameScene.h header as follows:

    #import "GameScene.h"

  11. Then find the startScene: method and replace it with following:

    -(CCScene *)startScene { return [[GameScene alloc] init]; }

  12. Build and run the game. After the splash screen, you should see the already familiar black screen as follows:

What just happened?

We've just created another project using the Cocos2D template. Most of the steps should be familiar as we have already done them in the past.

After creating the project, we removed the unneeded files generated by the Cocos2D template, just as you will do most of the time when creating a new project, since most of the time you don't need those example scenes in your project.

We're going to work on this project for some time and it is best to start organizing things well right away. This is why we've created a new group to contain our game scene files. We'll add more groups to the project later.

As a final step, we've created our GameScene scene and displayed it on the screen at the start of the game. This is very similar to what we did in our HelloWorld project, so you shouldn't have any difficulties with it.

Adding sprites to your game

A sprite is just a two-dimensional image used in the game. A sprite can represent a player character, an enemy, or even a background image. The following is a sample of a sprite:

A sprite can represent a tiny player character that is moved and animated, but at the same time, a sprite can be just a static image that we use to cover the whole background. The following is an example of this:

As you can see, sprites can come in different shapes and sizes depending on what they represent on the screen.

In Cocos2D, a sprite is represented by the CCSprite class that we already used in the past when we displayed the earth image on the screen in the HelloWorld project. The CCSprite class has everything you need to load the sprite image from the disk, position it, and draw it on the screen.

Let's start by adding a background image to our game scene.

Time for action – adding the background sprite

We will start by adding a good-looking background image instead of a boring black screen by performing the following steps:

  1. Open the Cocohunt project in Xcode if you've closed it.
  2. Before we can add background sprite to our game scene, we need to add an image file with the background to our project.
  3. Right-click on the Resources group in the Xcode Project Navigator on the left of the screen and choose the New Group option in the menu to create a subgroup. Call this group Backgrounds.
  4. Open the Assets/Backgrounds folder and drag the following files (all files in the folder) over to the Backgrounds group that you just created:
    • game_scene_bg.png
    • game_scene_bg-hd.png
    • game_scene_bg-iphone5hd.png

    Refer to the following screenshot:

  5. In the Choose options for adding these files dialog that appears after you release the mouse button, make sure that Copy items into destination group's folder (if needed) is checked and in Add to targets list the Cocohunt target is selected. Then, click on the Finish button. You should see all three files nicely added into Backgrounds group.
  6. Now open the GameScene.m file and import the cocos2d.h header file at the top, right below the #import "GameScene.h" line as follows:

    #import "cocos2d.h"

  7. After this, add the following addBackground method between the @implementation and @end parts in the GameScene.m file as follows:

    -(void)addBackground { //1 CGSize viewSize = [CCDirector sharedDirector].viewSize; //2 CCSprite *background = [CCSprite spriteWithImageNamed:@"game_scene_bg.png"]; //3 background.position = ccp(viewSize.width * 0.5f, viewSize.height * 0.5f); //4 [self addChild:background]; }

  8. However, adding the addBackground method is not enough to get it executed. Add the following init method that will call the addBackground method:

    -(instancetype)init { if (self = [super init]) { [self addBackground]; } return self; }

  9. Build and run the game. You should see the following nice background image instead of the black screen:

Looks better than the black screen, doesn't it? This clearly shows how significant sprites are in any modern game. Most of the objects on the screen are sprites!

What just happened?

As you can see, displaying a big background image wasn't any different from displaying an image of the earth in the HelloWorld project. All we did was add a background image to the Xcode project and then add a background sprite to the GameScene scene.

Let's review each step in detail.

Adding the background image to the Xcode project

You should already know how to add image files to the project and how to create groups in Xcode. We've combined both of these actions and added images into the Resources subgroup. In real games, you have dozens or even hundreds of sprites, so keeping all of them in one Resources group will lead to a total mess.

The interesting part is that this time we've added three versions of the same image. Let's briefly review each one as follows:

  • The game_scene_bg.png file: This is the basic background image used on older devices; in our case, it is used when a game is running on the iPhone 3GS device or the iPhone simulator (non-Retina). Several years ago, before the iPhone 4 (Retina) was introduced, the game_scene_bg.png file would be the only file that we need to add. This is why it comes without any suffix.
  • The game_scene_bg-hd.png file: This image is designated to iPhones with the Retina display, this means the image can be used on iPhone 4 or newer versions, including iPhones with 4-inch displays (for example, iPhone 5S).
  • The game_scene_bg-iphone5hd.png file: This image is intended only for iPhones with 4-inch Retina display, such as iPhone 5 or newer.

    Note that there is no such suffix as –iphone5shd or –iphone5chd for iPhone 5S or iPhone 5C. Instead, for all 4-inch displays, you should use the -iphone5hd suffix.

If you remember, we had only the earth.png and earth-hd.png images when we created the HelloWorld project. You are probably wondering how our HelloWorld project worked on iPhone Retina (4-inch) simulator without the earth-iphone5hd.png image?

The answer is in the way Cocos2D searches for the images. Let's review what happens when you write [CCSprite spriteWithImageNamed:@"earth.png"] and then execute this code on an iPhone 5 device or an iPhone Retina (4 inch) simulator in the following steps:

  1. Cocos2D detects that the game is currently running on an iPhone device with 4-inch display and the corresponding suffix for that device is -iphone5hd. So, it adds this suffix to the original file name that you've provided and the file name becomes earth-iphone5hd.png.
  2. Then, Cocos2D searches for this file, and if the file is found, it uses it. If the file is not found, Cocos2D tries the next most appropriate suffix, which is -hd, as this is still a Retina device suffix.
  3. Cocos2D searches for the file named earth-hd.png and if it finds the file, it uses this file, even though we're running on an iPhone device with a 4-inch display.
  4. If the file is not found, Cocos2D searches for the original file name that we've specified, which is earth.png, and if it can find this file, it uses the file with no suffix. After all, it is better than nothing.
  5. If the original file with no suffix is also not found, then an error occurs and an exception is thrown.

    If you're not handling this error in your code, it will mean that the game will crash at this point.

This is a simplified version of what is happening internally in Cocos2D, but it describes everything close enough to understand how images are searched.

Here is a little explanation of why it is done this way. If you're developing a game that supports both iPhone 4 (with 3.5-inch display) and iPhone 5 or a newer version (4-inch display), in most cases, you will still use the same set of sprites for both. Using separate sprites will make your game heavier (as you include two versions of each image) and you will also have to adjust your game logic and layout to support slightly different sizes of the images (for example, when you are checking collisions between them or positioning them on the screen).

Knowing that Cocos2D will fall back to the –hd suffix allows us to save some traffic and space on the player's disk and use the same images for the 3.5- and 4-inch displays, when appropriate, by specifying the –hd suffix. We will see this in action very soon, but first let's review the rest of changes we've made to the project.

Adding a background sprite to the GameScene

The only difference from the HelloWorld project aside using another image is that we've added the addBackground method and called it from the init method, instead of just adding the code to create a sprite straight into the init method.

This is done simply because adding all the code to the init method makes it harder to update the method in the future, and we're going to do a few more things in the init method.

I assume the code in the addBackground method looks familiar, but let's review it line by line as follows:

  1. This line of code gets the size of the current view.
  2. A background sprite is created using one of the game_scene_bg*.png images, depending on the screen size of the device or the simulator as described earlier.
  3. Then the created sprite is positioned at the center of the screen.

    Don't forget that setting the sprite position property sets the position of the center of the sprite by default. It is easy to forget this at first.

  4. Finally, the sprite is added to the scene. The scene now owns (retains) the sprite, so it won't be deallocated.

This is a typical list of actions required to add a sprite: create the sprite, set its properties, and finally add it to the scene. We'll be doing this sequence a lot of times.

Time for action – adding the player character

Now that we have added the background image, let's add our hunter character. There will be two images, as we're going to split our character in the following two halves:

  • Torso
  • Legs

This will allow us to rotate the torso (the upper body) when aiming. It will get clearer in a moment.

It is a good idea to group these two images into one Cocos2D node to have the ability to operate with both of them as a single game object. Also, we're going to add some code to implement the properties and behavior of our hunter character game object, so we'll need a class for it anyway.

To add the hunter to our game, we need to do the following:

  1. Create a new group for our game objects so that we can separate game object classes from the rest of the classes in the project. Go ahead and create a group called GameObjects next to the Scenes group.
  2. Then, right-click on the GameObjects group and click on the New File… option.
  3. Create a new Objective-C class. Name the class Hunter and make it a subclass of CCSprite.
  4. Save the file in the Xcode project folder.
  5. Before we can use the images in our code, we need to add the image files of the hunter to the project. Create a subgroup in the Resources group and name it Hunter, just as we created the Backgrounds group earlier.
  6. Make sure that the Copy items to destination group's folder (if needed) option and the Cocohunt target in the Add to targets list are checked.

    All of the options mentioned above should be selected by default, but it is always a good idea to check them just to be on the safe side. Bugs that appear due to incorrect settings are hard to detect and often appear only after some time, making you wonder what recent changes lead to the bug you're experiencing right now.

  7. After you've performed all the actions, you should see something like the following screenshot in your Project Navigator panel on the left.

  8. Finally, it is time to display the character on the screen. Open the Hunter.m file and replace its contents with the following:

    #import "Hunter.h" //1 #import "cocos2d.h" @implementation Hunter { //2 CCSprite *_torso; } -(instancetype)init { //3 if (self = [super initWithImageNamed:@"hunter_bottom.png"]) { //4 _torso = [CCSprite spriteWithImageNamed:@"hunter_top.png"]; //5 _torso.anchorPoint = ccp(0.5f, 10.0f/44.0f); _torso.position = ccp(self.boundingBox.size.width/2.0f, self.boundingBox.size.height); //6 [self addChild:_torso]; } return self; } @end

  9. Now, open the GameScene.m file and import the following Hunter.h header:

    #import "Hunter.h"

  10. Then, add the following _hunter instance variable:

    @implementation GameScene { Hunter *_hunter; }

  11. After this, add the following addHunter method below the addBackground method:

    -(void)addHunter { CGSize viewSize = [CCDirector sharedDirector].viewSize; //1 _hunter = [[Hunter alloc] init]; //2 float hunterPositionX = viewSize.width * 0.5f - 180.0f; float hunterPositionY = viewSize.height * 0.3f; _hunter.position = ccp(hunterPositionX, hunterPositionY); //3 [self addChild:_hunter]; }

  12. Add a call to the addHunter method inside the init method as follows:

    -(instancetype)init { if (self = [super init]) { [self addBackground]; [self addHunter]; } return self; } @end

  13. Build and run the project. You should see our hunter standing on the cliff, as shown in the following screenshot:

What just happened?

We've performed one more step towards a complete game and added the hunter character to our game scene. So that you understand it better, we will split everything that we did into several steps.

Preparations step

In this step, we created a stub class for our hunter character and added images of the hunter into the Xcode project. A separate class will allow us to gather the code related to the character in that class, implement the behavior of the hunter, and allow GameScene to work with our hunter as an object. Also, if you have several similar game scenes or levels, you can reuse the Hunter class in all of them.

Creating a class and adding resources should be something that we've done several times in the past, so we'll review different and interesting topics. Why did we split our character image into two images?

A picture is worth a thousand words they say. Well, the following is a screenshot demonstrating the benefits of having the torso separated from the legs:

As you can see, applying a slight rotation to the torso part will allow us to show where the hunter is aiming. This will make shooting from a bow at a different angle look much more realistic compared to the hunter just standing still, aiming straight forward. However, when he shoots, the arrow will fly in a different direction.

Another thing worth mentioning is that we don't have the special images for 4-inch iPhone devices and the simulator (the -iphone5hd suffix). This demonstrates the technique I've mentioned before when we can have the same resource for 4 and 3.5-inch displays and thus save some space.

In most cases, you can have the same images for non-fullscreen sprites on the screen (for example, player character, enemies, buttons, and so on); you will just show a bit more of the world on wider screens.

Adding images to the hunter character

The fact that our character consists of two images isn't all that good. It makes our life a bit harder, as this means that we need two CCSprite objects to display both images. As this is one character after all, we'll have to write the code to make it behave like one character so that we don't end up with the situation where the legs are separated from the body.

We can create two separate CCSprite objects in the GameScene scene and manage them separately, but it is more convenient to group them into one object. This way we can work with the hunter as with one object; for example, we can set its position, add, or remove it from the scene.

A common way to group objects in Cocos2D is to add a node as a child node of the other node. For example, you can add a hand as a child sprite of the body sprite, or in our case, we can add a torso as a child node of the legs' sprite, as it sits on top of the legs, or in other words, it grows out from the legs if we can say it this way.

The reason why we put the torso as the child of the legs and not vice versa is that when we're rotating a parent node, it rotates all of the children contained in it as well and we don't want the legs to be rotated when we rotate the torso.

In many cases, you may want to group several objects into one. For example, you might want your character to have different hats, weapons, or even body parts. Of course, you can do this by drawing several different characters, but there can be many combinations, which can lead to a lot of work for the designer and will take a lot of space to store.

Since we're not only grouping the objects but are also going to write some code to manage these objects (hunter's methods and properties), it is a good idea to make our class inherit from CCSprite and put all the code into this class. This is exactly what we've done in our code. We've inherited our class from CCSprite to extend it and make it a more specific Hunter class.

Let's review each code block marked by the comment with the number in the Hunter.m file as follows:

  1. The first thing we did was import the cocos2d.h header file so we can use the different Cocos2D classes.
  2. Next, we added an instance variable to hold the reference to the torso sprite, as we're going to manipulate it later.
  3. The init method uses the super class's initWithImageNamed: method to initialize itself with the legs' image. This current sprite will be the parent to the torso sprite, but as we've inherited the CCSprite class, we can also use it to display the legs' image. This way the Hunter class itself will be a container for the torso and will display the legs. Two birds with one rock!
  4. A separate sprite for the torso was created and stored in the _torso instance variable.
  5. After the sprite was created, its anchor point and position are changed.

    When we set the position of the child node, such as the torso, we set the position relative to its parent node. There is a good figure demonstrating the coordinates system a few pages later.

  6. Finally, the torso was added as the child to the current legs' sprite.

There is a tricky point in the code block marked with the number 5. Here, we calculated the position and the anchor point. Let's review it more carefully starting with the anchor point part.

An anchor point is the point around which all transformations and positioning manipulations are made. When we set the position of the sprite and any other node, we actually move the sprite using the anchor point as the pivot for the sprite. When we rotate the object, it rotates around the anchor point. Remember I mentioned that when we set the position for the sprite, we actually set where the center of the sprite will be by default. This is because the anchor point is set to the center of the sprite by default.

This is only true for sprites and shouldn't be assumed for scenes and nodes.

Imagine a sheet of paper that you pierced with a needle. When you move this needle, a sheet of paper follows. When you pin this needle on the board, you pin the whole sheet of paper in the position of the needle, although it might not be at the center of the sheet of paper. When you rotate this sheet of paper, it rotates around the needle. This is what an anchor point is.

In Cocos2D, the anchor point is normalized; this means that it goes from 0 to 1 for each side of the image and (0,0) is the left-bottom corner whereas (1,1) is the top-right corner. It doesn't depend on the actual size of the image; it is always 0 to 1.

Another important note is that you can set an anchor point out of the (0,0)(1,1) range; for example, you can set it to (-1,-1), which is outside the sprite rectangle. This will make the sprite rotate around that point outside itself. It might be quite useful when you want to rotate one object around another object; you just set the anchor point of the object to be rotated to the center of the object it should rotate around and then it will rotate around this object.

If you do this, don't forget that the anchor point is still in the local sprite coordinates, and you should first find the position of other node in the local coordinates of current sprite.

Back to our hunter. We're going to change its default anchor point, as we don't want to rotate around the center of the torso. We want to rotate around the point that is situated around the waist as is demonstrated in the following screenshot:

You can clearly see that x=0.5 (as it is the center of the image's width) and y is about 10 points from the bottom edge of the image, while the whole image is about 44 points high, which gives us y=10/44. Now this line of code that we added to the init method of the Hunter class should be much clearer:

_torso.anchorPoint = ccp(0.5f, 10.0f/44.0f);

The remaining changes that we've made to the Hunter.m file set the position of the torso. This should be quite simple when we remember that we set the position using the anchor point. We want our hunter's anchor point (which is in the middle of the waist, as shown earlier) to be at the middle of the top edge of the legs' image.

Instead of hardcoding the coordinates, we used the boundingBox property. This property gives us the size of the legs' sprite rectangle. We can use it to calculate the position of the middle point of the top edge of the legs' image.

Don't forget that we set the position relative to the legs' image sprite because the torso is the child node of the legs. And as you remember, each node is positioned relatively to its parent and not relative to the global origin. This means that from the torso's point of view, the bottom-left corner of the legs' image is (0,0), and we want our anchor point to be positioned at (width / 2.0, height). Refer to the following screenshot:

It might seem strange that we placed our child sprite outside of the parent, but this is totally normal in the Cocos2D world. The only downside of this approach is that when we want to move the whole hunter object, we need to remember that we should position the center of the legs' sprite, not the center of the whole hunter.

This can be fixed if we change the anchor point of the legs to (0.5, 1). The anchor points (legs and torso) will then be at one point. But since we won’t move the hunter after placing it we can leave everything as is.


Wow, we've done a lot in this article. We've learned how to use sprites and change their basic properties such as position, anchor point, and z-order. These properties are crucial to understand if we want to make video games.

Resources for Article:

Further resources on this subject:

You've been reading an excerpt of:

Learning iPhone Game Development with Cocos2D 3.0

Explore Title