8

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

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

4

5 回答 5

3

最简单的方法是使用调度组。在 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()块在所有其他块离开调度组之后运行。

于 2017-09-08T02:37:16.960 回答
2

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

但是,我认为您可以通过创建一个类来实现类似的效果,该类的方法充当调用SKAction runAction:节点的包装器。

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

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

于 2013-11-26T04:45:13.953 回答
1

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

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

于 2013-11-26T09:37:55.933 回答
1

自从这个问题首次发布以来的两年里,苹果似乎还没有扩展框架来处理这种情况。我犹豫要不要做一堆图遍历来检查正在运行的动作,所以我找到了一个解决方案,它使用了我的 SKScene 子类 (GameScene) 中的实例变量以及 /usr/include/libkern/OSAtomic 中的原子整数保护函数。 H。

在我的 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];
}

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

于 2015-08-13T18:44:26.653 回答
0

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

我关心的所有图块都是同一SKNode个子类的实例,因此我决定让该类负责跟踪正在进行的动画,并响应有关动画是否正在运行的查询。

我的想法是使用调度组来“计数”活动:它有一个内置的等待机制,并且可以随时添加,这样只要添加了任务,等待就会继续到组。*

这是解决方案的草图。我们有一个节点类,它创建并拥有调度组。私有类方法允许实例访问组,以便他们在制作动画时可以进入和离开。该类有两个公共方法,允许在不暴露实际机制的情况下检查组的状态:+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...方法是阻塞的,它显然必须与主线程异步发生;然后我们回到主线程做更多的工作。但是任何其他等待程序也是如此,所以这似乎是合理的。


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

屏障 Block 不起作用,因为我不知道什么时候可以真正入队。在我决定将“之后”任务排入队列时,无法添加“之前”任务。

信号量不起作用,因为它不控制顺序,只是同时性。如果节点在创建时增加信号量,在制作动画时减少,并在完成后再次增加,则其他任务只会在所有创建的节点都在制作动画时等待,并且不会等待比第一次完成更长的时间。如果节点最初没有增加信号量,那么一次只有一个节点可以运行。

调度组的使用很像信号量,但具有特权访问权限:节点本身不必等待。

于 2016-03-04T19:22:16.227 回答