Skip to content

Instantly share code, notes, and snippets.

@sarah-j-smith
Last active September 12, 2015 07:38
Show Gist options
  • Save sarah-j-smith/8d87e44378a77127066a to your computer and use it in GitHub Desktop.
Save sarah-j-smith/8d87e44378a77127066a to your computer and use it in GitHub Desktop.
Fade an SKNode in and out using a GKStateMachine

Control visibility of an SKNode via a State Machine

Problem with conventional approach: you want to hide a node by fading it out. But when you do that sometimes its already fading in, or partly faded out. You need to cancel any previous animation and then start the new animation.

Solution: use a state machine to capture the potential transitions between visible, fading in/out, and hidden.

To add to a node

var visibilityState: FaderStateMachine?

init() {
  super.init()
  visibilityState = FaderStateMachine(self, 0.4)
}

Then to hide:

visibilityState.enterState(Hiding)

or

visibilityState.enterState(Showing)
//
// FaderStateMachine.swift
// WordMonsters
//
// Created by Sarah Smith on 9/12/15.
// Copyright © 2015 Sarah Smith. All rights reserved.
//
import Foundation
import GameplayKit
import SpriteKit
class FaderStateMachine : GKStateMachine
{
let statesList: [GKState]
init(targetNode: SKNode, duration: NSTimeInterval)
{
statesList = [
Hidden(node: targetNode, duration: duration),
Hiding(node: targetNode, duration: duration),
Showing(node: targetNode, duration: duration),
Shown(node: targetNode, duration: duration)
]
super.init(states: statesList)
}
}
//
// Hidden.swift
// WordMonsters
//
// Created by Sarah Smith on 9/12/15.
// Copyright © 2015 Sarah Smith. All rights reserved.
//
import Foundation
import GameplayKit
import SpriteKit
class Hidden : GKState, NodeAffector
{
var targetNode : SKNode?
var fadeDuration : NSTimeInterval?
init(node: SKNode, duration: NSTimeInterval)
{
targetNode = node
fadeDuration = duration
}
override func didEnterWithPreviousState(previousState: GKState?)
{
if let actualTargetNode = targetNode {
actualTargetNode.hidden = true
actualTargetNode.alpha = 0.0
}
}
/**
The only thing a Hidden node can do is begin Showing itself.
*/
override func isValidNextState(stateClass: AnyClass) -> Bool {
return stateClass == Showing.self
}
}
//
// Hiding.swift
// WordMonsters
//
// Created by Sarah Smith on 9/12/15.
// Copyright © 2015 Sarah Smith. All rights reserved.
//
import Foundation
import GameplayKit
import SpriteKit
class Hiding : GKState, NodeAffector
{
var targetNode : SKNode?
var fadeDuration : NSTimeInterval?
init(node: SKNode, duration: NSTimeInterval)
{
targetNode = node
fadeDuration = duration
}
override func didEnterWithPreviousState(previousState: GKState?)
{
guard let actualTargetNode = targetNode else { return }
actualTargetNode.removeActionForKey("FadeInAnimation")
let fade = SKAction.fadeOutWithDuration(NSTimeInterval(fadeDuration!))
let changeState = SKAction.runBlock({
self.stateMachine?.enterState(Hidden.self)
})
let fadeAndChangeState = SKAction.sequence([fade, changeState])
actualTargetNode.runAction(fadeAndChangeState, withKey: "FadeOutAnimation")
}
/**
A node that is Hiding can do a u-turn and start Showing, or it can become Hidden.
*/
override func isValidNextState(stateClass: AnyClass) -> Bool
{
return stateClass == Showing.self || stateClass == Hidden.self
}
}
//
// NodeAffector.swift
// WordMonsters
//
// Created by Sarah Smith on 9/12/15.
// Copyright © 2015 Sarah Smith. All rights reserved.
//
import Foundation
import SpriteKit
protocol NodeAffector
{
var targetNode : SKNode? { get set }
var fadeDuration: NSTimeInterval? { get set }
}
//
// Showing.swift
// WordMonsters
//
// Created by Sarah Smith on 9/12/15.
// Copyright © 2015 Sarah Smith. All rights reserved.
//
import Foundation
import GameplayKit
class Showing : GKState, NodeAffector
{
var targetNode : SKNode?
var fadeDuration : NSTimeInterval?
init(node: SKNode, duration: NSTimeInterval)
{
targetNode = node
fadeDuration = duration
}
override func didEnterWithPreviousState(previousState: GKState?)
{
guard let actualTargetNode = targetNode else { return }
actualTargetNode.removeActionForKey("FadeOutAnimation")
let appear = SKAction.fadeInWithDuration(NSTimeInterval(fadeDuration!))
let changeState = SKAction.runBlock({
self.stateMachine?.enterState(Shown.self)
})
let fadeAndChangeState = SKAction.sequence([appear, changeState])
actualTargetNode.runAction(fadeAndChangeState, withKey: "FadeInAnimation")
}
/**
A node that is Showing can do a u-turn and begin Hiding again, or it can
become fully Shown.
*/
override func isValidNextState(stateClass: AnyClass) -> Bool
{
print("Checking if I can go from: \(stateClass) to \(self)")
return stateClass == Hiding.self || stateClass == Shown.self
}
}
//
// Shown.swift
// WordMonsters
//
// Created by Sarah Smith on 9/12/15.
// Copyright © 2015 Sarah Smith. All rights reserved.
//
import Foundation
import GameplayKit
class Shown : GKState, NodeAffector
{
var targetNode : SKNode?
var fadeDuration : NSTimeInterval?
init(node: SKNode, duration: NSTimeInterval)
{
targetNode = node
fadeDuration = duration
}
override func didEnterWithPreviousState(previousState: GKState?)
{
if let actualTargetNode = targetNode {
actualTargetNode.hidden = false
actualTargetNode.alpha = 1.0
}
}
/**
A Shown node can begin Hiding.
*/
override func isValidNextState(stateClass: AnyClass) -> Bool
{
return stateClass == Hiding.self
}
}
@sarah-j-smith
Copy link
Author

Arrgh! The sense of isValidNextState was completely wrong in the first issue of this gist. Its not what I read it as "am I a valid next state, given this source state" - its actually "given I'm the current state, can I transition to this next state".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment