Issue with own health indicator. Cocos2d - iphone

I want to create my own health indicator by aligning multiple images which represent one percent. So basically, based on the current health, I align as many one-percent parts as needed. However, removing them seems to be a problem.
-(void)updateHealthIndicator:(ccTime)delta{
//getting health and healthReduction (removed for better readability). This part does not affect the functioning of the loop...
if(health-healthReduction > 0 ){
NSLog(#"updatehealthindicator called ! health = %d ", health);
health -= healthReduction;
[self removeChildByTag:1000 cleanup:YES];
for (int i = health; i>0; i--){
onePercent = [CCSprite spriteWithFile:#"onepercentofhi.png"];
onePercent.anchorPoint = ccp(0,0);
onePercent.position = ccp(880+(-onePercent.contentSize.width) * i,712 );
[self addChild:onePercent z:2 tag:1000];
}
}
The health indicator shows up, but it only seems to remove the first "one-percent" piece. Are all sprites with tag 1000 affected by this [self removeChildByTag:1000 cleanup:YES]; ?

Only one view with the given tag is removed.
However, you could extend CCNode with the following code to remove all children
-(void) removeChildrenByTag:(int)aTag cleanup:(BOOL)cleanup
{
NSAssert( aTag != kCocosNodeTagInvalid, #"Invalid tag");
int w=[children count]-1;
while(w>=0){
CocosNode *node=[children objectAtIndex:w];
if( node.tag == aTag ){
[self detachChild:node cleanup:cleanup];
}
w--;
}
}
Note: This is a proposed solution to be integrated into Cocos2D but hasn't made it yet.

Related

Crash due to removal of Elements like CCSprite from NSMutableArray

So, here's how it goes.
I am currently working on Cocos2d game, which consists of many Obstacles. One obstacle gets added on the screen at an interval of 10 seconds like this.
ObstacleSprite* newObstacle = [ObstacleSprite spriteWithFile:#"Obstacle.png" rect:CGRectMake(0, 0, 20, 20)];
newObstacle.position = ccp(mainPlayer1.position.x,10);
[self addChild:newObstacle];
[self.arrayForObstacles addObject:newObstacle];
Now, I insert these obstacles into the arrayForObstacles because I also want to keep checking whether the Obstacles and MainPlayer don't collide.
I check it with the help of this function.
- (void) checkCollisionWithObstacle
{
if(mainPlayer1.playerActive)
{
for(int i = 0; i < [self.arrayForObstacles count]; i++)
{
ObstacleSprite* newObstacle = [self.arrayForObstacles objectAtIndex:i];
if(newObstacle != nil)
{
if(CGRectIntersectsRect([mainPlayer1 boundingBox], [newObstacle boundingBox]))
{
mainPlayer1.livesLeft--;
}
}
}
}
}
THE ISSUE
Problem is when I get to certain score, one of the Obstacles gets deleted. Removal of Obstacles works as in First In-First Out (FIFO) mode. So, to delete obstacles, I write the following method :
- (void) keepUpdatingScore
{
//update new score
mainPlayer1.score+=10;
//remove obstacle when score increases by 5k
if(mainPlayer1.score > 5000 && mainPlayer1.score > 0)
{
mainPlayer1.playerActive = NO;
if([self.arrayForObstacles count] > 0)
{
CCLOG(#"count is %d",[self.arrayForObstacles count]);
ObstacleSprite* newObstacle = [self.arrayForObstacles objectAtIndex:0];
[self.arrayForObstacles removeObjectAtIndex:0];
[self removeChild:newObstacle cleanup:YES];
CCLOG(#"count is %d",[self.arrayForObstacles count]);
}
mainPlayer1.playerActive = YES;
}
else
{
}
It crashes when score crosses 5000 mark!
UPDATE
Crash happens when it again goes to the method checkCollisionWithObstacle.
This is the THREAD Look.
THis is the line Which crashes.
you seem to be using mainPlayer1.playerActive as a semaphore to block checking the checkCollisionWithObstacle loop from a delete in the keepUpdatingScore method (are they asynchronous ?). Assuming they are, the way you are blocking the loop access wont work if the code enters keepUpdatingScore AFTER the loop started in checkCollisionWithObstacle ... your mileage will vary.

cocos2d pixel collision detection

i am currently writing a game with a number of rectangles (say 30), if a user clicks on one of the four sides a certain action will need to be taken. Trouble is each card will have the same side which will perform this action. For example with rectangle 1 the side which will perform this action will be the left, but rectangle 3 the side will be the top side. My guess is a pixel collision detection is needed here. What do you guys think? Also is there a good example on pixel collision using cocos2s out there?
You can simply use following code
if (CGRectIntersectsRect(Rect1, Rect2))
{
//Your code ...
}
USING above CGRectIntersectsRect you can detect the collision pixels within a rectangular area.
yes good point, the solution was a little messy but it worked, this is what i did: I subclassed CCSprite and created a new attribute called state saving the current position if rotated relative to that rotation. Depending on what side was touched i would use that information to determine which side of the rectangle was touched. –
if((spriteCentre1.x < spriteCentre2.x) && (xAxisDiff < yAxisDiff))
{
[HelloWorldLayer whichSideTouched:sprite1 sideTouched:kStateRightPlus touchingSprite:sprite2];
}
+(int)whichSideTouched:(SpriteCard *)sprite sideTouched:(int)touched touchingSprite:(SpriteCard *)sprite2
{
switch (sprite.state) {
case kStateUpPlus:
case kStateUpMinus:
if(touched == kStateDownPlus)
{
NSLog(# "touched up ");
[sprite setTop:sprite2];
retValue = kStateUpPlus;
}
else if(touched == kStateRightPlus)
{
NSLog(# "touch left");
[sprite setRight:sprite2];
retValue = kStateLeftPlus;
}
}
Thanks for that Dhaval, here is the code i wrote a future reference for anyone who wishes to do similar, it can be further fine tuned, if i figure out a more accurate way of doing this i will update this thread accordingly
-(void)touchWhichSide:(CCSprite *)sprite1 collidingSprite:(CCSprite *)sprite2
{
BOOL retValue = NO;
CGPoint spriteCentre1 = sprite1.position;
CGPoint spriteCentre2 = sprite2.position;
NSLog(#"X %f",spriteCentre2.x - spriteCentre1.x);
NSLog(#"Y %f",spriteCentre1.y - spriteCentre2.y);
float xAxisDiff = spriteCentre1.x - spriteCentre2.x;
float yAxisDiff = spriteCentre1.y - spriteCentre2.y;
NSLog(#"x axis %f",xAxisDiff);
NSLog(#"y axis %f",yAxisDiff);
if((spriteCentre1.x < spriteCentre2.x) && (xAxisDiff < yAxisDiff))
{
NSLog(#"right touch");
}
else if((spriteCentre2.x < spriteCentre1.x) && (xAxisDiff > yAxisDiff))
{
NSLog(#"left touch");
}
else if((spriteCentre2.y < spriteCentre1.y) && (yAxisDiff > xAxisDiff))
{
NSLog(#"Bottom touch");
}
else if((spriteCentre1.y < spriteCentre2.y) && (yAxisDiff < xAxisDiff))
{
NSLog(#"Top touch");
}
}

CCNode with children properties and actions

I'm pretty new to cocos2d development and ran into a problem of getting a valid boundingBox and contentSize, and running CCActions on CCNodes with children. It seemed to me that if your CCNode has children and you call boundingBox (for example) on that CCNode, you should get a CGRect of that CCNode that takes into account its children. Or is it that I'm just organizing my code incorrectly..?
Anyway, I've written a small category for CCNode that seems to return the correct boundingBox and contentSize and runs actions on its children.
#implementation CCNode(Children)
- (CGRect)boundingBoxC {
if (self.boundingBox.size.width != 0 || self.boundingBox.size.height != 0) {
return self.boundingBox;
}
CGRect holderRect = CGRectZero;
for (int i = 0; i < self.children.count; i++) {
CCNode *node = [self.children objectAtIndex:i];
holderRect = CGRectUnion(holderRect, node.boundingBoxC);
}
return holderRect;
}
- (CGSize)contentSizeC {
return self.boundingBoxC.size;
}
- (void)runActionC:(CCAction *)action {
[self runAction:action];
for (int i = 0; i < self.children.count; i++) {
id action2 = [action copy];
CCNode *node = [self.children objectAtIndex:i];
[node runActionC:action2];
[actions2 release];
}
}
#end
I'd love to get some feedback on this. For example, I started by trying to use the name boundingBox instead of boundingBoxC, but wasn't confident that that was good practice (it involved swizzling). Or if there's a more comprehensive, elegant solution to this, I'd love to hear about it.
Thanks!
Think of nodes as reference points, not boxes, and you can see why the default behaviour is as it is. Your code looks clean (and glad to see it's in a category rather than a subclass!) and I can't see any problems with your logic. Definitely don't override built-in methods (unless you really need to), as 1) you could create conflicts and 2) future developers will be confused.

Cocos2d game termination problem when a moving sprite goes inside the bounding box of a animating still sprite(a ball get into a hole)

Let me explain it in depth, when ever if(CGRectContainsPoint([hole1 boundingBox], ball1.position)) condition goes true, i do lots of stuffs, like unscheduled, a selector, destroying a ball body calling an animation (please refer Code below)etc. This work properly most of the time. But sometimes when ball is really near to hole(just touching the hole but but not enough to make the above condition true), or is been throws towards the hole really fast speed, then application got terminated. I have checked, by commenting many actions which are been performed in this section, but got nothing helpful, application keep terminating when some efforts are been done to make it terminate.
if(CGRectContainsPoint([hole1 boundingBox], ball1.position))
{
ballBody->SetLinearVelocity(b2Vec2(0.0f,0.0f));
ballBody->SetAngularVelocity(0.0f);
[self unschedule:#selector(tick:)];
self.isTouchEnabled = NO;
[self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
// create the sprite sheet
CCSpriteSheet *spriteSheet;
GolfBallsAppDelegate *appDelegate = (GolfBallsAppDelegate *)[[UIApplication sharedApplication] delegate];
if([appDelegate.ballValue isEqualToString:#"cricketball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"cricket_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"ironball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"iron_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"golfball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"golf_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"soccerball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"soccer_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"basketball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"basket_ball_strip.png"];
}
spriteSheet.position = ccp(hole1.position.x,60);
[self addChild:spriteSheet];
float frameWidth = 96;
float frameHeight = 84;
CCSprite *sprite = [CCSprite spriteWithTexture:spriteSheet.texture rect:CGRectMake(0, 0, frameWidth, frameHeight)];
[spriteSheet addChild:sprite];
//if(animation)
{
// create the animation
CCAnimation *spriteAnimation = [CCAnimation animationWithName:#"potting" delay:0.1f];
int frameCount = 0;
for (int x = 0; x < 6; x++)
{
// create an animation frame
CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:spriteSheet.texture rect:CGRectMake(x*frameWidth,0*frameHeight,frameWidth,frameHeight) offset:ccp(0,0)];
[spriteAnimation addFrame:frame];
frameCount++;
// stop looping after we've added 14 frames
if (frameCount == 6)
{
//[self removeChild:spriteSheet cleanup:YES];
break;
}
}
// create the action
CCAnimate *spriteAction = [CCAnimate actionWithAnimation:spriteAnimation];
//CCRepeatForever *repeat = [CCRepeatForever actionWithAction:spriteAction];
// run the action
[sprite runAction:spriteAction];
//[sprite runAction:repeat];
}
[self schedule:#selector(loading) interval:0.5];
[self schedule:#selector(holeFinish) interval:1];
//[self removeChild:spriteSheet cleanup:YES];
}
Any suggestion will be highly appreciated.
EDIT: What i found is, problem with folling lines [self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
(MAY be). but as it not occurs always, (as I mentioned), thus it's being ridiculous.
I think your problem will be that you are trying to delete a body when the b2World is 'locked', (When the world is busy working out collisions).
Try flagging the object as ready for deletion, and deleting it at the start of your next loop:
Replace:
[self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
with
ball1.isDead = YES;
And at the start of your next game loop:
for (Ball b in balls)
{
if (b.isDead)
world->DestroyBody(b.ballBody);
}

Problem with my custom class and it's instances

I'm doing an app where user can click on a map and hear a different sound playing depending on the location clicked. I've got a point in polygon for this and it works perfectly. I've created audio playlist system also that queues audiofiles and plays them in order. This too works. However I'm trying to implement a method that checks if has already previously clicked the area/polygon he just clicked so that the same sound cannot be played again and again. I've got a PlaceInMap class that holds the polygon for an area and does the necessary checks if a clicked point is in the polygon etc etc...
At the moment I've got 2 instances of that class PlaceInMap stored in NSMutableArray and whenever a click happens I loop through that array and call each instances - (BOOL)checkCollisionWithLat:(NSString *)lat andLon:(NSString *)lon method. The method return YES or NO depending on if the click is inside the polygon. This works fine (I'm outputing the results on a label). Here's the method:
- (BOOL)checkCollisionWithLat:(NSString *)lat andLon:(NSString *)lon {
if ( [self isLocationInsideX:lat andY:lon] ) {
if ( currentlyHere == 0) {
[[appDelegate audioPlayer] addToQueue:audioFileName];
[[appDelegate audioPlayer] iterateQueue];
}
currentlyHere = 1;
return YES;
}
else {
currentlyHere = 0;
return NO;
}
}
How ever for a some reason the check for currentlyHere does not seem to work except for the first instance of the class... Partially.
This is what is happening:
I get my initial location at infinity loop 1. This is inside instance one, the sound plays. I then click to polygon on instance two and it's sound plays. Now I click again to instance one's polygon and it's sound play byt when I click again to the instance twos polygon the sound does not play. But every time after that when I click to the instance one's polygon the sound plays as it is supposed to...
Now when I remove that if ( currentlyHere == 0 ) the sounds play correctly...
currentlyHere is NSInteger but I've tried it with int and BOOL also...
This is quite strange... The textual output always shows correctly on which polygon I have clicked.
And sorry for the typos my eyes are finnished... =P
Edit:
currentlyHere is defined in the header of the class as an instance variable. This is the init of the class:
- (id) init {
if (self = [super init]) {
currentlyHere = 0;
[self setAppDelegate:[[UIApplication sharedApplication] delegate]];
}
return self;
}
It seems like the currentlyHere variable is not properly set to zero; maybe the hit detection method is not called when your are outside. Add some NSLog statements before the tests to check the value of the currentlyHere.
- (BOOL)checkCollisionWithLat:(NSString *)lat andLon:(NSString *)lon {
NSLog("In checkCollisionWithLat (%d)", currentlyHere);
if ( [self isLocationInsideX:lat andY:lon] ) {
NSLog("Inside (%d)", currentlyHere);
if ( currentlyHere == 0) {
[[appDelegate audioPlayer] addToQueue:audioFileName];
[[appDelegate audioPlayer] iterateQueue];
}
currentlyHere = 1;
return YES;
}
else {
NSLog("Outside (%d)", currentlyHere);
currentlyHere = 0;
return NO;
}
}