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:

-(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;
}


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 :

-(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];
}


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 :

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


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:

- (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;
}


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).

-(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;
}


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.

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


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.

- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
}


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:  

When building games, one of the most visibly easy things to do, adding buttons and controls, is often a complicated task. Luckily, Nick Pannuto, a guy from the iPhone game developer community, released SneakyInput, a group of classes that include a joystick, a dPad and very good buttons. Created with simplicity and ease of use in mind, they’re still some of the most complete and functional input classes.

Downlading SneakyInput

Luckily for you, SneakyInput is an open source project, thus is free. Keep in mind giving donations to the creator if you make money using this kit. It surely will help getting updates and more content from him.

Start by downloading the package from this page : Link. To download, click on the big “Download Source” button at the top of the page.

When you unzip the package, you’ll see both the classes and a sample project. As you can see, there isn’t much there and all is pretty simple to understand as you’ll see.

Usage


Joystick / dPad

In addition to the actual Joystick class, the developer included a class that creates colored circles dynamically. Instead of using sprites, for debugging reasons or else, you can use this class. The circles are just like sprites, but with programmed color instead of an actual texture. There are no resources needed to use it.

Start by including the necessary files :

#import "SneakyJoystick.h"
#import "SneakyJoystickSkinnedBase.h"
#import "ColoredCircleSprite.h"


Those are the needed files in order to use either the Joystick or the dPad. A dPad is basically a joystick, moving only on either X or Y axis, without 360 degrees rotation. It’s a “crippled” joystick, and is programmed almost in the same way.

In your target layer, the layer you want to add the Joystick to, start by creating the base of the joystick, which will hold the rest of the stuff.

SneakyJoystickSkinnedBase *leftJoy = [[[SneakyJoystickSkinnedBase alloc] init] autorelease];
leftJoy.position = ccp(64,64);


The second line places the Joystick in the bottom left corner, because later in the tutorial, we’ll give it a 32 pixel radius.

After creating the Joystick, you have either of 2 options. You can:

-Use your own sprites
-Use ColoredCircleSprite, a class that creates colored circles with RBGA.

In the first example, we will be using our own sprite, created from a simple PNG file.

// Sprite that will act as the outter circle. Make this the same width as joystick.
leftJoy.backgroundSprite = [CCSprite spriteWithFile:@"outerJoy.png"];
// Sprite that will act as the actual Joystick. Definitely make this smaller than outer circle.
leftJoy.thumbSprite = [CCSprite spriteWithFile:@"innerJoy.png"];


On the other hand, for debugging purposes or if a simple RBGA Joystick is enough for your project, SneakyInput includes a class to create circles out of a radius and a RBGA code.

leftJoy.backgroundSprite = [ColoredCircleSprite circleWithColor:ccc4(255, 0, 0, 128) radius:32];
leftJoy.thumbSprite = [ColoredCircleSprite circleWithColor:ccc4(0, 0, 255, 200) radius:16];


In the previous code, instead of creating CCSprites to see the Joystick, we’re using a special class called ColoredCircleSprite. As simple as can be, this class takes 2 parameters: a ccc4 color (R,B,G,A -> meaning Red, Blue, Green, Alpha) and a radius, which is half the width of the wanted Joystick.

Then, whether you used ColorCircleSprite or a regular cocos2d CCSprite, the next part is the same. We’re gonna be adding the actual Joystick do our Joystick base.

leftJoy.joystick = [[SneakyJoystick alloc] initWithRect:CGRectMake(0,0,128,128)];
leftJoystick = [leftJoy.joystick retain];
[self addChild:leftJoy];


In the first line, we assign a Joystick to the joystick property of the Joystick Base. The SneakyJoystick class actually only takes 1 parameter, it’s size and position. This parameter is in the form of a CGRect : CGRectMake(int x, int y, int width, int height). The width and height are full widths and heights, not to mix with the radius of the previous code snippets.

The second line assigns a value to our SneakyJoystick property that we need to keep in order to query the Joystick at a specified interval.

In the last line, just like any other cocos2d example, we add the Joystick to the layer. Without this, the Joystick will not be visible.

Usage

The SneakyJoystick is not delegated. This means that in order to use it and apply its movement to CCSprites or any other object, you need to query it. This can be done anywhere, anytime. The suggested usage is to create a selector that will be called every frame and do all your stuff there.

[self schedule:@selector(tick:)];


Now the selector would look something like this:

-(void)tick:(float)delta {
/* This will take the joystick and tell a special method (not listed here, outside the scope of this guide) to take the joystick, apply movement to hero (CCSprite or else) and apply the real delta (to avoid uneven or choppy movement, delta is the time since the last time the method was called, in milliseconds). */
[self applyJoystick:leftJoystick toNode:hero forTimeDelta:delta];
}


This concludes how to use SneakyJoystick! Now you’re probably wondering how do turn this into an awesome dPad for your fighting game or whatever creative game you’re tinkering up. If you’re hoping for a quick one line solution, you’re in luck! Here’s what to do to turn the Joystick into a dPad :

joystick.isDPad = YES;


That’s all there is to it! Other than that, using sprites that looks like an actual dPad will surely give it a more oldschool and realistic look and feel.

Buttons

Now that you can handle the joystick, which is the most complicated of the duo, it’s time to add some buttons. Buttons from SneakyInput are much more complete and usable in a game as buttons from cocos2d (CCMenuItem).

First, we need to include the necessary files in order to use the buttons.

#import "SneakyButton.h"
#import "SneakyButtonSkinnedBase.h"


The buttons are created and added to the layer in the same way as we did with the joystick. In the following example, we’ll be using the ColoredCircleSprite class. Don’t forget that you can easily switch those for regular CCSprites

SneakyButtonSkinnedBase *rightBut = [[[SneakyButtonSkinnedBase alloc] init] autorelease];
rightBut.position = ccp(448,32);
rightBut.defaultSprite = [ColoredCircleSprite circleWithColor:ccc4(255, 255, 255, 128) radius:32];
rightBut.activatedSprite = [ColoredCircleSprite circleWithColor:ccc4(255, 255, 255, 255) radius:32];
rightBut.pressSprite = [ColoredCircleSprite circleWithColor:ccc4(255, 0, 0, 255) radius:32];
rightBut.button = [[SneakyButton alloc] initWithRect:CGRectMake(0, 0, 64, 64)];
rightButton = [rightBut.button retain];
rightButton.isToggleable = YES;
[self addChild:rightBut];


As you can see, instead of only using 2 sprites, the button uses 3. The 3rd sprite being used is only displayed when the button is set to isToggleable (will explain later). This means that it’ll only show when the button is selected.

At the end of the code, we set isToggleable to YES. This means the button will act as a switch. It’ll activate or deactivate on touch, just like a light switch.

To use the button, you will need to use the same technique as the joystick, meaning querying the buttons, whenever you want, to get their values. Here are the different possibilities:

// pressed
>if (rightButton.active == YES){
    [this doThat]; // Button is pressed at the moment
}

else{
    [this doElse]; // Button is NOT pressed at the moment
}
// switched (only if isToggleable is set to YES)
>if (rightButton.value == 1){
    [this doThat]; // Button is activated
}

else{
    [this doElse]; // Button is NOT activated
}


 

Understanding anchorPoint in cocos2d

On July 26, 2010, in cocos2d, by admin

There’s something in cocos2d which is super easy to understand, but that most people seem to have problems with it at first : Anchor Points. Every class derived from CCNode (Ultimate cocos2d class) will have a anchorPoint property.

When determining where to draw the object (sprite, layer, whatever), cocos2d will combine both properties position and anchorPoint. Also, when rotating an object, cocos2d will rotate it around its anchorPoint, as if a pin were placed on that very spot.

Positioning

As a first example, this sprite has an anchorPoint of ccp(0,0) and a position of ccp(0,0). This rectangle sprite will be placed at the bottom left corner of its parent, the layer.

CCSprite *sprite = [CCSprite spritewithFile:@"blackSquare.png"];
sprite.position=ccp(0,0);
sprite.anchorPoint=ccp(0,0);
[self addChild:sprite];






In this second example, we will assign a anchorPoint of ccp(-1,-1) to better understand the relative value of the anchor point.

CCSprite *sprite = [CCSprite spritewithFile:@"blackSquare.png"];
sprite.position=ccp(0,0);
sprite.anchorPoint=ccp(-1,-1);
[self addChild:sprite];






As you can see in the image, the anchor point is not a pixel value. The value of X and Y are relative to the size of the node. For example, in a 30×30 pixel square, something like the image above, a anchorPoint of ccp(-1,-1) will mean “Place the anchor 1 * myWidth to the left and 1 * myHeight under the sprite

As a final example, to better understand the combination of position and anchorPoint, I’ll show you different lines of code that give exactly the same result as the first image. That piece of code will combine both position and anchorPoint, and show you how to use the contentSize property.

CCSprite *sprite = [CCSprite spritewithFile:@"blackSquare.png"];
sprite.anchorPoint=ccp(1,1);
sprite.position=ccp(sprite.contentSize.width , sprite.contentSize.height);
[self addChild:sprite];

Using this snippet, we place the anchorPoint at ccp(1,1), which places it 1 * width to the right and 1 * height over. If we kept position at ccp(0,0), the sprite would be outside the screen, to the bottom left of it.

By placing the sprite at a position equal to ccp(1 * width, 1* height), it returns to the origin, at the bottom left corner.




Rotation

When rotating, we need to be careful with anchorPoint, as it can give us some very weird results, more so when using irregular anchor points such as ccp(0,0). By default, the anchorPoint is set to ccp(0.5f,0.5f), which is the middle of the sprite on both X and Y axes (remember that anchorPoint is a relative value to the size of the sprite).

Using ccp(0.5f,0.5f) as an anchorPoint will give the simplest rotation possible, which doesn’t change the actual position of the sprite. It’ll rotate just as you would normally think.

In the following example, the anchorPoint is set to ccp(1,1), meaning the sprite will rotate around its top right corner.





Remember that since anchorPoint is relative, you can assign it to ccp(2,2) and the sprite will rotate around that point. This is the case in the following example.




Simpler than it looks

I hope this tutorial was clear and helped you better understand how to use anchor points in cocos2d, and combine them with the position to place the sprites where you like, how you like. Also, by using different anchor points, you learned that it is possible to create different rotating patterns, such as rotating around external points.

Tagged with:  

Welcome to QcMat.com

On July 21, 2010, in Uncategorized, by admin

Hello,

Welcome to QcMat.com, where I will blog about my game development and add tutorials when the need comes. I love helping people and regularly am in a chat room helping people. I think that by putting this information in a blog or website, it can help loads of other people with the same problems or questions.

I hope you enjoy my blog, and feel free to follow me on twitter @QcMatDev.

 

Archives

All entries, chronologically...

WordPress Themes