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.