Cocos2D Tutorial for iOS: How To Drag and Drop Sprites

A Cocos2D tutorial on how to drag and drop sprites via touch gestures. By Ray Wenderlich.

Leave a rating/review
Save for later
Share

Drag and drop these cute pets with Cocos2D!

Drag and drop these cute pets with Cocos2D!

I’ve received several requests to write a Cocos2D tutorial on how to drag and drop sprites in Cocos2D by touch gestures. You asked for it, you got it!

In this Cocos2D tutorial, you’re going to learn:

  • The basics of dragging and dropping sprites with touches
  • How to scroll the view itself via touches
  • How to keep coordinates straight in your head
  • How to use gesture recognizers with Cocos2D for even more cool effects!

To make things fun, you’ll be moving some cute animals around the scene drawn by my lovely wife, on a background made by gwebstock.

This Cocos2D tutorial assumes you have at least basic knowledge of Cocos2D and have the Cocos2D templates already installed. If you are new to Cocos2D, you may wish to try out the How To Make A Simple iPhone Game with Cocos2D tutorial first.

So without further ado, drag your fingers over to the keyboard and let’s get started!

Getting Started

Before you implement the touch handling, first you’ll create a basic Cocos2D scene displaying the sprites and artwork.

Open up XCode, go to File\New Project, choose User Templates\cocos2d X.X.X\cocos2d Application, and click “Choose…“. Name the project “DragDrop“, and click Save.

Next, go ahead and download the images you’ll need for this Cocos2D tutorial. Once you download and unzip the file, drag all of the images into the Resources group in your project. Verify that “Copy items into destination group’s folder (if needed)” is checked, and click Add.

Once you’ve added the images to your project, open the Classes group in XCode, and select HelloWorld.h. Inside the @interface declaration, declare three instance variables you’ll need like the following:

CCSprite * background;
CCSprite * selSprite;
NSMutableArray * movableSprites;

You’ll be using these later on to keep track of the background image, the currently selected sprite, and the list of sprites that should be moved with touches.

Now switch to HelloWorldScene.m. Find the init method, and replace it with the following:

-(id) init {
    if((self = [super init])) {		
        CGSize winSize = [CCDirector sharedDirector].winSize;
        
        [CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGB565];
        background = [CCSprite spriteWithFile:@"blue-shooting-stars.png"];
        background.anchorPoint = ccp(0,0);
        [self addChild:background];
        [CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_Default];
        
        movableSprites = [[NSMutableArray alloc] init];
        NSArray *images = [NSArray arrayWithObjects:@"bird.png", @"cat.png", @"dog.png", @"turtle.png", nil];       
        for(int i = 0; i < images.count; ++i) {
            NSString *image = [images objectAtIndex:i];
            CCSprite *sprite = [CCSprite spriteWithFile:image];
            float offsetFraction = ((float)(i+1))/(images.count+1);
            sprite.position = ccp(winSize.width*offsetFraction, winSize.height/2);
            [self addChild:sprite];
            [movableSprites addObject:sprite];
        }     		
    }
    return self;
}

There's some new stuff in here, so let's go over it step by step.

Loading the Background

The first part of the method loads the background image for the scene (blue-shooting-stars.png). Note that it sets the anchor point of the image to the lower left of the image (0, 0).

In Cocos2D, when you set the posiiton of a sprite, you are actually setting where the anchor point of the image is. By default, the anchor point is set to the exact center of the image. However, by setting the anchor point to the lower left corner, when you set the position of the sprite, you are now setting where the lower left corner is.

The method doesn't set the position of the background anywhere, so the position of the background defaults to (0,0). Hence, the lower left corner of time image is located at (0,0), and so the image (which is about 800 points long) extends off screen to the right.

Another thing to note about loading the background image is it switches the pixel format before it loads the image. By default, when you load images with Cocos2D, they are loaded as 32-bit images. This means each pixel takes up 4 bytes in memory. This is good when you want very high quality artwork, but the tradeoff is it takes a large amount of memory to load the images - a premium on mobile devices.

When you're loading large images (such as background images), it's often good practice to load the background images as 16-bit images instead - basically sacrificing quality for lower memory usage. There are several different texture formats available in Cocos2D - here you choose the best possible level of quality for 16-bit images that don't need any transparency effects.

Loading the Images

The next part of the method loops through the list of images to load and creates and places a sprite for each. The sprites are distributed along the length of the screen, to have a nice initial layout. It also puts a reference to each sprite in the array of movable sprites, which will be used later.

Speaking of which, one last step - find your dealloc method and add the following lines to release the array:

[movableSprites release];
movableSprites = nil;

That's it! Build and run your code, and you should see a couple cute animals sitting there, just begging to be touched!

Sprites just waiting to be touched!

Selecting Sprites based on Touches

Now you'll write the code to determine which sprite is selected based on the user's current touch.

The first step is to enable your HelloWorldLayer to receive touches. Add the following line to the end of your init method to do so:

[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];

Note that this is a relatively new way to enable touches on your layer - the old way was to set the isTouchEnabled property to yes and implement ccTouchesBegan. If you are curious as to the advantages of this new method over the old method, check out the How To Make a Tile Based Game with Cocos2D Tutorial for more information.

Next, add the following new methods to the bottom of HelloWorldScene.m:

- (void)selectSpriteForTouch:(CGPoint)touchLocation {
    CCSprite * newSprite = nil;
    for (CCSprite *sprite in movableSprites) {
        if (CGRectContainsPoint(sprite.boundingBox, touchLocation)) {            
            newSprite = sprite;
            break;
        }
    }    
    if (newSprite != selSprite) {
        [selSprite stopAllActions];
        [selSprite runAction:[CCRotateTo actionWithDuration:0.1 angle:0]];
        CCRotateTo * rotLeft = [CCRotateBy actionWithDuration:0.1 angle:-4.0];
        CCRotateTo * rotCenter = [CCRotateBy actionWithDuration:0.1 angle:0.0];
        CCRotateTo * rotRight = [CCRotateBy actionWithDuration:0.1 angle:4.0];
        CCSequence * rotSeq = [CCSequence actions:rotLeft, rotCenter, rotRight, rotCenter, nil];
        [newSprite runAction:[CCRepeatForever actionWithAction:rotSeq]];            
        selSprite = newSprite;
    }
}

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {    
    CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
    [self selectSpriteForTouch:touchLocation];      
    return TRUE;    
}

The first method (selectSpriteForTouch) is a helper method that loops through all of the sprites in the movableSprites array, looking for the first sprite that the touch intersects.

Note that CCNode has a helper property called boundingBox that returns the bounding box around the sprite. This is generally better to use than computing the rectangle yourself, because a) it's easier, and b) it takes the transform of the sprite into consideration.

After it finds a matching sprite (if any), it runs a little animation on the sprite so the user knows which they have selected. It first cancels any animation running on the previously selected sprite, then uses a sequence of CCActions to make the sprite appear to "wiggle" back and forth (much like the iOS home screen when rearranging/deleting apps).

Finally, the ccTouchBegan method calls the above method based on the user's touch. Note that it has to convert the touch location from UIView coordinates to layer (node space) coordinates. To do this, it calls a helper method in CCNode called convertTouchToNodeSpace. This method does three things:

  1. Figures out the location of the touch in the touch's view (via locationInView)
  2. Converts the touch coordinates to OpenGL coordinates (via convertToGL)
  3. Converts the Open GL world coordinates to coordinates in a specific node (this layer, via convertToNodeSpace)

This is a common conversion to do, so using this method saves some time.

Compile and run your code, and you should now be able to click on the animals, and when you tap them they should wiggle in a particularly cute way to show that they are selected!

Sprites wiggling indicating selection