Accessing var from another class or scene in SpriteKit - swift

I'm building a game in SpriteKit that includes a player to select a character out of 7 options. Scene 1 will include 7 images. Each image will represent one character. The player will select the character by pressing on the image selected and new scene 2 will be presented. I want the selected character image to appear on scene 2. I saved data of selected character in scene 1, but when scene 2 is presented, it always shows character 1 and not another. Please advise what is the correct line of code to access var from scene 1 and bring it over to scene 2.
import SpriteKit
class Scene1: SKScene {
var Player1 = SKSpriteNode()
var Player2 = SKSpriteNode()
var Player3 = SKSpriteNode()
var Player4 = SKSpriteNode()
var Player5 = SKSpriteNode()
var Player6 = SKSpriteNode()
var Player7 = SKSpriteNode()
var playerSelect1 = false
var playerSelect2 = false
var playerSelect3 = false
var playerSelect4 = false
var playerSelect5 = false
var playerSelect6 = false
var playerSelect7 = false
// in touches began..
if self.nodeAtPoint(location) == self.player1 {
playerSelect1 = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"playerSelect1")
// then i present scene2
} else if self.nodeAtPoint(location) == self.player2 {
playerSelect2 = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"playerSelect2")
//then i present scene2
} else if self.nodeAtPoint(location) == self.player3 {
playerSelect3 = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"playerSelect3")
//then i present scene2
} else if self.nodeAtPoint(location) == self.player4 {
playerSelect4 = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"playerSelect4")
//then i present scene2
// i didn't list all 7 characters to make code shorter
class Scene2: SKScene {
var playerSelected: Scene1()
// did move to view
var character1 = SKSpriteNode()
var character2 = SKSpriteNode()
var character3 = SKSpriteNode()
var character4 = SKSpriteNode()
//grab the selection value
let playerSelect1 = NSUserDefaults.standardUserDefaults().boolForKey("playerSelect1")
let playerSelect2 = NSUserDefaults.standardUserDefaults().boolForKey("playerSelect2")
let playerSelect3 = NSUserDefaults.standardUserDefaults().boolForKey("playerSelect3")
let playerSelect4 = NSUserDefaults.standardUserDefaults().boolForKey("playerSelect4")
if playerSelect1 == true {
self.addChild(character1)
} else if playerSelect2 == true {
self.addChild(character2)
} else if playerSelect3 == true {
self.addChild(character3)
} else if playerSelect4 == true {
self.addChild(character4)
}
}

About your question there are many ways you can follow to achieve what you want. Now I personally only use NSUserDefaults when I need to communicate with for example to a today extension. You might also want to save your data files at a later time so I thought to this:
class Settings: NSObject { // NSCoding in case you want to save your data
var playerGender: [Bool]! = [Bool]()
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(playerGender, forKey: "playerGender")
}
init(coder aDecoder: NSCoder!) {
playerGender = aDecoder.decodeObjectForKey("playerGender") as! [Bool]
}
override init() {
super.init()
self.playerGender = Array(count:7, repeatedValue:false)
}
}
class GameManager: NSObject {
static let sharedInstance = GameManager()
var settings : Settings! = Settings()
}
class Scene1: SKScene {
let gameManager = GameManager.sharedInstance
var player : [SKSpriteNode]?
override func didMoveToView(view: SKView) {
// in touches began..
if self.nodeAtPoint(location) == self.player1 {
gameManager.settings.playerGender[0] = true
}
}
}
class Scene2: SKScene {
let gameManager = GameManager.sharedInstance
var character : [SKSpriteNode]?
override func didMoveToView(view: SKView) {
//grab the selection value
let playerSelect1 = gameManager.settings.playerGender[0] //it's true
}
}
Explaination:
Settings is a class where you can write your game settings, and in future , you can also save the property values to a file.
GameManager is a shared instance, you can call it where do you want.

So let's consider the first time the user selects player 1. You store the selected player as a Bool in NSUserDefaults. This value will persist in local storage until you change it, even after the app is closed. So the next time the user selects player 2, you set the Bool for playerSelect2. The problem is, that in Scene2 you start your if statement checking if playerSelect1 == true, which it is because you set it to true once and never changed it back. With your current method, you would have to set the desired playerSelect to true, but also reset all of the others to false. I would recommend changing your strategy to store a String corresponding to the selected player in NSUserDefaults. Something like this should get you started.
When you want to set the selected player:
if self.nodeAtPoint(location) == self.player1 {
NSUserDefaults.standardUserDefaults().setObject("playerSelect1", forKey: "selectedPlayer")
else if self.nodeAtPoint(location) == self.player2 {
NSUserDefaults.standardUserDefaults().setObject("playerSelect2", forKey: "selectedPlayer")
else if ...
When you want to determine which player was selected in Scene2:
//grab the value from local storage
let selectedPlayer = NSUserDefaults.standardUserDefaults().objectForKey("selectedPlayer") as! String
if selectedPlayer == "playerSelect1" {
//handle player 1 case
}
else if selectedPlayer == "playerSelect2" {
//handle player 2 case
}

Related

How to detect the current scene in Swift SpriteKit app

In my SpriteKit app I've 3 different scenes and I want to set a specific settings for each scene.
I mean I want set visible an AdBanner in MainPage and SomeInfoScene but not in GameScene. How can I do this?
This is my code:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
let mainPage = SKScene(fileNamed: "MainPage")!
mainPage.name = "MainPage"
let someInfoPage = SKScene(fileNamed: "SomeInfoScene")!
someInfoPage.name = "SomeInfoScene"
let gameScene = SKScene(fileNamed: "GameScene")!
gameScene.name = "GameScene"
view.presentScene(mainPage)
if let currentScene = view.scene {
if currentScene.name == mainPage.name {
print("MainPage")
adBannerView.isHidden = false
}
if currentScene.name == someInfoPage.name {
print("SomeInfoScene")
adBannerView.isHidden = false
}
if currentScene.name == gameScene.name {
print("GameScene")
adBannerView.isHidden = true
}
}
}
}

how to access entity from code - gameplay kit

I have a node I added in the scene. Also in the scene I give it a component to bounce. The component looks like this:
class BounceComponent: GKComponent, ComponentSetupProt {
var bounce: SKAction?
var node: SKSpriteNode?
#GKInspectable var diff: CGFloat = 0.2
#GKInspectable var startScale: CGFloat = 1
#GKInspectable var duration: TimeInterval = 0.5
override init() {
super.init()
comps.append(self)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setup() {
print("setup")
node = entity?.component(ofType: GKSKNodeComponent.self)!.node as? SKSpriteNode
setupBounce()
}
func setupBounce() {
...
}
func performBounce() {
print("performing")
node?.run(bounce!)
}
}//
In the didMove function on my scene file, it calls the components setup(). This works fine. I'm trying to call the function performBounce() when I click on the button...
if (play?.contains(pos))! {
print("test")
if let _ = play?.entity {
print("we have an entity")
}
play?.entity?.component(ofType: BounceComponent.self)?.performBounce()
}
When I click, the only thing it prints is "test" and the entity is nil. I was under the assumption that when you add a node to the editor, it also sets up the entity for that node, so I'm not sure why it is nil. Curious if anyone could shed some light on this?
Thanks for any help!
The entity on the SKSpriteNode is a weak reference, you need to make sure you retain your entities from your gameplaykit object
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = GKScene(fileNamed: "Main") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as? Main {
sceneNode.entities = scene.entities // add this
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.showsDrawCount = true
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
class Main: Base {
var entities = [GKEntity]() // add this

How do edit a sprite based on its name?

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

Why does my SCNAction sequence stop working intermittently?

I'm trying to move an SCNNode around a scene, constrained to a GKGridGraph. Think along the lines of PacMan but in 3D.
I have a ControlComponent which handles the movement of my SCNNode. The logic should go like this...
Set the queuedDirection property.
If the isMoving flag is false, call the move() method
Use the GKGridGraph to evaluate the next move
3.1 If the entity can move to the GKGridGraphNode using the direction property, nextNode = nodeInDirection(direction)
3.2 If the entity can move to the GKGridGraphNode using the queuedDirection property nextNode = nodeInDirection(queuedDirection)
3.3 If the entity can not move to a node with either direction, set the isMoving flag to false and return.
Create the moveTo action
Create a runBlock which calls the move() method
Apply the moveTo and runBlock actions as a sequence to the SCNNode
I've pasted in the full class below. But I'll explain the problem I'm having first. The above logic works, but only intermitently. Sometimes the animation stops working almost immediatley, and sometimes it runs for up to a minute. But at some point, for some reason, it just stops working - setDirection() will fire, move() will fire , the SCNNode will move once space in the specified direction and then the move() method just stops being called.
I'm not 100% convinced my current approach is correct so I'm happy to hear if there's a more idiomatic SceneKit/GameplayKit way to do this.
Here's the full class, but I think the important bit's are the setDirection() and move() methods.
import GameplayKit
import SceneKit
enum BRDirection {
case Up, Down, Left, Right, None
}
class ControlComponent: GKComponent {
var level: BRLevel!
var direction: BRDirection = .None
var queuedDirection: BRDirection?
var isMoving: Bool = false
var speed: NSTimeInterval = 0.5
//----------------------------------------------------------------------------------------
init(level: BRLevel) {
self.level = level
super.init()
}
//----------------------------------------------------------------------------------------
func setDirection( nextDirection: BRDirection) {
self.queuedDirection = nextDirection;
if !self.isMoving {
self.move()
}
}
//----------------------------------------------------------------------------------------
func move() {
let spriteNode: SCNNode = (self.entity?.componentForClass(NodeComponent.self)!.node)!
var nextNode = nodeInDirection( direction )
if let _ = self.queuedDirection {
let attemptedNextNode = nodeInDirection(self.queuedDirection! )
if let _ = attemptedNextNode {
nextNode = attemptedNextNode
self.direction = self.queuedDirection!
self.queuedDirection = nil
}
}
// Bail if we don't have a valid next node
guard let _ = nextNode else {
self.direction = .None
self.queuedDirection = nil
self.isMoving = false
return
}
// Set flag
self.isMoving = true;
// convert graphNode coordinates to Scene coordinates
let xPos: Float = Float(nextNode!.gridPosition.x) + 0.5
let zPos: Float = Float(nextNode!.gridPosition.y) + 0.5
let nextPosition: SCNVector3 = SCNVector3Make(xPos, 0, zPos)
// Configure actions
let moveTo = SCNAction.moveTo(nextPosition, duration: speed)
let repeatAction = SCNAction.runBlock( { _ in self.move() } )
let sequence = SCNAction.sequence([ moveTo, repeatAction ])
spriteNode.runAction( sequence )
}
//----------------------------------------------------------------------------------------
func getCurrentGridGraphNode() -> GKGridGraphNode {
// Acces the node in the scene and gett he grid positions
let spriteNode: SCNNode = (self.entity?.componentForClass(NodeComponent.self)!.node)!
// Account for visual offset
let currentGridPosition: vector_int2 = vector_int2(
Int32( floor(spriteNode.position.x) ),
Int32( floor(spriteNode.position.z) )
)
// return unwrapped node
return level.gridGraph.nodeAtGridPosition(currentGridPosition)!
}
//----------------------------------------------------------------------------------------
func nodeInDirection( nextDirection:BRDirection? ) -> GKGridGraphNode? {
guard let _ = nextDirection else { return nil }
let currentGridGraphNode = self.getCurrentGridGraphNode()
return self.nodeInDirection(nextDirection!, fromNode: currentGridGraphNode)
}
//----------------------------------------------------------------------------------------
func nodeInDirection( nextDirection:BRDirection?, fromNode node:GKGridGraphNode ) -> GKGridGraphNode? {
guard let _ = nextDirection else { return nil }
var nextPosition: vector_int2?
switch (nextDirection!) {
case .Left:
nextPosition = vector_int2(node.gridPosition.x + 1, node.gridPosition.y)
break
case .Right:
nextPosition = vector_int2(node.gridPosition.x - 1, node.gridPosition.y)
break
case .Down:
nextPosition = vector_int2(node.gridPosition.x, node.gridPosition.y - 1)
break
case .Up:
nextPosition = vector_int2(node.gridPosition.x, node.gridPosition.y + 1)
break;
case .None:
return nil
}
return level.gridGraph.nodeAtGridPosition(nextPosition!)
}
}
I'll have to answer my own question. First off, it's a bad question because I'm trying to do things incorrectly. The two main mistakes I made were
My compnent was trying to do too much
I wasn't using the updateWithDeltaTime method.
This is how the code and behaviour should be structured using GameplayKit's entity component structure. I'll try to epxlain how all the prices fit together at the end.
NodeComponent
This component is responsible for managing the actual SCNNode that represents my game character. I've moved the code for animating the character out of the ControlComponent and into this component.
class NodeComponent: GKComponent {
let node: SCNNode
let animationSpeed:NSTimeInterval = 0.25
var nextGridPosition: vector_int2 {
didSet {
makeNextMove(nextGridPosition, oldPosition: oldValue)
}
}
init(node:SCNNode, startPosition: vector_int2){
self.node = node
self.nextGridPosition = startPosition
}
func makeNextMove(newPosition: vector_int2, oldPosition: vector_int2) {
if ( newPosition.x != oldPosition.x || newPosition.y != oldPosition.y ){
let xPos: Float = Float(newPosition.x)
let zPos: Float = Float(newPosition.y)
let nextPosition: SCNVector3 = SCNVector3Make(xPos, 0, zPos)
let moveTo = SCNAction.moveTo(nextPosition, duration: self.animationSpeed)
let updateEntity = SCNAction.runBlock( { _ in
(self.entity as! PlayerEntity).gridPosition = newPosition
})
self.node.runAction(SCNAction.sequence([moveTo, updateEntity]), forKey: "move")
}
}
}
Note that every time the components gridPosition property is set, the makeNextMove method is called.
Control Component
My initial example was trying to do too much. This compnents sole responsibility now is to evaluate the next gridPosition for it's entity's NodeComponent. Note that because of updateWithDeltaTime, it will evaluate the next move as often as that method is called.
class ControlComponent: GKComponent {
var level: BRLevel!
var direction: BRDirection = .None
var queuedDirection: BRDirection?
init(level: BRLevel) {
self.level = level
super.init()
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
self.evaluateNextPosition()
}
func setDirection( nextDirection: BRDirection) {
self.queuedDirection = nextDirection
}
func evaluateNextPosition() {
var nextNode = self.nodeInDirection(self.direction)
if let _ = self.queuedDirection {
let nextPosition = self.entity?.componentForClass(NodeComponent.self)?.nextGridPosition
let targetPosition = (self.entity! as! PlayerEntity).gridPosition
let attemptedNextNode = self.nodeInDirection(self.queuedDirection)
if (nextPosition!.x == targetPosition.x && nextPosition!.y == targetPosition.y){
if let _ = attemptedNextNode {
nextNode = attemptedNextNode
self.direction = self.queuedDirection!
self.queuedDirection = nil
}
}
}
// Bail if we don't have a valid next node
guard let _ = nextNode else {
self.direction = .None
return
}
self.entity!.componentForClass(NodeComponent.self)?.nextGridPosition = nextNode!.gridPosition
}
func getCurrentGridGraphNode() -> GKGridGraphNode {
// Access grid position
let currentGridPosition = (self.entity as! PlayerEntity).gridPosition
// return unwrapped node
return level.gridGraph.nodeAtGridPosition(currentGridPosition)!
}
func nodeInDirection( nextDirection:BRDirection? ) -> GKGridGraphNode? {
guard let _ = nextDirection else { return nil }
let currentGridGraphNode = self.getCurrentGridGraphNode()
return self.nodeInDirection(nextDirection!, fromNode: currentGridGraphNode)
}
func nodeInDirection( nextDirection:BRDirection?, fromNode node:GKGridGraphNode? ) -> GKGridGraphNode? {
guard let _ = nextDirection else { return nil }
guard let _ = node else { return nil }
var nextPosition: vector_int2?
switch (nextDirection!) {
case .Left:
nextPosition = vector_int2(node!.gridPosition.x + 1, node!.gridPosition.y)
break
case .Right:
nextPosition = vector_int2(node!.gridPosition.x - 1, node!.gridPosition.y)
break
case .Down:
nextPosition = vector_int2(node!.gridPosition.x, node!.gridPosition.y - 1)
break
case .Up:
nextPosition = vector_int2(node!.gridPosition.x, node!.gridPosition.y + 1)
break;
case .None:
return nil
}
return level.gridGraph.nodeAtGridPosition(nextPosition!)
}
}
GameViewController
Here's where everything pieces together. There's a lot going on in the class, so I'll post only what's relevant.
class GameViewController: UIViewController, SCNSceneRendererDelegate {
var entityManager: BREntityManager?
var previousUpdateTime: NSTimeInterval?;
var playerEntity: GKEntity?
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene(named: "art.scnassets/game.scn")!
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// show statistics such as fps and timing information
scnView.showsStatistics = true
scnView.delegate = self
entityManager = BREntityManager(level: level!)
createPlayer()
configureSwipeGestures()
scnView.playing = true
}
func createPlayer() {
guard let playerNode = level!.scene.rootNode.childNodeWithName("player", recursively: true) else {
fatalError("No player node in scene")
}
// convert scene coords to grid coords
let scenePos = playerNode.position;
let startingGridPosition = vector_int2(
Int32( floor(scenePos.x) ),
Int32( floor(scenePos.z) )
)
self.playerEntity = PlayerEntity(gridPos: startingGridPosition)
let nodeComp = NodeComponent(node: playerNode, startPosition: startingGridPosition)
let controlComp = ControlComponent(level: level!)
playerEntity!.addComponent(nodeComp)
playerEntity!.addComponent(controlComp)
entityManager!.add(playerEntity!)
}
func configureSwipeGestures() {
let directions: [UISwipeGestureRecognizerDirection] = [.Right, .Left, .Up, .Down]
for direction in directions {
let gesture = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipe:"))
gesture.direction = direction
self.view.addGestureRecognizer(gesture)
}
}
func handleSwipe( gesture: UISwipeGestureRecognizer ) {
let controlComp = playerEntity!.componentForClass(ControlComponent.self)!
switch gesture.direction {
case UISwipeGestureRecognizerDirection.Up:
controlComp.setDirection(BRDirection.Up)
break
case UISwipeGestureRecognizerDirection.Down:
controlComp.setDirection(BRDirection.Down)
break
case UISwipeGestureRecognizerDirection.Left:
controlComp.setDirection(BRDirection.Left)
break
case UISwipeGestureRecognizerDirection.Right:
controlComp.setDirection(BRDirection.Right)
break
default:
break
}
}
// MARK: SCNSceneRendererDelegate
func renderer(renderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
let delta: NSTimeInterval
if let _ = self.previousUpdateTime {
delta = time - self.previousUpdateTime!
}else{
delta = 0.0
}
self.previousUpdateTime = time
self.entityManager!.update(withDelaTime: delta)
}
}
Entity Manager
I picked up this tip following this Ray Wenderlich tutorial. Essentially, it's a bucket to keep all your entities and components in to simplify to work of managing and updating them. I highly reccomend giving that tutorial a walthrough to understand this better.
class BREntityManager {
var entities = Set<GKEntity>()
var toRemove = Set<GKEntity>()
let level: BRLevel
//----------------------------------------------------------------------------------
lazy var componentSystems: [GKComponentSystem] = {
return [
GKComponentSystem(componentClass: ControlComponent.self),
GKComponentSystem(componentClass: NodeComponent.self)
]
}()
//----------------------------------------------------------------------------------
init(level:BRLevel) {
self.level = level
}
//----------------------------------------------------------------------------------
func add(entity: GKEntity){
if let node:SCNNode = entity.componentForClass(NodeComponent.self)!.node {
if !level.scene.rootNode.childNodes.contains(node){
level.scene.rootNode.addChildNode(node)
}
}
entities.insert(entity)
for componentSystem in componentSystems {
componentSystem.addComponentWithEntity(entity)
}
}
//----------------------------------------------------------------------------------
func remove(entity: GKEntity) {
if let _node = entity.componentForClass(NodeComponent.self)?.node {
_node.removeFromParentNode()
}
entities.remove(entity)
toRemove.insert(entity)
}
//----------------------------------------------------------------------------------
func update(withDelaTime deltaTime: NSTimeInterval) {
// update components
for componentSystem in componentSystems {
componentSystem.updateWithDeltaTime(deltaTime)
}
// remove components
for curRemove in toRemove {
for componentSystem in componentSystems {
componentSystem.removeComponentWithEntity(curRemove)
}
}
toRemove.removeAll()
}
}
So how does all this fit together
The ControlComponent.setDirection() method can be called at any time.
If an entity or component implements an updateWithDeltaTime method, it should be called every frame. It took me a little while to figure out how to do this with SceneKit as most GameplayKit example's are set up for SpriteKit, and SKScene has a very conventient updatet method.
For SceneKit, We have to make the GameVierwController the SCNSceneRendererDelegate for the SCNView. Then, using the rendererUpdateAtTime method, we can call updateAtDeltaTime on the Entity Manager which handles calling the same method on all entities and components every frame.
Note You have to manually set the playing property to true for this to work.
Now we move to the actual animation. ControlComponent is evaluating what the NodeComponents next grid position should be every frame, using the direction property (You can ignore the queuedDirection property, it's an implementation detail).
This means that the NodeComponents.makeNextMove() method is also being called every frame. Whenever newPosition and oldPosition are not the same, the animation is applied. And whenever the animation finishes, the node's gridPosition is updated.
Now, as to why my intial method for animating my SCNNode didn't work, I've no idea. But at least it forced me to better understand how to use GameplayKit's Entity/Component design.

Saving High Score Data with Swift

I am trying to save a high score using Swift for my SpriteKit game. There are several good examples on StackOverflow, one of which I got to work temporarily, but could not function properly in the Swift file where all of my nodes (and actual game) is located.
*Most of the following code is from a stack overflow answer.
This code I put in a separate file called "HighScore":
import Foundation
class HighScore: NSObject {
var highScore: Int = 0
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeInteger(highScore, forKey: "highScore")
}
init(coder aDecoder: NSCoder!) {
highScore = aDecoder.decodeIntegerForKey("highScore")
}
override init() {
}
}
class SaveHighScore:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func ArchiveHighScore(#highScore: HighScore) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("highScore.archive")
if NSKeyedArchiver.archiveRootObject(highScore, toFile: path) {
println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func RetrieveHighScore() -> NSObject {
var dataToRetrieve = HighScore()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("highScore.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? HighScore {
dataToRetrieve = dataToRetrieve2
}
return(dataToRetrieve)
}
}
In the scene where I actually want to get an input and output for the highscore I have:
var Score = HighScore()
override func viewDidLoad() {
super.viewDidLoad()
Score.highScore = 100
SaveHighScore().ArchiveHighScore(highScore: Score)
var retrievedHighScore = SaveHighScore().RetrieveHighScore() as HighScore
println(retrievedHighScore.highScore)
}
So when a particular node passes a "block" the high score should increment accordingly, and then save the number (as long as it is higher than the current highscore.)
func blockRunner() {
Score.highScore = 0
SaveHighScore().ArchiveHighScore(highScore: Score)
var retrievedHighScore = SaveHighScore().RetrieveHighScore() as! HighScore
println(retrievedHighScore.highScore)
for(block, blockStatus) in blockStatuses {
var thisBlock = self.childNodeWithName(block)!
if blockStatus.shouldRunBlock() {
blockStatus.timeGapForNextRun = random()
blockStatus.currentInterval = 0
blockStatus.isRunning = true
}
if blockStatus.isRunning {
if thisBlock.position.x > blockMaxX {
thisBlock.position.x -= CGFloat(groundspeed)
}
else{
thisBlock.position.x = self.origBlockPositionX
blockStatus.isRunning = false
retrievedHighScore.highScore++
if ((retrievedHighScore.highScore % 5) == 0) {
groundspeed++
}
self.scoreText.text = String(retrievedHighScore.highScore++)
}
}else{
blockStatus.currentInterval++
}
}
}
For some reason, it will only increment to 1 and then just display 1 in scoreText, even if it has passed more than one block. If I just declare a normal variable and subtitute it in for retrievedHighScore.highScore++, everything works fine. When I use retrievedHighScore.highScore, it only increments to one and just displays 1 in scoreText, strangely, the 1 isn't even saved.
I really recommend using NSUserDefaults in this situation to persist your high score. I also recommend not creating a highscores object for the sake of simply having an Integer variable. You're creating alot of unnecessary overhead by utilizing a class to model a simple Integer.
All that code for archiving highscore can be simplified to 1 line: NSUserDefaults.standardUserDefaults().setInteger(highScore, forKey: "Highscore")
When you need to overwrite the highscore (when a new highscore is to replace the old one) you can simply overwrite the old highscore by calling the above line of code again.
You save alot of work, and your code will perform more efficiently. Using a class to store a single value type in your situation is a terrible choice. Crusty will be mad...