我在玩一个滑动式游戏时正在处理这个问题。我既想阻止键盘输入,又想等待尽可能短的时间执行另一个操作,同时瓷砖实际上正在移动。
我关心的所有图块都是同一 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 不起作用,因为我不知道什么时候可以真正将它排入队列。在我决定将“之后”任务排入队列时,无法添加“之前”任务。
信号量不起作用,因为它不控制顺序,只是同时性。如果节点在创建时增加信号量,在制作动画时减少,并在完成后再次增加,则其他任务只会在 所有 创建的节点正在制作动画时等待,并且不会等待比第一次完成。如果节点最初没有增加信号量,那么它们一次只能运行一个。
调度组的使用很像信号量,但具有特权访问权限:节点本身不必等待。