添加,删除和缓存SKNode

时间:2014-10-07 19:16:31

标签: caching sprite-kit exc-bad-access sknode

背景信息 我创建了一个SpriteKit 2D平台式游戏,其中包含多个" floor"。当用户进入"门户网站时#34;他被送到另一个楼层(一个上升或下一个)。如果用户死于与最近生成点不同的楼层,则将其与生成点一起运回楼层。

问题:升级到iOS8后,此方案会导致游戏崩溃并出现EXC_BAD_ACCESS异常/错误。即使经过多个教程如何调试这些错误,我似乎无法找到问题。

所以我真的很感激,如果有人可以查看我的代码并告诉我他们是否看到了更好的方式来做我正在做的事情,最好不要让游戏冻结。

游戏布局: 我有一个自定义类TileMapLayer,它基于Ray Wenderlich的书 iOS Games By Tutorials 中的同一个类。它基本上是一个包含多个32x32p磁贴的自定义SKNode类,它为我的游戏创建了背景。

我在游戏开始时加载适当的楼层,然后在我的用户上/下时调用加载另一个楼层。在这种情况下,我首先在NSDictionary中缓存当前楼层,以便稍后如果用户返回此楼层,我可以访问它。

// Cache the current floor
[_allFloors setObject:_bgLayer forKey:[NSString stringWithFormat:@"floor_%tu", (unsigned)_currentFloor]];

// Remove the current floor
[_bgLayer removeFromParent];

// Get the cached (next) floor if it exists, if not create it
TileMapLayer *cachedFloor = [_allFloors objectForKey:[NSString stringWithFormat:@"floor_%tu", (unsigned)(_currentFloor + 1)]];
if (!cachedFloor) {
    _bgLayer = [self createScenery:[NSNumber numberWithInteger:(_currentFloor + 1)]];
    NSLog(@"creating new floor");
} else {
    _bgLayer = cachedFloor;
    NSLog(@"getting cached floor");
}

// Display the new floor
[_worldNode addChild:_bgLayer];

// Increment the floor number
_currentFloor++;

(我也有类似的方法来下楼)。 在升级到iOS8之前和之后,这都很完美。

当用户死亡时,他将被运回最后一个生成点。如果最后一个生成点位于不同的楼层,则楼层也会发生相应的变化。 我把这个自定义SKAction称为球本身作为动画的一部分:

SKAction *changeFloor = [SKAction runBlock:^{
  if (self.spawnFloor != _currentFloor) { 
    [_bgLayer removeFromParent];
    _bgLayer = [_allFloors objectForKey:[NSString stringWithFormat:@"floor_%tu", (unsigned)self.spawnFloor]];
    [_worldNode addChild:_bgLayer];
    _currentFloor = self.spawnFloor;
    NSLog(@"Made it here");
  }
}];

正如你所看到的,没有多大区别。 Made it here被记录到控制台,但游戏后立即冻结。我调用的下一个方法(将球移动到正确的位置)根本不执行。

为了好玩,我尝试缓存_bgLayer,然后将其从父级中移除,如下所示:

if (self.spawnFloor != _currentFloor) {
  [_allFloors setObject:_bgLayer forKey:[NSString stringWithFormat:@"floor_%tu", (unsigned)_currentFloor]];
  ...

由于某种原因,当我这样做时游戏不会冻结。然而,地板最终被混合,好像_bgLayer从未从其父项中移除(注意:"旧的"瓷砖不再对任何物理模拟作出反应,它们是'只是在后台无所事事。)

我从父母那里删除后立即尝试[_bgLayer removeAllChildren](删除单个图块):

[_bgLayer removeFromParent];
[_bgLayer removeAllChildren];

但是当我在重生后回到这个楼层时,这会导致_bgLayer为空。好像我在将它存储在字典中之前删除了所有节点。我的猜测是字典只引用_bgLayer的位置,而不是内容本身。因此,当我删除屏幕上的所有孩子时,我也会有效地删除缓存中的所有子项。

结束语: 我知道这是一个很长的问题,如果你做到这一点,我想说谢谢你。如果您有任何疑问,请不要犹豫,在下面的评论中询问他们。 最终,我想知道的是:我如何解决我的问题,以免游戏冻结?在不引起内存问题的情况下缓存楼层的最佳做法是什么?在屏幕上删除和添加节点的最佳做法是什么,并始终将当前楼层(屏幕上的节点)称为_bgLayer

再次,非常感谢你!

1 个答案:

答案 0 :(得分:2)

在删除块内的节点时,iOS8似乎表现不同,就好像你试图在后台线程上执行它一样,有时它会导致奇怪的崩溃。这可能是一个错误,但在那之前我会建议两件事:

1.-在块内标记要删除的节点,但在update:循环中执行。你不会注意到任何差异。

2.-确保removeFromParent在主线程中发生。但不确定这会解决问题。

__strong typeof(_bgLayer) strongLayer = _bgLayer;
dispatch_async(dispatch_get_main_queue(), ^{
        [strongLayer removeFromParent];
    });