为了进一步熟悉SpriteKit,我制作了一个简单的小应用程序,可以在你点击或触摸的任何位置(在7秒后自动消失)产生随机(3个选项)球,然后应用物理和重力,并以设定的间隔操纵重力完成。这很简单,工作得很好。但是,我决定给它添加声音,这样当撞击墙壁或彼此时,球会产生噪音。
当工作时,它提出了一个我自己无法解决的问题:如果你产生了无法处理它的那么多球,那么无声版将会滞后,但声音版本继续滞后。我检查了Xcode可靠的Debug Navigator,发现应用程序的内存继续扩展,每个球都添加到场景中。无声的版本并没有在内存上扩展得那么多,并且在将球从父母身上移除之后还收回了一些内容。
我不禁想到我的实现中出现了一些错误,它不仅会在声音文件被使用后保留在内存中,还会保留每个文件的倍数。
我很乐意与任何要求的人分享我的项目,但这是所发生方法的近似值:
初始化,声音动作被创建并存储在场景类中,以便以后可以访问它们:
@interface REP_Balls () {
SKAction* ballSound01;
}
@end
@implementation REP_Balls {
...
...
...
-(void) setUpSounds {
NSArray* array = @[ @"ball_hit_01.wav",]; //I have more in the array, I'm just simplifying code for stackoverflow
ballSound01 = [SKAction playSoundFileNamed:[array objectAtIndex:0] waitForCompletion:NO];
}
然后我有一个联系人监听器激活一个方法,该方法从可用的声音动作中随机化并返回其中一个:
-(void) didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *firstBody, *secondBody;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
SKAction* randomSound;
//blah blah blah if categories match and whatnot, do this:
REP_BallSpawn* ball = (REP_BallSpawn*) firstBody.node;
randomSound = [self soundBankRandomizer:YES];
[ball runAction:randomSound];
随机化的方法:
-(SKAction*) soundBankRandomizer:(BOOL)isWallHit {
NSArray* array;
switch (isWallHit) {
case YES:
array = @[ //according sound actions
];
break;
case NO:
array = @[
ballSound01, //and other sound actions
];
default:
array = @[
//if neither case is true somehow, just choose from a bank of all sound actions
];
break;
}
int randomChoice = arc4random() % [array count];
SKAction* sound = (SKAction*)[array objectAtIndex:randomChoice];
return sound;
}
除此之外,7秒后球会自动消失,我会假设有任何孩子(我还假设应该包括动作,例如声音动作)。
我甚至做了一个方法来确认没有节点在它们消失后没有粘附:
[self enumerateChildNodesWithName:@"//*" usingBlock:^(SKNode *node, BOOL *stop) {
string = [NSString stringWithFormat:@"%@ (())(()) %@", string, node];
}];
然后将结果复制到剪贴板,从而生成场景中所有节点和子节点的列表。 (“(())(())”在那里是为了在find / replace中搜索一个独特的模式来制作新行。
编辑:我也注意到在所有事情都已经消亡之后CPU使用率仍然很高。
请告诉我,我做错了什么!
=============================================== ====
所以我对代码做了一些更改。如下所述,我让它正常工作,但经过一些进一步的测试,它究竟是如何实现的似乎是挑剔的。也许其他人会明白为什么,但这对我来说毫无意义。
可行的方法
与上述相同的更改:
-(void) setUpSounds {
NSArray* array = @[
@"ball_hit_01.m4a",
@"ball_hit_02.m4a",
@"ball_hit_03.m4a",
@"ball_hit_04.m4a",
@"wall_hit_01.m4a",
@"wall_hit_02.m4a",
@"floor_hit_01.m4a",
@"floor_hit_02.m4a",
];
SKAction* ballSound01 = [SKAction playSoundFileNamed:[array objectAtIndex:0] waitForCompletion:NO];
// etc
sounds = [NSArray arrayWithObjects:ballSound01, ballSound02, ballSound03, ballSound04, wallSound01, wallSound02, floorSound01, floorSound02, nil]; //is declared class wide
}
-(SKAction*) soundBankRandomizer:(BOOL)isWallHit {
int randomChoice = arc4random() % ([sounds count] / 2);
if (isWallHit) {
randomChoice += ([sounds count] / 2);
}
return [sounds objectAtIndex:randomChoice];
}
联系人监听器几乎完全相同(我删除了球的转换并将操作应用于世界节点)。我也尝试使用字典而不是数组,它工作正常。
方法不适用于某些原因
我会删除soundBankRandomizer方法,以及从联系人侦听器中删除任何操作调用。相反,我添加了这个方法:
-(void)playSound:(BOOL)isWallHit {
int randomChoice = arc4random() % ([sounds count] / 2);
if (isWallHit) {
randomChoice += ([sounds count] / 2);
}
SKAction* randomSound = [sounds objectAtIndex:randomChoice];
[bgNull runAction:randomSound];
}
然后通过以下方式从联系人监听器调用它:
[self playSound:YES]; //for wall hit
[self playSound:NO]; //for ball on ball action
出于某种原因,这导致了我最初遇到的完全相同的性能问题。这对任何人都有意义吗?!
答案 0 :(得分:1)
我看到了几个问题....通过宣布SKAction ballSound01,它会坚持下去再次使用。此外,我按照你的想法,通过删除每个节点,它将删除每一个动作,但我再次认为,通过声明它,这将不会发生。此外,SKActions可以应用于多个节点,因此我认为您不能真正将它们视为所运行对象的子项。
我认为你最好声明一个固定在那些SKActions上的数组,而不是每次在soundBankRandomizer中创建它。
此外,我从未见过带有bool变量的switch语句。只是这样做......
if (isWallHit == YES) {
} else{
}
更重要的是因为默认块永远不会运行。因为isWallHit是YES或NO,所以这两个条件就是它,不需要默认值。
您可能需要考虑将Singleton类作为声音管理器,并使其与节点完全分离。所以不要打电话......
randomSound = [self soundBankRandomizer:YES];
[ball runAction:randomSound];
你可以打电话给......
[[SoundManager sharedSounds] playRandomWallSound];
无需使特定节点成为播放声音的一部分。