Sequencing Multiple Node Animations In Swift

Jul 30, 2015

In learning Swift 2.0 I am writing little game which hopefully will be shipping when iOS 9 does.

One of the things I needed to do was to sequence animations on groups of nodes which is something SpriteKit does not provide any support for. It has lots of ways to group and sequence animations on a single node but you can't run animations on several nodes, then other animations on other nodes when the first set is finished, and so on. So I came up with these two simple classes.

import SpriteKit

class Animator
{
  typealias functionType = (Void -> Void)?
  var function : functionType
  var parentNode : SKNode

  init (function:functionType, parent : SKNode)
  {
    self.function = function
    self.parentNode = parent
  }

  func execute()
  {
    if let f = function
    {
      f()
    }
  }

  func isDone() -> Bool
  {
    var count = parentNode.children.count
    for node in parentNode.children
    {
      if !node.hasActions()
      {
        count--;
      }
    }
    return count==0;
  }
}

class AnimatorSequence
{
  var animatorStack = [Animator]()
  var currentAnimator : Animator?

  func startAnimator()
  {
    currentAnimator = animatorStack.removeAtIndex(0)
    currentAnimator!.execute()
  }

  func addAnimator( animator : Animator )
  {
    animatorStack.append(animator)
    if currentAnimator==nil && animatorStack.count == 1
    {
      startAnimator();
    }
  }

  func process()
  {
    if let anim = currentAnimator
    {
      if anim.isDone()
      {
        currentAnimator = nil
        if animatorStack.count > 0
        {
          startAnimator();
        }
      }
    }
  }

  func somethingAnimating() -> Bool
  {
    return !(currentAnimator==nil)
  }
}

The Animator class is given a closure and a parent node. The AnimationSequencer continuously executes any available Animation until it is complete (the process function is called in update(:)). The closure can animate on any group of nodes as long as they are contained in the parent node—this version determines completion when there are no more animating nodes in the parent. Of course that could be defined in some other way but it works for my needs. Since this all happens in the main thread there is no issue with synchronization.

I actually use multiple AnimationSequencers with different parent nodes (in my case a scoring root node and a board root node) which can operate independently.

As an example here is my score display animation. It's not very interesting as it only animates a single node but there can be several of these queued up to run serially.

func showScoringMessage(scoreValue:Int, lengths:[Int])
{
  let a = Animator(function: { () -> Void in

    let m = self.scoringParentNode!.childNodeWithName("Message") as! SKLabelNode

    let seq = SKAction.sequence(
      [
        SKAction.fadeInWithDuration(0.2),
        SKAction.waitForDuration(1.2),
        SKAction.fadeOutWithDuration(0.2)
      ]
    )
    var s = "\(lengths[0])"
    for (index,value) in lengths.enumerate()
    {
      if index>0
      {
        s += "X\(value)"
      }
    }
    m.text = "\(s) for \(scoreValue) points"
    m.runAction(seq)

    }
    , parent : scoringParentNode!
  )
  scoreAnimationSequencer.addAnimator(a)

}

In my board sequencer I animate a group of nodes, then fade them out, then fade in new nodes all in a loop. Each loop also triggers animations in the scoring sequencer which runs at its own pace.

Swift 2.0 can often be a pain since it is so new and still changing. One sample code I was looking at released at WWDC last month is already uncompilable as further betas changed syntax. But I'm getting a better idea of how to work with it.