添加,删除和缓存SKNode [英] Adding, Removing, and Caching SKNodes

查看:90
本文介绍了添加,删除和缓存SKNode的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景信息: 我正在创建一个带有多个地板"的SpriteKit 2D平台风格的游戏.当用户进入门户"时,他被运送到另一层(一个向上或一个向下).如果用户死于与最近的生成点所在的楼层不同的楼层,则将其与生成点一起转移回该楼层.

问题:升级到iOS8后,此情况导致游戏崩溃并出现EXC_BAD_ACCESS异常/错误.即使经过有关如何调试此类错误的多个教程,我似乎也找不到问题所在.

因此,如果有人可以看一下我的代码并告诉我他们是否看到一种更好的方式来做我正在做的事情(最好不会导致游戏死机),我将不胜感激.

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

我在游戏开始时加载适当的楼层,然后在用户上/下时调用以加载另一个楼层.在这种情况下,我首先将当前楼层缓存在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从未被从其父级移除的混合在一起(请注意:旧"图块不再对任何物理模拟有反应,它们只是在后台无所事事). /p>

从父级移除后,我还尝试了[_bgLayer removeAllChildren](移除单个图块):

[_bgLayer removeFromParent];
[_bgLayer removeAllChildren];

但是,当我重生后返回此楼层时,这会导致_bgLayer为空.好像我删除所有节点,然后再将它们存储在字典中一样.我的猜测是字典仅引用_bgLayer的位置,而不引用内容本身.因此,当我删除屏幕上的所有子级时,我也会有效地删除缓存中的所有子级.

总结: 我知道这是一个很长的问题,如果您能做到这一点,我想说谢谢.如果您有任何疑问,请随时在下面的评论中提问. 最终,我想知道的是:如何解决我的问题,使游戏不会冻结?在不引起内存问题的情况下缓存楼层的最佳实践是什么?删除和添加屏幕上的节点,并始终将当前楼层(屏幕上的楼层)称为_bgLayer的最佳做法是什么?

再次感谢您!

解决方案

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

1.-在块内标记要删除的节点,但在update:循环上进行.您将不会发现任何差异.

2.-确保removeFromParent在主线程中发生.但是不确定这是否可以解决问题.

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

Background info: I'm creating a SpriteKit 2D platform-style game with multiple "floors". When the user enters a "portal" he is transported to another floor (either one up or one down). If the user dies at a different floor from where the most recent spawnpoint is, he is transported back to the floor with the spawnpoint.

Problem: After upgrading to iOS8, this scenario causes the game to crash with an EXC_BAD_ACCESS exception/ error. Even after going through multiple tutorials on how to debug such errors, I cannot seem to find the problem.

So I'd really appreciate if someone could take a look at my code and tell me if they see a better way of doing what I'm doing, preferably without causing the game to freeze.

Game layout: I have a custom class TileMapLayer which is based on the same class from Ray Wenderlich's book iOS Games By Tutorials. It is basically a custom SKNode class containing multiple 32x32p tiles, combined creating the background for my game.

I load the appropriate floor when the game begins, and then call to load another floor when my user goes up/down. In this scenario, I cache the current floor first in a NSDictionary so I can access it later if the user returns to this floor.

// 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++;

(I have a similar method for going down a floor as well). This works perfectly, both before and after upgrading to iOS8.

When the user dies, he is transported back to the last spawnpoint. If the last spawnpoint is on a different floor, the floor also changes appropriately. I call this custom SKAction on the ball itself as a part of an animation:

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");
  }
}];

As you can see there isn't much difference. The Made it here gets logged to the console, but the game freezes immediately after. The next method I call (to move the ball to the correct location) is not executed at all.

Just for fun I tried caching the _bgLayer before removing it from its parent like so:

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

And for some reason the game does not freeze when I do this. However, the floors end up being mixed as if the _bgLayer never was removed from its parent (note: the "old" tiles does no longer react to any physics-simulations, they're just in the background doing nothing).

I also tried [_bgLayer removeAllChildren] (removing the individual tiles) right after removing from the parent:

[_bgLayer removeFromParent];
[_bgLayer removeAllChildren];

But this causes the _bgLayer to be empty when I return to this floor after respawning. As if I removed all the nodes before I stored it in the dictionary. My guess is that the dictionary only references the location of the _bgLayer, not the content itself. So when I remove all the children on screen, I effectively remove all of them in the cache as well.

Wrapup: I know this is a long question, and I'd like to say thank you if you made it this far. If you have any questions, please don't hesitate to ask them in the comments below. Ultimately, what I'd like to know is this: How can I resolve my problem so the game doesn't freeze? What's the best practice for caching the floors without causing memory problems? What's the best practice for removing and adding the nodes on screen, and always referring to the current floor (the one that's on screen) as _bgLayer?

Again, thank you so much!

解决方案

iOS8 seems to behave different when removing nodes inside blocks, as if you tried to do it on a background thread so sometimes it causes strange crashes. It might be a bug but until then I'd suggest two things:

1.- Mark the node to be removed inside the block but do it on the update: loop. You will not notice any difference.

2.- Make sure the removeFromParent happens in the main thread. But not sure this will fix the problem.

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

这篇关于添加,删除和缓存SKNode的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆