如何将两个或多个SKSpriteNode的SKActions同步?

4
我想要让两个(或更多)SKSpriteNode同步移动。任何差异都会显示出来。我尝试按顺序触发每个精灵的SKAction,当最后一个完成时触发一个新的移动。但事实证明,这些动作结束的顺序与它们开始的顺序不同,这会导致微小的时间差异,是可以察觉到的。
有没有办法在两个或多个精灵上运行持续时间相同的并行SKActions,使它们在完全相同的时间结束,或者至少按照它们开始的顺序结束?
以下是一个示例,说明了不起作用的原则:
- (void)testMethod1{
SKSpriteNode *child_1=[arrayWithSprites objectAtIndex:1];
SKSpriteNode *child_2=[arrayWithSprites objectAtIndex:2];

//This doesn't work.
[child_1 runAction:[SKAction moveToX:20.0 duration:0.5]];
[child_2 runAction:[SKAction moveToX:20.0 duration:0.5] 
    completion:^{[self testMethod1];}];
//Actions might not be finished in the order they are started.
}

以下方法我尚未尝试,但想知道是否可以解决我的问题:

- (void)testMethod2{
SKSpriteNode *child_1=[arrayWithSprites objectAtIndex:1];
SKSpriteNode *child_2=[arrayWithSprites objectAtIndex:2];

//Will this guarantee total syncronisation?
[self runAction:[SKAction group:[NSArray arrayWithObjects:
                                 [SKAction runBlock:^{[child_1 runAction:[SKAction moveToX:20.0 duration:0.5]];}],
                                 [SKAction runBlock:^{[child_2 runAction:[SKAction moveToX:20.0 duration:0.5]];}],
                                 nil]]
      completion:^{[self testMethod2];}];
}

我希望你能够理解我的英语和思路。
//Micke....

你尝试过使用group方法吗? - prototypical
3个回答

4

只需添加一个容器SKNode,然后它们就会同时移动:

SKNode *containerNode = [[SKNode alloc] init];    
[containerNode addChild:node1];    
[containerNode addChild:node2]; //add as many as necessary    
[containerNode runAction:someAction]; //declare the action you want them both to perform

这仅在精灵以完全相同的方向移动时有效,但有时我需要它们沿不同方向移动,一个沿x方向移动,另一个沿y方向移动。然后容器对我没用。这些精灵是蛇的一部分,在x/y网格内移动的段落。 - user3032732
我发现使用这个解决方案时,如果子节点已经有父节点分配,它会失败并显示以下错误:*** Terminating app due to uncaught exception 'NSInvalidArgumentException',reason: 'Attemped to add a SKNode which already has a parent。 - New2ObjectiveC

4

Tyler的解决方案完美地奏效了。

但是如果您不能或不想这样做,您应该知道,很可能操作是多线程的,因此它们可以以任何顺序完成,但它们仍应在同一帧中完成。

为确保在运行testMethod之前两个操作都已完成,您应该在didEvaluateActions中执行代码。然后,您需要找到一种方法来确定两个操作是否都已完成。一种方法是使用关键字。

[child_1 runAction:[SKAction moveToX:20.0 duration:0.5] withKey:@"action1"];
[child_2 runAction:[SKAction moveToX:20.0 duration:0.5] withKey:@"action2"]; 

然后检查这两个操作是否已经完成,检查它们是否仍然存在:

-(void) didEvaluateActions
{
    if ([child_1 actionForKey:@"action1"] == nil && 
        [child_2 actionForKey:@"action2"] == nil)
    {
        // both actions have ended, start new ones here ...
    }
}

或者你可以使用一个完成块让两个操作同时运行。增加一个NSUInteger计数器变量,在块中将计数器增加1,然后测试计数器的值是否等于(或大于)并发操作的数量。如果是,你就知道两个操作都已经完成了:

__block NSUInteger counter = 0;
void (^synchBlock)(void) = ^{
    counter++;
    if (counter == 2)
    {
        [self testMethod1];
    }
};

[child_1 runAction:[SKAction moveToX:20.0 duration:0.5] completion:synchBlock];
[child_2 runAction:[SKAction moveToX:20.0 duration:0.5] completion:synchBlock]; 

我已经用你最后一个备选方案的确切方法解决了它,即在每个操作上设置一个完成块来设置计数器,然后在调用movemethod之前等待所有计数器都设置为1。但我认为应该有比那更好的解决方案,也许是我错过了存在的多个精灵的组合动作,因此我在这里提出了问题...但也许我必须满足于完成块解决方案... - user3032732

0

你的帧率是恒定的吗?从技术上讲,你的第一个例子应该可以工作,但完成顺序可能基于其他因素,比如它们的绘制顺序。

你可以采用一种概念,即将它们暂时封装在容器节点中,然后通过动作仅移动容器节点。当动作完成后,将它们从容器中移除并返回到它们原来的父级。

你是否发现它们在视觉上移动不同步?或者你的testMethod1以某种方式要求两个动作都完成?

虽然我建议的容器概念对于你的特定示例将起作用,但除非希望对象移动得像它们连接在一起,否则这不是一个有效的选项。如果每个对象必须在1秒钟内移动到不同的位置,则容器不起作用的示例。


完成顺序可能会不同,这很可能是因为它们是独立的线程,如果最后一个线程在第一个线程结束之前结束,它会重新触发新的移动,导致视觉上不再同步。如果这种情况重复发生,不同步现象会越来越明显。我希望有一个函数可以确保操作并行执行或至少按顺序执行... - user3032732
我不确定是什么规定了顺序,值得调查,因为这将是理想的解决方案,你可以设置两者的完成方法并查看是否一致。如果一致,那么我们可能能够推断出原因。如果不一致,那么另一种方法可能更好。 - prototypical

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接