I have an arcade style iPhone game that is built with Cocos2d-iPhone and works fine except that it randomly crashes for no apparent reason after an unpredictable amount of playing. Sometimes you can play several levels without a problem and others it crashes after a few seconds. There are a few common lines reported in XCode as usually EXC_BAD_ACCESS which would suggest a memory thing but the objects are all autorelease with extra retains and the crashes are very inconsistent. One thing that seems to fit is that as more is going on at once the likelihood of a crash increases which again suggests some memory issue but then there is no app memory warning fired.
Some example crash lines are:
if(gameGlobals.gameState == GAME_STATE_PAUSED) {...}
if(![hearts isKindOfClass:[CCSprite class]]) {...}
but both these lines have global objects that are fune for ages until the crash.
So, my question is really how do I go about tracking down the problem and are there any likely culprits?
Thanks.
UPDATE
I have been doing a bit of detective work and I at least have some consistency. I think that there are two things going on:
1) is that the maths used to detect a pixel perfect collision with an object with deletion of pixel data is cooking the CPU.
2) I have some zombie sprites and otherwise that are being released with out my realising.
When I add a bullet sprite I use:
CCSprite *bullet = [CCSprite spriteWithFile:@"bullet.png"];
bullet.tag = BULLET_TAG_GOODY;
bullet.position = ccp(player.position.x,50);
[self addChild:bullet];
[bullet runAction:[CCSequence actions:
[CCMoveTo actionWithDuration:2 position:ccp(player.position.x,320)],
[CCCallFuncN actionWithTarget:self selector:@selector(bulletMoveFinished:)],
nil]];
[bullets addObject:bullet];
And then I loop through the bullets array. At the moment it crashes saying that I am trying to access a deallocated instance.
So, 1) how do I set this up so my sprites don't get released? Is it a good idea to remove the autorelease from CCSprite? 2) What is the best way to detect bullets colliding with a space invaders style base attriting the sprite as it gets shot?
Currently:
if([self overlapBases: bullet.position.x :bullet.position.y :true]) {...}
-(bool)overlapBases :(GLfloat)x :(GLfloat)y :(bool)up {
// NSLog(@"overlapping - %f,%f",x,y);
if(y > 20 && y < 100) {
int bn = -1;
if(x > 28 && x < 118) {
bn = 0;
}
if(x>140 && x<230 ) {
bn = 1;
}
if(x>254 && x<343 ) {
bn = 2;
}
if(x>365 && x<453 ) {
bn = 3;
}
if(bn> -1) {
NSLog(@"overlapping - %f,%f",x,y);
// for (int ix = 0; ix < NUM_BASES; ix++) {
if (overLap(x, 2, aspeaker[bn].position.x, BASE_WIDTH * aspeaker[bn].scale)) {
if (overLap(y, 4, aspeaker[bn].position.y, BASE_HEIGHT * aspeaker[bn].scale)) {
NSLog(@"ix: %i, x: %f, y: %f",bn,x,y);
return [self fineCollision:bn :x :y :up];
}
}
}
}
return false;
}
-(bool)fineCollision :(GLuint)baseNum :(GLfloat)x :(GLfloat)y :(bool)up {
//convert bullet x and y passed in to this base coords
//check if solid
//if so, remove bits in gfx and array, return true
//else return false;
GLfloat bullx = (x - aspeaker[baseNum].position.x + BASE_WIDTH) / 2.0;
GLfloat bully = (y - aspeaker[baseNum].position.y + BASE_HEIGHT) / 2.0;
GLint testx = (GLint)bullx;
if ((testx < 0) | (testx >= BASE_WIDTH)) {
return false;
}
GLuint testy;
bool hit = false;
for (int iy = -2; iy < 2; iy++) {
testy = (GLint)(bully - iy);
if ((testy >= 0) & (testy < BASE_HEIGHT)) {
if (baseShape[baseNum][15 - testy][testx] > 0) {
if(showrm == YES) {
CCSprite *maskSprite = [CCSprite spriteWithFile:@"bullet-mask.png"];
[maskSprite setBlendFunc: (ccBlendFunc) {GL_ZERO, GL_ONE_MINUS_SRC_ALPHA}];
maskSprite.position = ccp( x , y-(40+22-5) );
NSLog(@"sprite: %f",maskSprite.position.x);
NSLog(@"render mask: %f",rm.frameInterval);
[rm addSpriteMask:maskSprite];
[rm drawCurrent];
}
[self remove:testx :testy :baseNum :up];
GLuint seed = rand()%64;
if (seed & 1) {
[self remove:testx - 1:testy - 1 :baseNum :up];
}
if (seed & 2) {
[self remove:testx - 1:testy :baseNum :up];
}
if (seed & 4) {
[self remove:testx - 1:testy + 1:baseNum :up];
}
if (seed & 8) {
[self remove:testx + 1:testy - 1:baseNum :up];
}
if (seed & 16) {
[self remove:testx + 1:testy :baseNum :up];
}
if (seed & 32) {
[self remove:testx + 1:testy + 1:baseNum :up];
}
hit = true;
}
}
}
return hit;
}
- (void)remove:(GLint)offX :(GLint)offY :(GLuint)baseNum :(bool)up {
if ((offX < 0) | (offX >= BASE_WIDTH)) {
return;
}
if ((offY < 0) | (offY >= BASE_HEIGHT)) {
return;
}
baseShape[baseNum][15 - offY][offX] = 0;
}
bool overLap(GLfloat x1, GLfloat w1, GLfloat x2, GLfloat w2) {
GLfloat left1, left2;
GLfloat right1, right2;
GLfloat halfWidth1 = w1 * 0.5f;
GLfloat halfWidth2 = w2 * 0.5f;
left1 = x1 - halfWidth1;
left2 = x2 - halfWidth2;
right1 = x1 + halfWidth1;
right2 = x2 + halfWidth2;
if (left1 > right2) {
return false;
}
if (right1 < left2) {
return false;
}
return true;
}
For detection and a rendermask think using lots of sprites and their blend mode for the attrition.
Thanks.
UPDATE AGAIN :)
Ok, I found some Zombies!
Address Category Event Type RefCt Timestamp Size Responsible Library Responsible Caller
0 0x13a00e90 CCSprite Malloc 1 00:32.974.212 432 SpacedInvaders +[CCSprite spriteWithFile:]
1 0x13a00e90 CCSprite Autorelease <null> 00:32.974.235 0 SpacedInvaders +[CCSprite spriteWithFile:]
2 0x13a00e90 CCSprite Retain 2 00:32.974.546 0 SpacedInvaders -[CCArray insertObject:atIndex:]
3 0x13a00e90 CCSprite Retain 3 00:32.974.629 0 SpacedInvaders -[CCActionManager addAction:target:paused:]
4 0x13a00e90 CCSprite Retain 4 00:32.974.634 0 SpacedInvaders -[CCArray addObject:]
5 0x13a00e90 CCSprite Release 3 00:32.986.279 0 QuartzCore CA::Display::DisplayLink::dispatch(unsigned long long, unsigned long long)
6 0x13a00e90 CCSprite Release 2 00:33.074.889 0 SpacedInvaders -[CCArray removeObject:]
7 0x13a00e90 CCSprite Release 1 00:33.074.915 0 SpacedInvaders -[CCActionManager deleteHashElement:]
8 0x13a00e90 CCSprite Release 0 00:33.074.918 0 SpacedInvaders -[CCArray removeObject:]
9 0x13a00e90 CCSprite Zombie -1 00:33.074.939 0 SpacedInvaders -[GameLayer update:]
So what's going on here? I see that the retain count has gone too low but how do I avoid this. I assume I am doing something dumb btw.
Thanks.