知道所有SKActions何时完成或没有任何运行

时间:2013-11-26 02:06:43

标签: ios cocoa-touch animation sprite-kit skaction

我在各个节点上运行了一些SKActions。我怎么知道它们何时全部完成?我希望在动画运行时忽略触摸。如果我能以某种方式在多个节点上并行运行动作,我可以等待最后一个动作运行,但我没有看到任何方法来协调跨节点的动作。

我可以通过遍历所有场景的孩子并检查每个孩子的hasActions来伪造这个。看起来有点蹩脚,但确实有效。

5 个答案:

答案 0 :(得分:2)

据我所知,通过默认框架功能无法做到这一点。

但是,我认为你可以通过创建一个方法来实现这样的事情,这些方法充当在节点上调用SKAction runAction:的包装器。

在该包装器方法中,您可以将节点推送到数组中,然后将performSelector操作附加到每个操作/组/序列。因此,在完成动作/组/序列后调用您指定的任何方法。调用该方法时,您只需从数组中删除该节点即可。

通过此实现,您将始终拥有一个当前在其上运行操​​作的所有节点的数组。如果数组为空,则没有运行。

答案 1 :(得分:2)

最简单的方法是使用dispatch group。在Swift 3中,这看起来像

func moveAllNodes(withCompletionHandler onComplete:(()->())) {
    let group = DispatchGroup()
    for node in nodes {
        let moveAction = SKAction.move(to:target, duration: 0.3)
        group.enter()
        node.run(moveAction, completion: { 
            ...
            group.leave()
        }
    }
    group.notify(queue: .main) {
        onComplete()
    }
}

在运行每个操作之前,我们调用group.enter(),将该操作添加到组中。然后在每个动作完成处理程序中,我们调用group.leave(),将该动作从组中取出。

group.notify()块在所有其他块离开调度组后运行。

答案 2 :(得分:1)

您运行的每个操作都有一个持续时间。如果您跟踪最长的运行动作的持续时间,您就知道它何时完成。使用它等到最长的运行动作结束。

或者,保持运行操作的全局计数器。每次运行暂停输入的操作都会增加计数器。您运行的每个操作都需要一个最终执行块,然后减少计数器。如果计数器为零,则没有任何输入忽略操作正在运行。

答案 3 :(得分:1)

看起来在这个问题首次发布后的两年内,Apple还没有扩展框架来处理这个案例。我在做一堆图遍历来检查运行动作时犹豫不决,所以我找到了一个解决方案,使用我的SKScene子类(GameScene)中的实例变量和/ usr / include / libkern / OSAtomic中的原子整数保护函数。小时。

在我的GameScene类中,我有一个名为runningActionCount的int32_t变量,在initWithSize()中初始化为零。

我有两种GameScene方法:

-(void) IncrementUILockCount
{
    OSAtomicIncrement32Barrier(&runningActionCount);
}

-(void) DecrementUILockCount
{
    OSAtomicDecrement32Barrier(&runningActionCount);
}

然后我声明一个块类型传递给SKNode :: runAction完成块:

void (^SignalActionEnd)(void);

在我的方法中,在各种SKSpriteNodes上启动操作,将该完成块设置为指向安全递减方法:

SignalActionEnd = ^
{
    [self DecrementUILockCount];
};

然后在我启动操作之前,运行安全增量块。操作完成后,将调用DecrementUILockCount以安全地减少计数器。

[self IncrementUILockCount];
[spriteToPerformActionOn runAction:theAction completion:SignalActionEnd];

在我的更新方法中,我只是在重新启用UI之前检查该计数器是否为零。

if (0 == runningActionCount)
{
    // Do the UI enabled stuff
}

此处唯一需要注意的是,如果您在完成之前删除了任何已运行操作的节点,则还会删除完成块(不会运行),并且您的计数器永远不会减少并且您的UI永远不会重新启用。答案是检查要删除的节点上的运行操作,如果有任何操作正在运行,则手动运行受保护的减量方法:

if ([spriteToDelete hasActions])
{
    // Run the post-action completion block manually.
    [self DecrementUILockCount];
}

这对我来说很好 - 希望它有所帮助!

答案 4 :(得分:0)

我正在处理这个问题,同时摆弄滑动式游戏。我想既防止键盘输入又等待尽可能短的时间来执行另一个动作,而瓷砖实际上正在移动。

我所关注的所有图块都是同一个SKNode子类的实例,因此我决定让该类具有跟踪正在进行的动画的可靠性,以及响应有关动画是否正在运行的查询

我的想法是使用调度组来计算"计算" activity:它有一个等待的内置机制,可以随时添加,这样只要任务被添加到组中,等待就会继续。*

这是解决方案的草图。我们有一个节点类,它创建并拥有调度组。私有类方法允许实例访问组,以便它们在动画时可以进入和离开。该类有两个公共方法,可以在不暴露实际机制的情况下检查组的状态:+waitOnAllNodeMovement+anyNodeMovementInProgress。前者阻止直到该组为空;后者只是立即返回一个BOOL,指示该组是否忙碌。

@interface WSSNode : SKSpriteNode

/** The WSSNode class tracks whether any instances are running animations,
 *  in order to avoid overlapping other actions. 
 *  +waitOnAllNodeMovement blocks when called until all nodes have 
 *  completed their animations.
 */
+ (void)waitOnAllNodeMovement;

/** The WSSNode class tracks whether any instances are running animations,
 *  in order to avoid overlapping other actions. 
 *  +anyNodeMovementInProgress returns a BOOL immediately, indicating 
 *  whether any animations are currently running.
 */
+ (BOOL)anyNodeMovementInProgress;

/* Sample method: make the node do something that requires waiting on. */
- (void)moveToPosition:(CGPoint)destination;

@end
@interface WSSNode ()

+ (dispatch_group_t)movementDispatchGroup;

@end

@implementation WSSNode

+ (void)waitOnAllNodeMovement
{
    dispatch_group_wait([self movementDispatchGroup], 
                        DISPATCH_TIME_FOREVER);
}

+ (BOOL)anyNodeMovementInProgress
{
    // Return immediately regardless of state of group, but indicate 
    // whether group is empty or timeout occurred.
    return (0 != dispatch_group_wait([self movementDispatchGroup], 
                                     DISPATCH_TIME_NOW));
}

+ (dispatch_group_t)movementDispatchGroup
{
    static dispatch_group_t group;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        group = dispatch_group_create();
    });

    return group;
}

- (void)moveToPosition:(CGPoint)destination
{   
    // No need to actually enqueue anything; simply manually
    // tell the group that it's working.
    dispatch_group_enter([WSSNode movementDispatchGroup]);
    [self runAction:/* whatever */
         completion:^{ dispatch_group_leave([WSSNode movementDispatchGroup])}];
}

@end

想要在移动过程中阻止键盘输入的控制器类可以做这样简单的事情:

- (void)keyDown:(NSEvent *)theEvent
{
    // Don't accept input while movement is taking place.
    if( [WSSNode anyNodeMovementInProgress] ){
        return;
    }
    // ...
}

你可以根据需要在场景update:中做同样的事情。必须尽快发生的任何其他操作都可以等待动画:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        [WSSNode waitOnAllNodeMovement];
        dispatch_async(dispatch_get_main_queue(), ^{
          // Action that needs to wait for animation to finish
     });
});

这是此解决方案中一个棘手/混乱的部分:因为wait...方法是阻塞的,所以它显然必须与主线程异步发生;然后我们回到主线程做更多的工作。但是对于任何其他等待程序也是如此,所以这似乎是合理的。

*另外两种可能性是带有障碍块和计数信号量的队列。

障碍阻挡不起作用,因为我不知道何时可以将它排队。在我决定将"排在"之后#34;任务,没有"之前"可以添加任务。

信号量不起作用,因为它不能控制排序,只是同时控制。如果节点在创建时增加了信号量,在动画时递增,并在完成后再次递增,则另一个任务只会等待所有创建的节点都是动画,并且不会再等待比第一次完成。如果节点最初没有增加信号量,那么它们中只有一个可以一次运行。

调度组的使用方式与信号量非常相似,但具有特权访问权限:节点本身不必等待。