我在各个节点上运行了一些SKActions
。我怎么知道它们何时全部完成?我希望在动画运行时忽略触摸。如果我能以某种方式在多个节点上并行运行动作,我可以等待最后一个动作运行,但我没有看到任何方法来协调跨节点的动作。
我可以通过遍历所有场景的孩子并检查每个孩子的hasActions
来伪造这个。看起来有点蹩脚,但确实有效。
答案 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;任务,没有"之前"可以添加任务。
信号量不起作用,因为它不能控制排序,只是同时控制。如果节点在创建时增加了信号量,在动画时递增,并在完成后再次递增,则另一个任务只会等待所有创建的节点都是动画,并且不会再等待比第一次完成。如果节点最初没有增加信号量,那么它们中只有一个可以一次运行。
调度组的使用方式与信号量非常相似,但具有特权访问权限:节点本身不必等待。