I'm writing a game that has a number of switch sprites that can be moved by the game player.I was intending to use a 'Game-play-kit' state machine to organize my code. I can't figure out how to manage multiple state machines - specifically I store my switches in an array, and each switch object includes a statemachine - how do I reference the 'parent' switch from within GKState classses in order to change it's properties(in this case running a new animation?)
This is my switch class:
class RailSwitch: SKSpriteNode {
var switchID: Int
var currentSwitchPosition: switchPosition
var initialSwitchPosition: switchPosition
var switchLocation: CGPoint
var isSwitchLocked: Bool
var isLeftMiddleSwitch: Bool
var currentAnimation: switchAnimation /* this is a dictionary of animation textures */
var stateMachine : GKStateMachine!
init(switchID: Int,
switchLocation: CGPoint,
initialSwitchPosition: switchPosition,
isSwitchLocked: Bool,
isLeftMiddleSwitch: Bool,
currentAnimation: switchAnimation,
texture:SKTexture!) {
self.switchID = switchID
self.switchLocation = switchLocation
self.initialSwitchPosition = initialSwitchPosition
self.currentSwitchPosition = initialSwitchPosition
self.isSwitchLocked = isSwitchLocked
self.isLeftMiddleSwitch = isLeftMiddleSwitch
self.currentAnimation = currentAnimation
super.init (texture: texture!, color: UIColor.clearColor(), size: texture!.size())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
This is my switch Network class:
class SwitchNetwork {
var level : Int
var switchNetwork = [RailSwitch]()
var railSwitchAnimation : [switchAnimationState: switchAnimation]
init (level:Int) {
self.level = level
self.switchNetwork = []
self.railSwitchAnimation = [:]
}
func initialiseSwitches() {
/* one test example switch - in practice there will be many*/
railSwitchAnimation = loadSwitchAnimations()
switchNetwork.append(RailSwitch(switchID: 1,
switchLocation: CGPointMake(400,300),
initialSwitchPosition: (.left) ,
isSwitchLocked: false,
isLeftMiddleSwitch: true,
currentAnimation: railSwitchAnimation[.left]!,
texture: railSwitchAnimation[.left]!.textures[0]
))
}
I initiate the switches from within GameScene:
func initiateSwitchNetwork() {
for thisSwitch in 0 ... switches.switchNetwork.count - 1 {
switches.switchNetwork[thisSwitch].stateMachine = GKStateMachine(states: [
GameStart(scene: self),
SwitchLeft(scene: self),
SwitchRight(scene: self),
SwitchMiddle(scene: self),
SwitchLeftLocked(scene: self),
SwitchRightLocked(scene: self),
SwitchMiddleLocked(scene: self)])
switches.switchNetwork[thisSwitch].stateMachine.enterState(GameStart)
}
Here's my question.From within the switch statemachine gkstate classes, who do I change the animation?I need to access the parent switch object that holds the statemachine somehow?
class GameStart: GKState {
unowned let scene: GameScene
init(scene: SKScene) {
self.scene = scene as! GameScene
super.init()
}
override func didEnterWithPreviousState(previousState: GKState?) {
// scene.addChild(scene.switches.switchNetwork[0].currentAnimation.textures[0])
}
}
One approach to consider is instead of passing the scene into each state's init function, you could pass a reference to the parent switch instead? So your state's init function looks like this;
init(switch: RailSwitch) {
self.railSwitch = switch
super.init()
}
Then your RailSwitch might have a function to change the animation which you would call in the state's updateWithDeltaTime function.
override func updateWithDeltaTime(seconds: NSTimeInterval) {
self.railSwitch.changeAnimation(to switchTexture: .left)
}
Note that you have access to the stateMachine in each state;
override func updateWithDeltaTime(seconds: NSTimeInterval) {
self.stateMachine?.enterState(SwitchRight.self)
}
As an aside, I would prefer to use Strategy Pattern to implement this kind of functionality, unless a switches next state is strongly determined by current state. Strategy is better suited where an external factor will determine the next change.
Related
I'm developing a SpriteKit Game using the GKStateMachine for transitioning between multiple scenes. I'm using a class that holds an instance of the StateMachine, which has multiple states. The states themselves have access to the current SKView through an initializer property. The States are responsible for presenting scenes, so they have a scene property, which will be presented on the didEnter-Method.
Now I have a MainMenuState, which has a scene with 3 buttons. For passing the button events to the State, I wrote a custom delegate. The MainMenuState implements the delegate protocol and sets the scene's delegate property to 'self'. So when a user hits a button the action is forwarded to a delegate method. I wanted to use the delegate method to transition to the next state (e.g. SettingsState). However, when I try to access the GKStates StateMachine property within the delegate func it's always nil.
I think that I have a design problem here and I don't know how to solve it.
The Code of the MainMenuState
import Foundation
import GameplayKit
class MainMenuState : GKState, MenuSceneDelegate {
var view: SKView
var scene: GKScene?
init(view: SKView) {
self.view = view;
self.scene = GKScene(fileNamed: "MenuScene")
super.init()
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is MultiplayerHostState.Type ||
stateClass is MultiplayerSearchState.Type ||
stateClass is SettingsState.Type
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = self.scene {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as! MenuScene? {
// Set delegate
sceneNode.menuDelegate = self
// Copy gameplay related content over to the scene
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
sceneNode.size = view.bounds.size
// Present the scene
if let view = self.view as SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
}
func hostGameClicked() {
if let stateMachine = self.stateMachine {
stateMachine.enter(MultiplayerHostState.self)
}
}
func joinGameClicked() {
if let stateMachine = self.stateMachine {
stateMachine.enter(MultiplayerSearchState.self)
}
}
func settingsClicked() {
// <-- Here the StateMachine is nil -->
if let stateMachine = self.stateMachine {
stateMachine.enter(SettingsState.self)
}
}
}
After some research I found out this behavior was caused by Swifts ARC System.
The class holding the reference of the state machine was only declared within a func of the overall ViewController. So after existing the func, the class and state machine were deallocated.
I solved it within the View Controller:
class ViewController {
var classWithStateMachine: ClassWithStateMachine?
func initializeClassWithStateMachine {
self.classWithStateMachine = ClassWithStateMachine()
}
}
This code snippet is just a demonstration of the concept, no real code.
This is probably 2 swift questions in one...
How do I solve a situation where I want to extend an existing base class (UIView in my case) with functionality that requires stored properties? ...so that I can reuse the code for other classes?
I have tried to solve it through composition below, but I don't know if there is a more obvious way that I just can't see as I am fairly new to swift...
The second question:
In my implementation I have an abstract class ManagedComponentImpl which needs an eventReceiver object which is going to be the containing UIView subclass.
The problem I have with my implementation is that swift forces me to define an object binding where Receiver:NSObject for ManagedComponentImpl, so that I can declare the optional variable eventReceiver as weak. (and I guess I would create a memory leak otherwise). However I would want to use this implementation on a variety of objects (which could of course all inherit NSObject, but they do not actually need to for other reasons but this, so it seems odd). So question number 2: Is there a way to avoid this?
EDIT: And yes! I made a mistake mixing model and view code here, but I guess the fundamental problem remains when you switch UIViewController for UIView :-)
public protocol ManagedConnection {
var connectionKey:String { get set }
}
public protocol ManagedComponent: ConnectionObserver {
var connectionKey:String { get set }
func connectTo()
func disconnectFrom()
}
public protocol EventReceiver: ConnectionObserver {
var variableSet:Set<VariableID>? { get }
var handleVariableUpdates: ((Set<VariableID>)->Void)? { get }
}
class ManagedComponentImpl<Receiver: EventReceiver> where Receiver:NSObject {
public var _connectionKey: String = Shared
//The connection Key
public var connectionKey: String
{
set {
disconnectFrom()
self._connectionKey = newValue
connectTo()
}
get {
return _connectionKey
}
}
// The varset needed by this control
weak var eventReceiver:Receiver!
// handler for the status pane variables
//
var connectionObserverHandlerID:UInt16 = 0
var eventHandlerID:UInt16 = 0
public init(receiver:Receiver) {
self.eventReceiver = receiver
}
public func connectTo() {
guard let manager = Connections.shared[self.connectionKey] else { return }
let connection = manager.connection
// disconnect any previous connections
disconnectFrom()
// Connect the connection observer
connectionObserverHandlerID = connection.addConnectionObserver(observer: eventReceiver)
if let variableSet = eventReceiver.variableSet, let handler = eventReceiver.handleVariableUpdates {
eventHandlerID = connection.requestVariables(variables: variableSet, handler: handler)
}
}
public func disconnectFrom(){
guard let manager = Connections.shared[self.connectionKey] else { return }
let connection = manager.connection
// Disconnect
if connectionObserverHandlerID != 0 {
connection.removeConnectionObserver(id: connectionObserverHandlerID)
}
if eventHandlerID != 0 {
connection.unRequestVariables(ident: eventHandlerID)
}
}
}
class ManagedUIView: UIView, ManagedComponent, EventReceiver {
private var component:ManagedComponentImpl<ManagedUIView>!
public var variableSet:Set<VariableID>?
public var handleVariableUpdates:((Set<VariableID>)->Void)?
public override init(frame: CGRect) {
super.init(frame: frame)
component = ManagedComponentImpl<ManagedUIView>(receiver: self)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
component = ManagedComponentImpl<ManagedUIView>(receiver: self)
}
var connectionKey:String {
set {
component.connectionKey = newValue
}
get {
return component.connectionKey
}
}
func connectTo() {
component.connectTo()
}
func disconnectFrom() {
component.disconnectFrom()
}
func notifyState(state: ConnectionState) {}
}
Okay - for everybody reading this, the answers are:
- The problem should probably be solved by a delegate and not by inheritance.
- To avoid inheriting from NSObject: the problem seems to be that protocols can not only be implemented by classes. Therefore the protocol needs a class limitation to work as weak references. As a result ManagedComponentImpl does not need to be generic any more and I can just have a weak CAPEvent receiver optional.
I am trying to make the game settings which loads, saves itself, and makes it singleton class. All my efforts lead to failure, XCode asks me "Cannot invoke initializer for type "Settings" with no arguments". How can I fix this?
This is the code:
class Settings: NSObject, NSCoding {
static let sharedInstance = Settings()
var currentLevel: Int
var positionOfPlayer: [Int]?
var sounds: Bool
var shape: String
var completedLevels: [Int: Bool]
init?(currentLevel: Int, positionOfPlayer: [Int]?, sounds: Bool, shape: String, completedLevels: [Int: Bool]) {
self.currentLevel = currentLevel
self.sounds = sounds
self.shape = shape
self.completedLevels = completedLevels
if let position = positionOfPlayer as [Int]? {
self.positionOfPlayer = position
}
super.init()
}
required convenience init?(coder aDecoder: NSCoder) {
let currentLevel = aDecoder.decodeObjectForKey(SettingNames.nameOfCurrentLevel) as? Int
let completedLevels = aDecoder.decodeObjectForKey(SettingNames.nameOfCompletedLevels) as? [Int: Bool]
let positionOfPlayer = aDecoder.decodeObjectForKey(SettingNames.positionOfPlayerOnCurrentLevel) as? [Int]
let sounds = aDecoder.decodeObjectForKey(SettingNames.nameOfSounds) as? Bool
let shape = aDecoder.decodeObjectForKey(SettingNames.nameOfShapes) as? String
self.init(currentLevel: currentLevel!, positionOfPlayer: positionOfPlayer, sounds: sounds!, shape: shape!, completedLevels: completedLevels!)
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(currentLevel, forKey: SettingNames.nameOfCurrentLevel)
aCoder.encodeObject(completedLevels, forKey: SettingNames.nameOfCompletedLevels)
if let position = positionOfPlayer as [Int]? {
// If game canceled or ended during playing, it saves the current player position.
// Next time, when player open the game, it will continue
aCoder.encodeObject(position, forKey: SettingNames.positionOfPlayerOnCurrentLevel)
}
aCoder.encodeBool(sounds, forKey: SettingNames.nameOfSounds)
aCoder.encodeObject(shape, forKey: SettingNames.nameOfShapes)
}
}
You're trying to access a constructor which accepts no parameters which was not implemented for the current class. Try overriding the init method, that should remove the error.
override init(){
// some code
}
Here's a full example I've tried:
import Foundation
class Settings : NSObject, NSCoding {
static let sharedInstance = Settings()
var a: String?
var b: String?
convenience init(a: String, b: String){
self.init()
self.a = a
self.b = b
}
override init(){
}
required init?(coder aDecoder: NSCoder) {
}
func encodeWithCoder(aCoder: NSCoder) {
}
}
Just a point of view: a Singleton that requires you to pass parameters in order to configure it does no longer behave like a singleton.
So in my game i have a function that spawns coins,they are given the name "coin", Now I have no way to reference the coins,example to kill them or move them.So what I'm trying to do is make a reference to be able to use in my code to just change its zPosition.
Everytime I run my app and have a function run that uses the coinRef [ex. to change the zPosition], the app crashes with the error:
'Thread 1 EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)'
Heres my code:
let coinRef: SKSpriteNode = self.childNodeWithName("coin")! as! SKSpriteNode
func hideCoins() {
coinRef.zPosition = -1
}
func showCoins() {
coinRef.zPosition = 101
}
func killCoins() {
coinRef.removeFromParent()
}
Looking at what you write
So in my game i have a function that spawns coins,they are given the name "coin"
it looks like there are multiple coins in your scene. As you can imagine a single name coin is not enough to univocally identify more then 1 coin :)
We'll need a way do identity multiple coins.
1. The Coin class
class Coin: SKSpriteNode {
private static var lastID: UInt = 0
let id:UInt
init() {
self.id = Coin.lastID++
let texture = SKTexture(imageNamed: "coin")
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
self.name = "coin"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
As you can see Coin has an internal mechanism to assign a new id to each new instance. You can use this id to reference the coins in your scene.
let coin0 = Coin()
coin0.id // 0
let coin1 = Coin()
coin1.id // 1
let coin2 = Coin()
coin2.id // 2
2. Managing your coins
class GameScene: SKScene {
func retrieveCoin(id:UInt) -> Coin? {
return children.filter { ($0 as? Coin)?.id == id }.first as? Coin
}
func hideCoin(id:UInt) {
retrieveCoin(id)?.hidden = true
}
func showCoin(id:UInt) {
retrieveCoin(id)?.hidden = true
}
func deleteCoin(id:UInt) {
retrieveCoin(id)?.removeFromParent()
}
}
The retrieveCoin method returns (if does exist) a coin with the specified id. Otherwise nil is returned.
The hideCoin and showCoin do change the hidden property to change its visibility.
Finally deleteCoin remove from the scene the Coin with the specified id.
Try this. Initialise coinRef before the didMoveToView function, and then give coinRef its value in the didMoveToView function.
class scene : SKScene {
let coinRef: SKSpriteNode = SKSpriteNode()
override func didMoveToView(view: SKView) {
coinRef: SKSpriteNode = self.childNodeWithName("coin")! as! SKSpriteNode
}
func hideCoins() {
coinRef.zPosition = -1
}
func showCoins() {
coinRef.zPosition = 101
}
func killCoins() {
coinRef.removeFromParent()
}
}
I am able to initialize this class, and see my particle effect, but the subclass doesn't call init() or deinit. Why?
class Particles: SKEmitterNode {
var test: Int?
override init() {
test = 1
super.init()
println("created particle emitter")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
println("destroyed particle emitter")
}
}
This is called from GameScene()
let particle = Particles(fileNamed:"test")
and I tried this to:
var particle: Particles?
viewDidLoad:
particle = Particles(fileNamed:"test")
Subclasses can only call superclass designated initialiser so cannot call super.init(fileNamed:). A workaround is not to make an initialiser at all but make a class method and perform setup code in there.
I copied the code apple provides to unarchive an sks scene and changed it to work with your emitter. The method finds the particle file if it exists and unarchives it as a Particles object:
class Particles: SKEmitterNode {
var test: Int?
class func fromFile(file : String) -> Particles? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var data = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: data)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKEmitterNode")
let p = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as Particles
archiver.finishDecoding()
//Perform any setup here
p.test=11
return p
}
return nil
}
}
and use it like so:
let p=Particles.fromFile("MyParticle")!
p.test=0
p.particleSpeed=10
addChild(p)