Cocos2d: Black screen when removing sprites - cocos2d-iphone

I'm currently doing the tutorial http://www.raywenderlich.com/25736/how-to-make-a-simple-iphone-game-with-cocos2d-2-x-tutorial .
I have a problem on the part that reacts to when a ninja star hits the monsters. My code is:
- (void)update:(ccTime)dt {
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (CCSprite *projectile in _projectiles) {
NSMutableArray *monstersToDelete = [[NSMutableArray alloc] init];
for (CCSprite *monster in _monsters) {
if (CGRectIntersectsRect(projectile.boundingBox, monster.boundingBox)) {
[monstersToDelete addObject:monster];
}
}
for (CCSprite *monster in monstersToDelete) {
[_monsters removeObject:monster];
[self removeFromParentAndCleanup:YES];
}
if (monstersToDelete.count > 0) {
[projectilesToDelete addObject:projectile];
}
[monstersToDelete release];
}
for (CCSprite *projectile in projectilesToDelete) {
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}
which works okay, does not crash, but when I hit a monster with an projectile, the screen turns black on the simulator. No error or anything. I logged the CGRectIntersectRect, and it records as it is supposed to. The problem is that when this happens, it all turns black. Any idea why?

I looked at the tutorial, and the line i identified in the comments above reads :
[self removeChild:monster cleanup:YES];
Try that.

You're doing [self removeFromParentAndCleanup:YES] which removes your current layer from the parent. So you get a black screen.
You probably want to remove the child monster from the layer instead.

Related

EXC BAD ACCESS With Cocos2d pushscene that works for blank scene

So I am trying to do a levelup screen with pushscene/popscene. The pushscene works when it's a blank scene, but not when it's my scene that I want. My scene/layer loads completely with all images and text displaying their exact correct content. After all of the images load there is a EXC BAD ACCESS that doesn't seem to be linked to any particular message being sent. Any help or further diagnostic tests would be appreciated.
I have a version where I commented out the sprites and labels and it still crashes. Is there something big that I'm missing?
EDIT: I've added the [self = [super init]] and [super onEnter] methods and still same problem. It's something else. Any ideas?
EDITEDIT: I think this has something to do with the optionsArray I'm using, not sure what objects need to be retained. The array is a CCArray and contains NSDictionaries of differing capacities
#import "LevelupLayer.h"
#import "GameManager.h"
#implementation LevelupLayer
#synthesize optionsArray,spritesArray;
#synthesize confirmLabel;
#synthesize counter;
+(id) scene {
CCScene *scene = [CCScene node];
CCLayer* layer = [LevelupLayer node];
[scene addChild:layer];
return scene;
}
-(void)onEnter
{
counter = 1; // for debugging
//Detemine what levelups are possible
GameManager* gm = [GameManager sharedManager]; //GameManager is a helper that oversees communication between layers and plists
optionsArray = [gm possibleLevelups]; //Access plist and formats data into expected format
[optionsArray retain];
int numPossibilities = [optionsArray count];
//Build Levelup layer based on possible options
CGSize size = [[CCDirector sharedDirector] winSize];
//float positionIncrement = (size.width / numPossibilities) - ((size.width/numPossibilities) * 0.5);
float positionIncrement = (size.width / numPossibilities);
float stripWidth = size.width / numPossibilities;
for (int i = 0; i < numPossibilities; i++) {
int slot = i+1;
NSDictionary* optionDict = [optionsArray objectAtIndex:i];
NSString* name = [optionDict objectForKey:#"name"];
NSString* title = [optionDict objectForKey:#"title"];
NSString* description = [optionDict objectForKey:#"description"];
// Add the sprite
CCSprite* optionSpite = [CCSprite spriteWithSpriteFrameName:[NSString stringWithFormat:#"%#.png",name]];
[self addChild:optionSpite];
[spritesArray addObject: optionSpite];
optionSpite.position = CGPointMake(slot * positionIncrement, size.height*0.60);
[optionSpite setAnchorPoint:CGPointMake(0.5f, 0.5f)];
// Add the description
CCLabelBMFont *optionDescription = [CCLabelBMFont labelWithString:description fntFile:#"bodyFont.fnt" width:stripWidth alignment:kCCTextAlignmentCenter];
[self addChild:optionDescription];
optionDescription.position = CGPointMake(slot * positionIncrement, size.height*0.30);
[optionDescription setAnchorPoint:CGPointMake(0.5f, 0.5f)];
// Add the title
CCLabelBMFont *optionTitle = [CCLabelBMFont labelWithString:title fntFile:#"titleFont.fnt" width:stripWidth alignment:kCCTextAlignmentCenter];
[self addChild:optionTitle];
optionTitle.position = CGPointMake(slot * positionIncrement, size.height*0.90);
[optionTitle setAnchorPoint:CGPointMake(0.5f, 0.5f)];
}
[self scheduleUpdate]; //Update only prints counter to see how many frames it lasts
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
return YES;
}
-(void) update:(ccTime)delta
{
CCLOG(#"counter: %d",counter);
counter++;
}
-(void) onExit
{
[optionsArray release];
}
#end
`
i don't see any [super onEnter]; or [super init] or anything like that ...that's your problem
First add [super onEnter] on the first line in onEnter.
Second add an init method like this:
-(id)init{
if (self=[super init]){}
}
Third add [super onExit] at the end of your onExit method
I figured it out, it wasn't anything to do with the code I posted, so sorry about that. It was just a stupid release call to a non-retained array. Retained the array previously and it worked fine. Sorry about crying wolf

Cocos2d Changing Scenes: BAD_ACCESS Exception

I have sincerely tried to understand the error from any remotely related question I have seen here. However, the issue that I am having is that when I try to transition from my level1 scene to levelselector scene, the game crashes with the bad access exception. The menus and transitions worked flawlessly while I was using them among main menu, credits, levelselector and all.
Now I do know that the error is on the main thread.
The selector shows that it is at
int retVal = UIApplicationMain(argc, argv, nil, #"AppDelegate");
now on my level1 scene, I do have three thread using [self schedule] and I have put in the corresponding [self unschedule];
I also have a NSMutableArray which I tried releasing and pointing to nil but I get another error. I put it in the dealloc method of the scene. I did the same with the [self unschedule] as well. Is there a better place to put it?
here is how it looks like, had forgotten to retain the stuff. release is working fine now but still crashes right after the transition. the scene loads as in I can see it, but then it crashes right away.
[enemies release];
[player release];
[accel release];
[pausedLabel release];
[pausedLayer release];
[health release];
[score release];
enemies = nil;
player = nil;
accel = nil;
pausedLabel = nil;
pausedLayer = nil;
health = nil;
score = nil;
[self unschedule:#selector(updateGameLogic)];
[self unschedule:#selector(throwEnemey)];
[self unschedule:#selector(enemyShoot)];
[super dealloc];
I used [[Class init] alloc] retain] this time around for each of the objects above

Animations are not working properly(Cocos2d)

My problem is that animation are not working properly during movements of sprite.
Below is the code which i'm using
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
[selSprite resumeSchedulerAndActions];
CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
[self selectSpriteForTouch:touchLocation];
return TRUE;
}
- (void)selectSpriteForTouch:(CGPoint)touchLocation
{
CCSprite * newSprite = nil;
for (CCSprite *sprite in movableSprite) {
if (CGRectContainsPoint(sprite.boundingBox, touchLocation)) {
newSprite = sprite;
break;
}
}
if (newSprite != selSprite) {
[selSprite stopAllActions];
selSprite = newSprite;
_MoveableSpritetouch = TRUE;
}
if(_MoveableSpritetouch==TRUE)
{
movement=0;
CGRect selRect=CGRectMake((SpriteX)-20.0,(SpriteY)-20.0,40.0,40.0);
if(CGRectContainsPoint(selRect, touchLocation))
{
[selSprite stopAllActions];
}
if((selSprite==MarshallCar)&& (!(CGRectContainsPoint(selRect, touchLocation))))
{
movement=1;
[self reorderChild:selSprite z:5];
NSMutableArray *MarshallCarWalkAnimFrames = [NSMutableArray array];
for(int i = MarshallCarTouchStartFrameIndex; i <= MarshallCarTouchEndFrameIndex; ++i) {
[MarshallCarWalkAnimFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"mcar_move_%d.png", i]]];
}
MarshallCarWalkAnim = [CCAnimation animationWithFrames:MarshallCarWalkAnimFrames delay:MarshallCarTouchFrameDelay];
walkMarshallCar = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:MarshallCarWalkAnim restoreOriginalFrame:NO]];
[selSprite runAction:walkMarshallCar];
}
}
}
- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
if(gameState == TRUE){
CGPoint point = [touch locationInView:[touch view]];
point = [[CCDirector sharedDirector] convertToGL:point];
if (moveDifference.x>0)
{
selSprite.flipX = YES;
}
else {
selSprite.flipX = NO;
}
[selSprite setPosition:point];
}
}
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
movement=0;
if(selSprite==MarshallCar)
{
[selSprite setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"mcar_idle.png"]];
}
[selSprite pauseSchedulerAndActions];
}
The animation frames for movement are not playing every time during movements sometimes it plays or sometimes not. It plays properly when you touch and move your sprite for the first time but if touch another sprite and then again move the previous sprite the animations for movement won't play.
Is anyone having any idea why this is happening?
Please tell me the proper code for removing this bug.
Thanks!!!
I believe your problem is the if/else if construct:
if (_MoveableSpritetouch==TRUE)
{
CGRect selRect = CGRectMake(SpriteX - 20, SpriteY - 20, 40, 40);
if(CGRectContainsPoint(selRect, touchLocation))
{
[selSprite stopAllActions];
}
else if(...)
{
...
[selSprite runAction:walkMarshallCar];
}
}
If you don't see it right away: if the touch location is inside the selRect, you call stopAllActions on the selected (new) sprite and do nothing else. Only if the touch location is not within that rectangle you'll run the animation action.
I think the "in rectangle" check is superfluous since you've already called stopAllActions anyway a few lines above.
Allow me a few general remarks about your code:
The method "selectSpriteForTouch" tells me that you're selecting a new sprite. The function does that. But it does not advertise playing an animation. You might want to refactor this out to a seperate "playAnimationOnSelectedSprite" method.
You wrote 20.0 and 40.0 several times. This means you're actually passing a double (8 bytes floating point data type) to a CGPoint which takes floats (4 bytes floating point). Strictly speaking use either 20.0f with the suffixed "f" to denote it as a floating point data type, or use integers since you don't use the floating point part.
Why you put (SpriteX) in brackets is not clear to me, if you want to enhance readability you'll achieve more by adding spaces after commas and operands.
In Objective-C, use YES and NO macros instead of TRUE and FALSE.
The bool variable _MoveableSpritetouch seems superfluous, unless needed someplace else. In any case you should move the following if(_MoveableSpritetouch==TRUE) block to where you set the _MoveableSpritetouch variable to TRUE because it just makes your code harder to read by setting a variable, leaving the code block you were in ( if(selSprite != newSprite) ) just to jump into another block of code ( if(_MoveableSpritetouch==TRUE) ) that you already know you're going to run anyway.
if((selSprite==MarshallCar)&& (!(CGRectContainsPoint(selRect, touchLocation))))
{
movement=1;
[self reorderChild:selSprite z:5];
NSMutableArray *MarshallCarWalkAnimFrames = [NSMutableArray array];
for(int i = MarshallCarTouchStartFrameIndex; i <= MarshallCarTouchEndFrameIndex; ++i) {
[MarshallCarWalkAnimFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"mcar_move_%d.png", i]]];
}
MarshallCarWalkAnim = [CCAnimation animationWithFrames:MarshallCarWalkAnimFrames delay:MarshallCarTouchFrameDelay];
walkMarshallCar = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:MarshallCarWalkAnim restoreOriginalFrame:NO]];
[selSprite runAction:walkMarshallCar];
}
I have added [selSprite stopAllActions];
and it started working correctly because in the touch ended method i was pausing the actions
but not resuming them so when i touch the sprite for the second time it was not playing animation because the action was paused.

Cocos2D removeChildByTag

I am making my first cocos2D game and i had trouble in tag. i gonna add many sprite to my gamelayer so i used [self addChild:sprite z:1 tag:aTag]; where aTag +=1; each time i increase the tag value. because each sprite should have a unique tag value. sometime i wanna clear all the child in my gamelayer so i remove those sprite by using the tag value like this.
for (int i=10; i<1000; i++) {
CCNode *child = [self getChildByTag:i];
if (child == nil)
NSLog(#"removeChildByTag: child not found!");
else{
NSLog(#"child removed");
[self removeChild:child cleanup:YES];
child=nil;
}
}
and when i add these sprite again like [self addChild:sprite z:1 tag:aTag] to my gamelayer there was error occured "EXE bad Access". why it show the error.
you can directly remove a child by using
[self removeChildByTag:aTag cleanup:YES];
as for the bad access, check if sprite is empty or if the image is empty
Use to define SAFE_REMOVE like this
#define SAFE_REMOVE(p) if (p) [p removeFromParentAndCleanup:YES];
// Remove the the tag
CCNode* node = [self getChildByTag:YOURTAGNAMEHERE];
if (node != nil)
{
SAFE_REMOVE(node);
}

Cocos2d: Preloading animation causes a crash

I am trying to preload an animation in the init method of my layer. I then call the animation if the screen is touched. The app crashes with no error message as soon as I touch the screen and seems it is to do with calling the preloaded animation. I would like to do it this way as it seems expensive to create the animation every time the screen is touched - which does seems to work though. Any tips greatly appreciated.
Sample Code:
In my header:
#interface Test : CCLayer {
NSMutableArray *wake;
CCSprite* ani;
CCAnimate *animate;
}
#end
In my implementation:
-(id) init {
if( (self=[super init])) {
// enable touches
self.isTouchEnabled = YES;
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"ani.plist" texture:[[CCTexture2D alloc] initWithImage:[UIImage imageNamed:#"ani.png"]]];
ani = [CCSprite spriteWithSpriteFrameName:#"ani1.png"]; //comes from .plist file
ani.anchorPoint=ccp(0,0);
ani.position = ccp(700,65);
[self addChild:ani z:30];
wake = [NSMutableArray array];
for(int i = 1; i <= 4; i++) {
[wake addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"ani%d.png",i]]];
}
animate = [CCAnimate actionWithAnimation:[CCAnimation animationWithFrames:wake delay:1.0f] restoreOriginalFrame:FALSE];
}
return self;
}
Handling the touch:
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// run the animation
[ani runAction:animate];
}
Animations in Cocos2d are not designed to be reused. You need to create a new one every time.
Problem solved by creating properties for the array and animation on the class using nonatomic,retain.
You only need to retain the animation but the array can be local.
self.myAnimation = [[CCAnimation animationWithFrames:myAniFramesArray delay:0.1f] retain];
Remember to make the property nonatomic, retain as stated by Chev and to release any objects you retain in the appropriate dealloc method.