multi touch error in cocos2d - index 1 beyond bounds - objective-c

I am trying to get multi touch working so that I can move two sprites at the same time. I followed this tutorial http://www.saturngod.net/detecting-touch-events-in-cocos2d-iphone-ganbaru-games and this is the code I have in ccTouchesBegan:
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *touchArray = [touches allObjects];
// We're going to track the first two touches (i.e. first two fingers)
// Create "UITouch" objects representing each touch
UITouch *fingerOne = [touchArray objectAtIndex:0];
UITouch *fingerTwo = [touchArray objectAtIndex:1];
// Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
CGPoint pointOne = [fingerOne locationInView:[fingerOne view]];
CGPoint pointTwo = [fingerTwo locationInView:[fingerTwo view]];
// The touch points are always in "portrait" coordinates
// You will need to convert them if in landscape (which we are)
pointOne = [[CCDirector sharedDirector] convertToGL:pointOne];
pointTwo = [[CCDirector sharedDirector] convertToGL:pointTwo];
if (CGRectContainsPoint(ball.boundingBox, pointOne))
{
//ball.position = ccp(location.x , location.y);
areWeTouchingABall = YES;
//printf("*** ccTouchesBegan (x:%f, y:%f)\n", location.x, location.y);
}
if(CGRectContainsPoint(sp.boundingBox, pointOne)){
areWeTouchingASquare = YES;
// printf("*** ccTouchesBegan (x:%f, y:%f)\n", location.x, location.y);
}
// Only run the following code if there is more than one touch
if ([touchArray count] > 1)
{
if ( CGRectContainsPoint(ball.boundingBox, pointTwo))
{
//ball.position = ccp(location.x , location.y);
areWeTouchingABall = YES;
//printf("*** ccTouchesBegan (x:%f, y:%f)\n", location.x, location.y);
}
if(CGRectContainsPoint(sp.boundingBox, pointTwo)){
areWeTouchingASquare = YES;
// printf("*** ccTouchesBegan (x:%f, y:%f)\n", location.x, location.y);
}
}
}
this is in touchesMoved:
-(void) ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *touchArray = [touches allObjects];
// We're going to track the first two touches (i.e. first two fingers)
// Create "UITouch" objects representing each touch
UITouch *fingerOne = [touchArray objectAtIndex:0];
UITouch *fingerTwo = [touchArray objectAtIndex:1];
// Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
CGPoint pointOne = [fingerOne locationInView:[fingerOne view]];
CGPoint pointTwo = [fingerTwo locationInView:[fingerTwo view]];
// The touch points are always in "portrait" coordinates
// You will need to convert them if in landscape (which we are)
pointOne = [[CCDirector sharedDirector] convertToGL:pointOne];
pointTwo = [[CCDirector sharedDirector] convertToGL:pointTwo];
if (areWeTouchingABall == YES) //
{
ball.position = ccp(pointOne.x , pointOne.y);
ball.zOrder = 1;
sp.zOrder = 0;
}
if (areWeTouchingASquare == YES) //
{
sp.position = ccp(pointOne.x , pointOne.y);
sp.zOrder = 1;
ball.zOrder = 0;
}
// Only run the following code if there is more than one touch
if ([touchArray count] > 1)
{
/*if (areWeTouchingABall == YES && CGRectContainsPoint(ball.boundingBox, pointOne)) //
{
ball.position = ccp(pointOne.x , pointOne.y);
ball.zOrder = 1;
sp.zOrder = 0;
}*/
if (areWeTouchingABall == YES && CGRectContainsPoint(ball.boundingBox, pointTwo)) //
{
ball.position = ccp(pointTwo.x , pointTwo.y);
ball.zOrder = 1;
sp.zOrder = 0;
}
/*if (areWeTouchingASquare == YES && CGRectContainsPoint(ball.boundingBox, pointOne)) //
{
sp.position = ccp(pointOne.x , pointOne.y);
sp.zOrder = 1;
ball.zOrder = 0;
}*/
if (areWeTouchingASquare == YES && CGRectContainsPoint(ball.boundingBox, pointTwo)) //
{
sp.position = ccp(pointTwo.x , pointTwo.y);
sp.zOrder = 1;
ball.zOrder = 0;
}
}
}
and this is in touchesEnded:
-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
areWeTouchingABall = NO;
areWeTouchingASquare = NO;
//printf("*** ccTouchesEnded (x:%f, y:%f)\n", location.x, location.y);
}
Every time I touch anywhere with one finger, I get this error:
"Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'",
When I touch with two fingers, the error does not come up but multi touch does not work correctly ( I cannot drag a sprite with a finger each. The second sprite touched jumps straight to the other finger location so that both sprites are under one finger.)
I made sure to add the code:
[glView setMultipleTouchEnabled:YES];
to my AppDelegate.m file and I have touch enabled in my init method.
How can I fix this issue so that multi-touch works properly and the error is removed?

UITouch *fingerOne = [touchArray objectAtIndex:0];
UITouch *fingerTwo = [touchArray objectAtIndex:1];
There's no guarantee that you'll always receive two touches in a touch event. First test how many touches there are in touchArray using
UITouch *fingerOne = [touchArray objectAtIndex:0];
UITouch *fingerTwo = nil;
if (touchArray.count > 1)
{
fingerTwo = [touchArray objectAtIndex:1];
}
Even then this won't work logically because the second touch in the array may not always correspond to finger #2. I suggest to read up on tracking individual fingers here.

Related

Set SKTexture for SKSpriteNode with a given sprite.name

I've been trying to run the SKTexture change from a node on touch, said node is created with a loop:
int nameIndex = 0;
for (int i = 1; i <= 3; i++) {
self.card = [SKSpriteNode spriteNodeWithImageNamed:#"front"];
NSString *nodeName = [NSString stringWithFormat:#"node%d", nameIndex];
self.card.name = nodeName;
self.card.position = CGPointMake((((board-self.card.size.width)/3)*i), y);
[self addChild:self.card];
nameIndex++;
}
In the touches began method I have:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
for (SKNode *node in nodes) {
if ([node.name isEqualToString:#"node0"]) {
//Change the texture for the given node:
NSLog(#"Node 0");
[self.card setTexture:[SKTexture textureWithImageNamed:nameIndex]];
}
if ([node.name isEqualToString:#"node1"]) {
NSLog(#"Node 1");
}
if ([node.name isEqualToString:#"node2"]) {
NSLog(#"Node 2");
}
}
The touches began method works just fine, it runs the block as soon as one sprite is touched
however, as expected with the current code the texture change only occurs in the last created sprite. I've been looking for a way to run a method on a sprite with X given name, but I haven't fun a way.
Thanks in advance!
Turns out I needed to use the childNodeWithName instead of trying to force a conditional on the .name property
SKSpriteNode *card = (SKSpriteNode*)[self childNodeWithName:#"node0"];
[card setTexture:[SKTexture textureWithImageNamed:#"aTexture"]];
Not sure if this is a helpful question, mods feel free to erase if needed.

SKSpriteNode Targeting

So RIGHT NOW my code presents 7 playing cards. If the playing card is pressed it moves removes the card from the playerCard array and adds it to the playedCards array. The problem is, is move the card I target it with a SKNode in the touchesBegan method;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node isKindOfClass:[CardSpriteNode class]]) {
Okay, no problem, the card moves up. The problem is, All the other cards left in the playerCards now need to move i.e ( tighten up ) as they were originally. Here's my formula for distances between them;
int cardsLaid = i * (cardDisplay.size.width / 3);
So I can now get the cards that are originally displayed and the ones that are touched, to form a nice gap between them.
What I need to do is to move the cards back to a nice gap, after one of the cards between them has been moved
for (int i = 0; i < [playerCards count]; i++) {
int cardsLaid = i * (cardDisplay.size.width / 3);
CGPoint forPoint = CGPointMake((0.5 - (self.frame.size.width / 4)) + cardsLaid, 0.0);
playerCards[i] //Here I need to target each skspritenode.name for each playerCards
[??? runAction:[SKAction moveTo:forPoint duration:0.6]];
}
So I need to target each SKSpritenode.name by the names saved in the playerCards MutableArray. Then once I've targetted a spritenode I need to move it where the ??? is.
Here's my entire touchesBegan method if it helps.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node isKindOfClass:[CardSpriteNode class]]) {
int add = (int)[playerCards count] * (cardDisplay.size.width / 3);
CGPoint origPos = CGPointMake(-self.frame.size.width/2.8 + add, -218);
if ([cardsPlayed containsObject:node.name]) {
//Card is already played, return to original position and remove from array.
CGPoint point = origPos;
[node runAction:[SKAction moveTo:point duration:0.6]];
node.zPosition = zPosCount;
zPosCount += 1;
[cardsPlayed removeObject:node.name];
[playerCards addObject:node.name];
NSLog(#"this ran");
} else {
//Card is not already played, position to add card and add to array.
amountOfCardsLaid = (int)[cardsPlayed count] * (cardDisplay.size.width / 3);
CGPoint point = CGPointMake((0.5 - (self.frame.size.width / 4
)) + amountOfCardsLaid, 0.0);
[node runAction:[SKAction moveTo:point duration:0.6]];
node.zPosition = zPosCount;
zPosCount += 1;
[playerCards removeObject:node.name];
[cardsPlayed addObject:node.name];
for (int i = 0; i < [playerCards count]; i++) {
int cardsLaid = i * (cardDisplay.size.width / 3);
CGPoint forPoint = CGPointMake((0.5 - (self.frame.size.width / 4)) + cardsLaid, 0.0);
playerCards[i]
[??? runAction:[SKAction moveTo:forPoint duration:0.6]];
}
}
//Hide.Unhide buttons
if ([cardsPlayed count] == 0) {
if (addButton.hidden == FALSE) addButton.hidden = true;
if (cancelButton.hidden == FALSE) cancelButton.hidden = true;
} else {
if (addButton.hidden == TRUE) addButton.hidden = false;
if (cancelButton.hidden == TRUE) cancelButton.hidden = false;
}
}
}
The gap I need to close is on the bottom playerCards
SKNode *tempNode = [self childNodeWithName:playerCards[i]];
After a few days. fml

SKSpriteNode rotation not working properly

I'm trying to rotate a SKSpriteNode by using UIRrotationGestureRecognizer. I've implemented a code but sometimes, when I rotate the node it jumps to a rotation that isn't the one it should be. Here you have the code:
- (void) handleRotation:(UIRotationGestureRecognizer *) rotationrecognizer{
CGFloat initialrotation = 0.0;
if (rotationrecognizer.state == UIGestureRecognizerStateBegan) {
CGPoint touchLocation = [rotationrecognizer locationInView:rotationrecognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
[self selectNodeForTouch:touchLocation];
initialrotation = selected.zRotation;
}
else if (rotationrecognizer.state == UIGestureRecognizerStateChanged) {
CGFloat angle = initialrotation + rotationrecognizer.rotation;
selected.zRotation = angle;
}
}
You are resetting initial rotation to 0 on every call... you should move this to be an ivar of your view if you need it to stay resident.
The way you have it written now, the line that sets 'angle' is effectively equal to this:
CGFloat angle = 0 + rotationrecognizer.rotation;
Instead, you should do the following (where initialRotation is defined as a private ivar):
- (void) handleRotation:(UIRotationGestureRecognizer *) rotationrecognizer{
if (rotationrecognizer.state == UIGestureRecognizerStateBegan) {
CGPoint touchLocation = [rotationrecognizer locationInView:rotationrecognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
[self selectNodeForTouch:touchLocation];
_initialrotation = selected.zRotation;
}
else if (rotationrecognizer.state == UIGestureRecognizerStateChanged) {
CGFloat angle = _initialrotation + rotationrecognizer.rotation;
selected.zRotation = angle;
}
}

Creating a condition while there are more than 1 targets in a stage

I am trying to develop a game with multiple stages using cocos2d. In a level for example, I have 4 sprites, 2 white and 2 black. If the player hits the black sprites, the game is over, if he hits the white sprite, he wins. How can I implement a condition wherein if the player hits the white sprite, it checks if there are other white sprites present on the scene, if there is, the game continues. If there's none, then he goes to the stage clear scene? I tried putting the sprites in two different arrays (arrayBlack and arrayWhite) but I'm stuck with how I'm gonna make the condition for the white sprites. Can anybody please give me an idea or suggestion or a tutorial that shows a good example for this?
UPDATE:
I kind of figured it out myself. Here's my code:
-(id) init
{
if( (self=[super init]) ) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
self.isTouchEnabled = YES;
//These are declared in the .h class
blackArray = [[NSMutableArray alloc]init];
whiteArray = [[NSMutableArray alloc]init];
black1 = [CCSprite spriteWithFile:#"b1.png"];
black1.position = ccp(100, 160);
black2 = [CCSprite spriteWithFile:#"b2.png"];
black2.position = ccp(105, 150);
white = [CCSprite spriteWithFile:#"w1.png"];
white.position = ccp(150, 150);
white2 = [CCSprite spriteWithFile:#"w2.png"];
white2.position = ccp(80, 160);
[self addChild:black1 z:1 tag:1];
[self addChild:black2 z:1 tag:2];
[self addChild:white z:1 tag:3];
[self addChild:white2 z:1 tag:4];
[blackArray addObject:black1];
[blackArray addObject:black2];
[whiteArray addObject:white];
[whiteArray addObject:white2];
}
return self;}
-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSSet *allTouch = [event allTouches];
UITouch *touch = [[allTouch allObjects]objectAtIndex:0];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector]convertToGL:location];
endTouch = location;
posX = endTouch.x;
//Minimum swipe length
posY = ccpDistance(beginTouch, endTouch);
//selectedSprite is a sprite declared in .h file
if(selectedSprite.tag == 1 || selectedSprite.tag == 2)
{
//action here
}
if([whiteArray count] > 0)
{
if(selectedSprite.tag == 3 || selectedSprite.tag == 4)
{
//action here
}
[whiteArray removeObject:selectedSprite];
if([whiteArray count] == 0)
{
//Go to game over
}
}}
This doesn't look pretty but it works. Anyway, if there's a better way to implement this than how I am currently doing it please let me know.
Make your arrays (arrayBlack and arrayWhite) mutable. Then,
if(user hit sprite1)
{
if([arrayBlack containsObject:sprite1])
{
[arrayBlack removeObject:sprite1];
// Game over
}
else
{
[arrayWhite removeObject:sprite1];
if(arrayWhite.count>0)
{
// Continue game
}
else
{
// Stage clear scene
}
}
}

CGRectContainsPoint problems

In my test game i have some sprites (Bubbles = NSMutableArray) wich are appear in random location at bottom of the screen.
I have addBubble and spawBubble methods:
- (void) addBubble {
CGSize winSize = [[CCDirector sharedDirector] winSize];
bubbles = [[NSMutableArray alloc] init];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"bubbleSpriteList.plist"];
CCSpriteBatchNode *bubbleSpriteList = [CCSpriteBatchNode batchNodeWithFile:#"bubbleSpriteList.png"];
[self addChild:bubbleSpriteList];
bigBubble = [CCSprite spriteWithSpriteFrameName:#"bubble"];
[self addChild:bigBubble];
[bubbles addObject:bigBubble];
for (CCSprite *bubble in bubbles) {
int minX = bubble.contentSize.width/2;
int maxX = winSize.width-bubble.contentSize.width/2;
int rangeX = maxX - minX;
int actualX = (arc4random() % rangeX) + minX;
bubble.position = ccp(actualX, 0);
int minSpeed = 15.0;
int maxSpeed = 20.0;
int rangeSpeed = maxSpeed - minSpeed;
int actualSpeed = (arc4random() % rangeSpeed) + minSpeed;
ccBezierConfig bubblePath;
bubblePath.controlPoint_1 = ccp(200, winSize.height/3);
bubblePath.controlPoint_2 = ccp(-200, winSize.height/1.5);
bubblePath.endPosition = ccp(0, winSize.height+bubble.contentSize.height/2);
id bezierMove = [CCBezierBy actionWithDuration:actualSpeed bezier:bubblePath];
[bubble runAction:bezierMove];
}}
-(void)spawBubble:(ccTime)dt {
[self addBubble];}
Then in my init method i added background and spawBubble method with random time interval
[self schedule:#selector(spawBubble:) interval:actualTime];
I'm trying to make every bubble from Bubbles blow, when it was touched, with this code
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchLocation = [touch locationInView:[touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchLocation = [touch locationInView:[touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
for (CCSprite *bubble in bubbles) {
CGRect bubbleRect = CGRectMake(bubble.position.x - (bubble.contentSize.width/2),
bubble.position.y - (bubble.contentSize.height/2),
bubble.contentSize.width,
bubble.contentSize.height);
if (CGRectContainsPoint(bubbleRect, touchLocation)) {
NSLog(#"%i", [bubbles count]);
[bubble setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"bubbleBlow"]];
id disappear = [CCFadeTo actionWithDuration:0.1 opacity:0];
[bubble runAction:disappear];
}
}
return TRUE;}
Every bubbles blowing perfectly if only one bubble in the screen, but if one bubble on the screen and another one was appeared, only last one is detects touches.
What am i doing wrong?
Hard to tell without seeing more code - but here's where I'd start:
Above this line:
for (CCSprite *bubble in bubbles) {
add:
NSLog(#"%i", [bubbles count]);
EDIT:
Based on the code you added:
Your problem is with this line:
bubbles = [[NSMutableArray alloc] init];
which is effectively wiping out your bubbles array every time you add a new bubble.
So, there will only ever be one bubble in the array - the last one.
Hence the problem you're running into.
I changed addBubble methods by removing bubbles = [[NSMutableArray alloc] init];, and adding it to init method...
Now everything works perfectly! )