Sepa cuándo se completan todas las SKActions o si no hay ninguna ejecución.

Tengo una cantidad de SKActions ejecutándose en varios nodos. ¿Cómo puedo saber cuándo están todos completados? Quiero ignorar los toques mientras se ejecutan las animaciones. Si pudiera ejecutar acciones en paralelo en una serie de nodos, podría esperar a que se ejecute una acción final, pero no veo ninguna forma de coordinar las acciones entre los nodos.

Puedo fingir esto corriendo por todos los niños de la escena y buscando hasActions en cada niño. Parece un poco cojo, pero funciona.

Que yo sepa, no hay forma de hacerlo a través de las capacidades del marco por defecto.

Sin embargo, creo que podría lograr algo como esto creando una class con methods que actúen como un contenedor para llamar a SKAction runAction: en un nodo.

En ese método de contenedor, puede insert el nodo en una matriz y luego agregar una acción performSelector a cada acción / grupo / secuencia. Entonces, cualquiera que sea el método que especifique, se llamará después de completar la acción / grupo / secuencia. Cuando se llama a ese método, puede eliminar ese nodo de la matriz.

Con esta implementación, siempre tendrá una matriz de todos los nodos que actualmente tienen una acción ejecutándose en ellos. Si la matriz está vacía, no se está ejecutando ninguna.

Cada acción que ejecutas tiene una duración. Si realiza un seguimiento de la duración de la acción de ejecución más larga, sabrá cuándo finalizará. Use eso para esperar hasta que finalice la acción más larga.

Alternativamente, mantenga un contador global de acciones en ejecución. Cada vez que ejecuta una acción que pause la input, aumente el contador. Cada acción que ejecutas necesita un bloque de ejecución final que luego disminuye el contador. Si el contador es cero, ninguna de las acciones que ignoran la input se está ejecutando.

Parece que en los dos años desde que se publicó la pregunta por primera vez, Apple no ha ampliado el marco para lidiar con este caso. Tenía dudas de hacer una gran cantidad de recorridos de charts para verificar las acciones en ejecución, así que encontré una solución usando una variable de instancia en mi subclass SKScene (GameScene) combinada con las funciones de protección de integers atómicos que se encuentran en / usr / include / libkern / OSAtomic. marido.

En mi class GameScene, tengo una variable int32_t llamada runningActionCount, inicializada a cero en initWithSize ().

Tengo dos methods GameScene:

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

Luego declaro un tipo de bloque para pasar al bloque de finalización SKNode :: runAction:

 void (^SignalActionEnd)(void); 

En mi método para iniciar las acciones en los diversos SKSpriteNodes, establezca ese bloque de finalización para apuntar al método de decremento seguro:

 SignalActionEnd = ^ { [self DecrementUILockCount]; }; 

Luego, antes de ejecutar una acción, ejecute el bloque de incremento seguro. Cuando finalice la acción, se llamará DecrementUILockCount para disminuir de forma segura el contador.

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

En mi método de actualización, simplemente controlo para ver si ese contador es cero antes de volver a habilitar la interfaz de usuario.

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

La única otra cosa a tener en count aquí es que si elimina cualquiera de los nodos que tienen acciones en ejecución antes de que se completen, el bloque de finalización también se elimina (sin ejecutarse) y su contador nunca se networkingucirá y su UI nunca -habilitar. La respuesta es comprobar si hay acciones en ejecución en el nodo que está a punto de eliminar y ejecutar manualmente el método de disminución protegida si hay alguna acción en ejecución:

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

Esto funciona bien para mí, ¡espero que ayude!

Estaba lidiando con este problema mientras jugueteaba con un juego de tipo azulejo deslizante. Quería evitar la input del keyboard y esperar el menor time posible para realizar otra acción, mientras que las fichas se estaban moviendo.

Todos los mosaicos que me preocupaban eran instancias de la misma subclass SKNode , así que decidí darle a esa class la responsabilidad de hacer un seguimiento de las animaciones en progreso y de responder a las consultas sobre si las animaciones se estaban ejecutando.

La idea que tuve fue usar un grupo de despachos para "contar" la actividad: tiene un mecanismo incorporado para ser esperado y puede agregarse en cualquier momento, de modo que la espera continuará siempre que se agreguen tareas al grupo. *

Este es un boceto de la solución. Tenemos una class de nodo, que crea y posee el grupo de envío. Un método de class privada permite que las instancias accedan al grupo para que puedan ingresar y abandonarlo cuando están animando. La class tiene dos methods públicos que permiten verificar el estado del grupo sin exponer el mecanismo real: +waitOnAllNodeMovement y +anyNodeMovementInProgress . Los primeros bloques hasta que el grupo esté vacío; el último simplemente devuelve un BOOL inmediatamente indicando si el grupo está ocupado o no.

 @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 occurnetworking. 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 

Una class de controller que quiere evitar la input de keyboard durante los movimientos puede hacer algo simple como esto:

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

y puedes hacer lo mismo en la update: una escena update: según sea necesario. Cualquier otra acción que deba suceder lo antes posible puede esperar en la animation:

 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 }); }); 

Esta es la única parte complicada / desorderada de esta solución: debido a que el método de wait... está bloqueando, obviamente tiene que suceder de forma asíncrona al hilo principal; Luego regresamos al hilo principal para hacer más trabajo. Pero lo mismo sería cierto con cualquier otro procedimiento de espera, así que esto parece razonable.


* Las otras dos posibilidades que se presentaron fueron una queue con un bloque de barrera y un semáforo de conteo.

La barrera Block no funcionaría porque no sabía cuándo podría encojarla. En el momento en que decidí poner en queue la tarea "después", no se podían agregar tareas "antes".

El semáforo no funcionaría porque no controla el order, solo la simultaneidad. Si los nodos incrementaron el semáforo cuando se crearon, disminuyeron al animar y se incrementaron nuevamente cuando terminaron, la otra tarea solo esperaría si todos los nodos creados estuvieran animando y no esperarían más de la primera vez. Si los nodos no incrementaron el semáforo inicialmente, solo uno de ellos podría funcionar a la vez.

El grupo de despacho se usa mucho como un semáforo, pero con acceso privilegiado: los propios nodos no tienen que esperar.

La forma más sencilla de hacerlo es usar un grupo de envío . En Swift 3 esto parece

 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() } } 

Antes de ejecutar cada acción, llamamos a group.enter() , agregando esa acción al grupo. Entonces, dentro de cada controller de finalización de acción, llamamos a group.leave() , sacando esa acción del grupo.

El bloque group.notify() se ejecuta después de que todos los demás bloques hayan salido del grupo de envío.