0
\$\begingroup\$

UPDATE: It no longer crashes after removing [self removeAllChildrenWithCleanup:YES]; from onExit. The artifacts still remain though.

UPDATE 2: Found my problems. See my answer below.

I have a game with a level select managed by a SceneManager, which basically just uses ReplaceScene. The first time I load a level everything works fine. On subsequent calls, for example: completing the level and continuing to the next, things blow up. The level loads fine, but when I try to pan the map or try to move the player the game crashes. Debugging through I found that there are multiple occurrences of self and related children like player and mapLayer.

As a test, I put this code in my ccTouchesBegan:

NSLog(@"test %i", [self retainCount]);

The first time a level is loaded, it gives:

test 2

The second time I load a level it gives:

test 2
test 1

as in it spits out both values by looping through twice, not just appending an output to the last. It continues with this pattern for each subsequent load. So the third time will give 2 1 1.

Particular code that causes the game to crash involve calling _tileMap.tileSize because there is a second GameScene with a tileMap that was supposedly destroyed, so it has tileSize and mapSize of 0.

I noticed dealloc doesn't really ever get called, so I tried to manage some things with -(void) onExit

-(void) onExit
{
    [self unscheduleAllSelectors];
    [_player stopAllActions]; //stop any animations just in case. normally handled in ccTouchesEnded
    [self removeAllChildrenWithCleanup:YES]; 
}

I never replace the GameScene while I'm in a GameScene; if the level is completed it goes to a GameOver scene, or I use a back button that goes to the LevelSelect scene.

This is [the relevant parts of] my init, in case something like the adding of children matters:

-(id) init
{        
    _mapLayer = [CCLayer node];

    //load data for level
    GameData *gameData = [GameDataParser loadData];
    int selectedChapter = gameData.selectedChapter;
    int selectedLevel = gameData.selectedLevel;
    Levels *chapterLevels = [LevelParser loadLevelsForChapter:selectedChapter];

    //loop until we get selected level, then do stuff
    for (Level *level in chapterLevels.levels) {
        if (level.number == selectedLevel) {
            //load the level map                
            _tileMap = [CCTMXTiledMap tiledMapWithTMXFile:level.file];
        }
    }

    _background = [_tileMap layerNamed:@"Background"];
    _foreground = [_tileMap layerNamed:@"Foreground"];
    _meta = [_tileMap layerNamed:@"Meta"];
    _meta.visible = NO;

    //initialize Spawn Point object and place player there
    CCTMXObjectGroup *objects = [_tileMap objectGroupNamed:@"Objects"];
    NSAssert(objects != nil, @"'Objects' object group not found");
    NSMutableDictionary *spawnPoint = [objects objectNamed:@"SpawnPoint"];
    NSAssert(spawnPoint != nil, @"SpawnPoint object not found");
    int x = [[spawnPoint valueForKey:@"x"] intValue] / retinaScaling;
    int y = [[spawnPoint valueForKey:@"y"] intValue] / retinaScaling;

    //setup animations
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"MouseRightAnim_24x21.plist"];
    CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:@"MouseRightAnim_24x21.png"];
    [_mapLayer addChild:spriteSheet z:1];
    NSMutableArray *rightAnimFrames = [NSMutableArray array];
    for(int i = 1; i <= 3; ++i) {
        [rightAnimFrames addObject:
         [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
          [NSString stringWithFormat:@"MouseRight%d_24x21.png", i]]];
    }        
    CCAnimation *rightAnim = [CCAnimation animationWithSpriteFrames:rightAnimFrames delay:0.1f];        
    self.player = [CCSprite spriteWithSpriteFrameName:@"MouseRight2_24x21.png"];
    _player.position = ccp(x, y);
    self.rightAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:rightAnim]];        
    rightAnim.restoreOriginalFrame = NO;
    [spriteSheet addChild:_player];

    //get map size in pixels
    mapHeight = _tileMap.contentSize.height;
    mapWidth = _tileMap.contentSize.width;
    //setup defaults
    //this value works well for the calculation later, trial and error really
    distance = 150;
    lastGoodDistance = 150;
    mapScale = 1;

    [self setViewpointCenter:_player.position];

    [_mapLayer addChild:_tileMap];
    [self addChild:_mapLayer z:-1];

    self.isTouchEnabled = YES;
}
return self;
}

And here's the SceneManager code for replacing scenes:

+(void) goGameScene
{
    CCLayer *gameLayer = [GameScene node];
    [SceneManager go:gameLayer:[GameHUD node]];
}

//this is what every call looks like besides the GameScene one above
+(void) goLevelSelect
{
    [SceneManager go:[LevelSelect node]:nil];
}

+(void) go:(CCLayer *)layer: (CCLayer *)hudLayer
{
    CCDirector *director = [CCDirector sharedDirector];
    CCScene *newScene = [SceneManager wrap:layer:hudLayer];
    if ([director runningScene]) {
        [director replaceScene:newScene];
    }
    else {
        [director runWithScene:newScene];
    }
}

+(CCScene *) wrap:(CCLayer *)layer: (CCLayer *)hudLayer
{
    CCScene *newScene = [CCScene node];
    [newScene addChild: layer];
    if (hudLayer != nil) {
        [newScene addChild: hudLayer z:1];
    }    
    return newScene;
}

Any ideas why I'm getting these fatal artifacts? I'm hoping this isn't considered too localized since it basically combines 3 tutorials that anyone could end up following. (Ray Wenderlich Animations, Tim Roadley Scene Manager, Pan and Zoom with Tiled Maps.

\$\endgroup\$
3
  • \$\begingroup\$ Do you happen to use blocks in any of the codes further in your class? If you do, please provide those as well. There are cases where blocks causes a retain loop which essentially prevents the object from being deallocated so that might be the problem. Plus, I don't see the code for the replace scene, that might be useful to see as well. \$\endgroup\$
    – Jamornh
    Oct 26, 2012 at 16:51
  • \$\begingroup\$ @Jamornh Nope, no blocks anywhere. And I'll add the replace scene code to the question. \$\endgroup\$ Oct 26, 2012 at 16:52
  • \$\begingroup\$ Ok, so I fixed the crashing by removing [self removeAllChildrenWithCleanup:YES]; from onExit because it was causing an over-release problem with _tileMap. However, the problem of the artifacts still remains, just without fatal (for now) consequences. For now the problem has been demoted to a memory leak problem. \$\endgroup\$ Oct 26, 2012 at 17:56

1 Answer 1

0
\$\begingroup\$

Found my problems.

I had to add [super onExit] to onExit. Missing this was causing dealloc to not fire.

I also had to remove the following lines from dealloc:

self.tileMap = nil;       
self.background = nil;
self.meta = nil;
self.foreground = nil;
self.fogOfWar = nil;    
self.mapLayer = nil;

They are all the ones related to the tilemap, and they were causing over-releases once I was able to hit dealloc. It's weird though; they're declared as retain in the header file.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .