Dragging multiple sprites in Cocos2d

On September 6, 2010, in cocos2d, Game development, by admin

One of the questions that pops up often when talking to people new at using cocos2d is how to drag multiple sprites. There are lots of ways to achieve this, some more messy code-wise than others. This article will show you how to delegate touches to the sprites themselves, and not have to worry about it in the layer.

Click here to download the complete project.

This simple project consists of 2 classes :
HelloWorld” : The CCLayer where we will have our sprites, plus a button to add more of them
DragSprite” : Our CCSprite subclass that will handle touches itself

Let’s start with the init of the DragSprite class:

{code type=php}
-(id) init
{
if( (self=[super init] )) {

// Button to add more sprites to the layer
CCMenuItemFont *add = [CCMenuItemFont itemFromString:@”Add Sprite” target:self selector:@selector(addSprite:)];
add.position=ccp(80,30);
CCMenu *menu = [CCMenu menuWithItems:add,nil];
[self addChild:menu];
menu.position=ccp(0,0);

}
return self;
}
{/code}

The first thing we do is simple. We create a CCMenuItemFont, a button to add more sprites to the layer. As you can see, when pressed, the button will call a selector called addSprite. Here is the code for this selector :

{code type=php}
-(void) addSprite:(id)sender{
//
// Add a sprite and position it randomly on the screen
//
DragSprite *s = [DragSprite spriteWithFile:@”smile.png”];
s.position=ccp(arc4random() % 480,arc4random() % 320);
[self addChild:s];
}
{/code}

This is all the code for our layer! As you can see, this just creates a simple sprite (don’t forget DragSprite inherits CCSprite). It places it randomly on the X and Y axis of the device’s screen. Also, note that we don’t have any arrays keeping track of sprites. We don’t need one because the sprites will keep track of themselves, and handle their own touches. They implement the CCTargetedTouchDelegate protocol.

Now, let’s get to the meat of this article, the DragSprite class. I will again start with the init, which consists of a single line of code :

{code type=php}
-(id) init{
if((self=[super init])){
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
return self;
}
{/code}

Here, we tell the CCTouchDispatcher singleton that this sprite (self) wants to receive touch events. Note that it will receive ALL touch events, not just the ones touching the it. This is why we need to set priorities and check for a position match. In our case, all the sprites will have the same priority. swallowsTouches means that once we accept the touch (return YES on the touch began method), no other nodes will receive the touch. If we set this to NO, all touch delegates will be informed of the touch, wether others have accepted it or not. This can be useful for dragging multiple sprites at once for example.

Next, now that we added the node to the CCTouchDispatcher, we need to implement the touch methods. Here’s the first one, ccTouchBegan:

{code type=php}
– (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchPoint = [touch locationInView:[touch view]];
touchPoint = [[CCDirector sharedDirector] convertToGL:touchPoint];

if([self isTouchOnSprite:touchPoint]){
isDrag=YES;
whereTouch=ccpSub(self.position, touchPoint);
return YES;
}

return NO;
}
{/code}

The first 2 lines are just to get the touch in cocos2d coordinates. Then, we check if the touch is on the sprite. Note that the method isTouchOnSprite is not from cocos2d. It will be detailed next in this article. If the method returns YES, it means that the touch is on the sprite. Since it is, we return YES. if it is not, we return NO and the 2 other touch method, touchMoved and touchEnded will not be called. The touch will then be passed to all other nodes implementing the touch delegate.

You will also see that I set another variable, whereTouch. This is a simple CGPoint declared in the header file. Its goal is to know exactly where the touch is on the sprite, so when we move it, it won’t auto center to your finger. For example, if you grab it at it’s top left corner, it will be dragged from that point. You’ll understand when running the included project.

Now here’s the isTouchOnSprite method, which checks if the touch is on the sprite (remember that all nodes that receive touches receive ALL touches, wether it actually touches the concerned node or not).

{code type=php}
-(BOOL) isTouchOnSprite:(CGPoint)touch{
if(CGRectContainsPoint(CGRectMake(self.position.x – ((self.contentSize.width/2)*self.scale), self.position.y – ((self.contentSize.height/2)*self.scale), self.contentSize.width*self.scale, self.contentSize.height*self.scale), touch))
return YES;
else return NO;
}
{/code}

The CGRectContainsPoint is a simple function that takes 2 parameters : a CGRect and a CGPoint. It returns YES if the point is in the rect and no if it is not.

CGRectMake is a method that creates a CGRect, and takes 4 parameters : x, y, width and height. If CGRectContainsPoint returns YES, we return YES to our ccTouchBegan method to actually register the touch and receive the touchMoved and touchEnded calls, where we will move the sprite.

Now, here’s the ccTouchMoved selector. This will be called when we accepted the touch (returned YES on ccTouchBegan) and the finger moved. This is where we will move the sprite to the new touch position and add the whereTouch difference so the sprite doesn’t auto center to the finger’s position.

{code type=php}
– (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchPoint = [touch locationInView:[touch view]];
touchPoint = [[CCDirector sharedDirector] convertToGL:touchPoint];
self.position=ccpAdd(touchPoint,whereTouch);
}
{/code}

As you can see, all we’re doing is changing the position of the sprite (self) to the addition of the new touch location + touch location on the sprite. This means that if we drag it from a corner, as soon as we move, the sprite wont center itself on the touch. If this is what you want, remove whereTouch from this project and it will work like that.

In ccTouchEnded, for the case of this project, we put nothing. We could have not even implemented it and it would work fine.

{code type=php}
– (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{

}
{/code}

I hope this article helped you better understand how touches work in cocos2d. Doing it this method, you could use multiple fingers and drag multiple sprites at the same time. This is the true way to do multitouch sprite dragging in Cocos2d.

Tagged with:  

16 Responses to “Dragging multiple sprites in Cocos2d”

  1. cocos2dnoobe says:

    Awesome! Thank you!

    I tried to add sprites on the same z level but I did not succeed
    In -(void) addSprite:(id)sender{

    [self addChild:s z:0]; // adding z:0 did not helped
    }
    what would be a solution?

  2. admin says:

    @cocos2dnoobe : By default, it is at 0. The way it works is if 2 sprites share the same Z, the one added last will be on top. if you want a moved sprite to come back on top, simply do this in ccToucheEnded :

    CCNode *p = self.parent;
    [p removeChild:self cleanup:NO];
    [p addChild:self];

    I hope this helps.

    • cocos2dnoobe says:

      Great tip!
      After some thinking I applied this idea to ccTouchBegan instead of ccTouchedEnded and it did the trick.
      Now the dragged sprite would go on top of others at once.

  3. cocos2dnoobe says:

    Thank you for your reply!

    In the original code the every next sprite was created closer to the user. So it looked that all sprites had different Z.

    Now the behavior is as follows: If we create a few sprites and come back to the one created earlier the first drag will take it on top of some and below some other. After releasing the drag and dragging the sprite again this sprite would go on top of all others.
    So first drag takes this sprite usually under all other sprites second drag takes it over.

    1. How to make it consistent?
    2. Could we crate sprites to have same Z?

  4. admin says:

    1. Actually, in the original code, the newer the sprite is, the more on top it is. This is the way it was supposed to be. I retested because you made me doubt, but It’s like that both on device and simulator.

    2. How do you vision all being on the same Z ? There has to be one on top of the other one. Do you want them to blend? This is how it works in cocos2d. If 2 sprites share the same Z order, the newest one is on top, unless you do what I told you : Remove sprite from its parent and add it back. This will make it go back on top as if it had just been added.

    • cocos2dnoobe says:

      Sorry for the confusion. When I was referring to z-order I based it on the article from wiki http://en.wikipedia.org/wiki/Z-order:
      “When dealing with visual objects on a computer screen, an object with a Z-order of 1 would be visually “underneath” an object with a Z-order of 2 or greater.”
      So I assumed the same principle for cocos2d sprites.

      Yes you are right, I was wondering what to do to the sprites in order to see them “blending” when one is overlapping the other. This way all of them would be equal from visual point of view.

      Thank you for your response.

  5. shuwee says:

    Hello its a great article, I hope you can also help me regarding my query.

    I have to animate an action and condition is like this:

    If the Up key is pressed and then (along with UP key still pressed) you press key-1 then only the character should animate, else it should not.

    If Key-1 is pressed without UP key pressed, then different animation should run.

    I am using TouchBegin but for that both the keys should be pressed at the same time, which is random. I wish to keep UP key pressed and then press Key-1.

    So how do I check if the key is still pressed??

  6. gnomesgames says:

    Just a quick comment to thanks you for this very well explained article, you have saved me a lot of time !

  7. Lowell says:

    Hi,
    I’m new to Cocos2d but when I compile the project, I’m not able to drag multiple sprites. I’ve tried swallowsTouches as YES and NO but have made no other changes. Any thoughts?

  8. Alexander says:

    Hi,
    Great guide! Helped me a lot unterstand a few things 😉 BTW RocketBoy is awesome…looking forward to Dream Track Nation!
    greetings Alex

  9. admin says:

    Thanks all for the comments, I’m glad this article is working out for you 🙂

  10. CJ says:

    This is an awesome tutorial. Thanks so much for posting it!

    Letting the sprites manage themselves really cleans up the code. I’m fairly new to objective C and I’m running into an issue where I want to pass a string to the dragsprite class to add as a label onto the sprite. — such that everytime I create a sprite, I can give it a string along with a image when its created.

    It might look like:
    [DragSprite spriteWithFile:@”myImage.png” spriteName:@”myName”];

    I keep running into errors because the spriteWithImage is passed down to the CCSprite and I dont want spriteName to be passed down farther, just within the dragSprite class. I’m pretty sure i’m missing some concept with inhertence but I cant be sure. How would you suggest tackling this problem?

  11. Dave K. says:

    hi,

    I wanted to be able to multi-touch drag the sprites (like in eliss) and i’m a real noob at this… i changed the code:

    -(id) init{
    if((self=[super init])){
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:NO];
    }
    return self;
    }

    but that wasn’t enough. only one moves at a time… what else do i have to change?

  12. Thank You! This is the only article out of the 12 I’ve been reading to actually help me move separate objects by touch with different sprites instead of NSArray sprites.

  13. Joseph says:

    I’m getting an error that the class doesn’t implement ‘CCTargetedTouchDelegate’ protocol.

    I have had the protocol working on a layer, but it doesn’t seem to work on a sprite for me.

    • Joseph says:

      I actually solved this. I only followed the tutorial without downloading the code, so I hadn’t added the protocol to my header.

      But now I have a fresh problem: I can only drag a sprite to the edge of the screen. As soon as I hit it, even though I’m using a follow action, I can’t drag the sprite any further.

Leave a Reply

Premium WordPress Themes

Archives

All entries, chronologically...

WordPress Blog