How to programmatically change text of SKLabelNode? - swift

I added some SKLAbelNode into SKNode, which is placed inside SKScene
I need SKNode for grouping some nodes
I added name "altitude" to my SKLAbelNode
and put this code into SKScene class which was associated with my .sks file
var altitude:SKLabelNode = SKLabelNode()
override func sceneDidLoad()
{
if let alti:SKLabelNode = self.childNode(withName: "altitude") as? SKLabelNode {
altitude = alti
}
}
...
override func didMove (...) {
altitude.text = "000"
}
But always I get Fatal error
UPD: I solved this error, but got a question, why when I point a name of sknode even if it's inside in other node and try to get access to it i must point all tree something like
let altitude= (self.childNode(withName: "sknode") as! SKNode).childNode(withName: "altitude") as! SKLabelNode
altitude.text = "000"

Related

Load a spritekit scene from another bundle?

I am making a SpriteKit framework using swift 4.2 and want to include some .sks files for scenes and actions. I have tried to load the scene from the bundle using the code below:
class func newGameScene() -> GameScene {
guard let gameScenePath = Bundle(for: self).path(forResource: "GameScene", ofType: "sks") else { assert(false) }
guard let gameSceneData = FileManager.default.contents(atPath: gameScenePath) else { assert(false) }
let gameSceneCoder = NSKeyedUnarchiver(forReadingWith: gameSceneData)
guard let scene = GameScene(coder: gameSceneCoder) else { assert(false) }
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
return scene
}
I load the scene and present it. (This code is mostly from Apple's template for SpriteKit as Im testing this issue.)
guard let view = view else {
return nil
}
let scene = GameScene.newGameScene()
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
return nil
The GameScene.sks and the code is unchanged from Apples template in this case. This code and the .sks assets are in the dynamic framework and imported into another project.
When having the framework load the scene into a view I pass it, it shows the fps and node count but not the "Hello, World!" text.
In the code below, also copied from the template, a break point shows that these are not called when mousing down.
#if os(OSX)
// Mouse-based event handling
extension GameScene {
override func mouseDown(with event: NSEvent) {
if let label = self.label {
label.run(SKAction.init(named: "Pulse")!, withKey: "fadeInOut")
}
self.makeSpinny(at: event.location(in: self), color: SKColor.green)
}
override func mouseDragged(with event: NSEvent) {
self.makeSpinny(at: event.location(in: self), color: SKColor.blue)
}
override func mouseUp(with event: NSEvent) {
self.makeSpinny(at: event.location(in: self), color: SKColor.red)
}
}
#endif
I know it must have to do with how SpritKit loads the scene but cannot find a solution. I have to use an NSKeyedUnarchiver becuase SpritKit's built in file initializer:
GameScene(fileNamed: "GameScene")
Only loads from the Main Bundle.
Now in the above I assumed that the file can be loaded by using a coder but Tomato made the point that sks most likely was not saved using a coder. In that case, It may be impossible to load an sks file from another bundle in sprite-kit using the provided api from apple. The answer may not include coders.
I have compiled the above discussion/solution into a single extension function on SKScene. Hope this helps someone!
import SpriteKit
extension SKScene {
static func fromBundle(fileName: String, bundle: Bundle?) -> SKScene? {
guard let bundle = bundle else { return nil }
guard let path = bundle.path(forResource: fileName, ofType: "sks") else { return nil }
if let data = FileManager.default.contents(atPath: path) {
return NSKeyedUnarchiver.unarchiveObject(with: data) as? SKScene
}
return nil
}
}
Just as I thought let gameSceneCoder = NSKeyedUnarchiver(forReadingWith: gameSceneData) was not creating a proper coder for you.
Just do
guard let scene = NSKeyedUnarchiver.unarchiveObject(with: gameSceneData) as? SKScene
else{
assert(false)
}
This will unarchive the file properly for you.
Note, if you want to use GameScene, make sure GameScene is set in the custom class of the SKS file

Calling GameScene.swift files through ViewController.swift files [Swift 3.0 Spritekit]

I have been trying to use UIButtons for a game interface, but I am still trying to figure out how to call methods that are inside a GameScene file through my view controller file. Is there anyway I can give my view controller a reference to the instance of the GameScene that is actually being displayed on the GameScene.sks file?
So far this is what I have tried in the view controller:
// Inside view controller
class GameViewController: UIViewController {
static var gameScene: GameScene = GameScene()
func setGameScene(scene: GameScene) {
GameViewController.gameScene = scene
}
...
This is what I did in the GameScene file.
//Inside GameScene.swift
class GameScene: SKScene {
override func didMove(to view: SKView) {
let vc = GameViewController()
vc.setGameScene(scene: self)
}
...
You want to create a computed property to your view's scene
class GameViewController: UIViewController {
var gameScene : GameScene? { return (view as? SKView).scene as? GameScene}
...
}
Now this needs to be optional, because there may be times where your scene is not GameScene, and this could cause your app to crash.

get userData of SKSpriteNode using the scene editor

I set the userData of my SKSpriteNode in the scene editor:
Then I tried to get it, using:
let sprite:SKSpriteNode = self.childNode(withName: "sprite") as? SKSpriteNode
let numero:Int = sprite.userdata?.valueForKey("numero") as! Int
But I got nil value : "fatal error: unexpectedly found nil while unwrapping an Optional value"
I also tried to init the userData with:
sprite.userData? = NSMutableDictionary()
But same result...
Probably also your sprite is nil:
try to search your sprite with the optional binding (if let) and the "//" before sprite.
if let sprite = self.childNode(withName: "//sprite") as? SKSpriteNode {
let numero:Int = sprite.userdata?.valueForKey("numero") as! Int
} else {
print("I cannot find sprite..")
}
It performs a recursive search from its current position.
Check the API reference here.

Add SKReferenceNode/SKScene to another SKScene in SpriteKit

I would like to add a SKScene to my main GameScene. SKReferenceNode seems to be a good solution.
I have :
- GameScene.sks (main scene)
- Countdown.sks (scene to add to GameScene)
- Countdown.swift (Custom class, how does to init it? SKScene ? SKReferenceNode ? SKNode)
I don't know how to add programmatically my countdown using my class Countdown.
I tried:
let path = Bundle.main.path(forResource: "Countdown", ofType: "sks")
let cd = SKReferenceNode (url: NSURL (fileURLWithPath: path!) as URL) as! Countdown
cd.name = "countdown"
self.addChild(cd)
But I have the following error :
Could not cast value of type 'SKReferenceNode' (0x10d97ad88) to 'LYT.Countdown' (0x10a5709d0
I also tried something more simple like:
let cd=Countdown(scene:self)
self.addChild(cd)
But I don't know how to init the class using the Countdown.sks file.
I know I also have the possibility to create a SKNode class, and init it 100% programmatically, but it really important for me to use the associated .sks file in order to use the Xcode scene editor.
I do that, I don't know if is the best way to do this, but works:
I've 2 file Dragon.swift and sks
I've added a "main" node like DragonNode and other node children of this
Now, the DragonNode is a custom class, set it in sks file:
The DragonNode is a normal SKSpriteNode
class DragonNode: SKSpriteNode, Fly, Fire {
var head: SKSpriteNode!
var body: SKSpriteNode!
var shadow: SKSpriteNode!
var dragonVelocity: CGFloat = 250
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//Example other node from sks file
body = self.childNodeWithName("Body") as! SKSpriteNode
head = body.childNodeWithName("Head") as! SKSpriteNode
shadow = self.childNodeWithName("Shadow") as! SKSpriteNode
shadow.name = "shadow"
}
//Dragon Func
func fireAction () {}
func flyAction () {}
}
Inside the scene, add a SKReferenceNode:
In the SKScene code:
let dragonReference = self.childNodeWithName("DragonReference") as! SKReferenceNode
let dragonNode = dragonReference.getBasedChildNode() as! DragonNode
print(dragonNode)
//Now you can use the Dragon func
dragonNode.flyAction()
getBasedChildNode() is an extension to find your based node (the first one)
extension SKReferenceNode {
func getBasedChildNode () -> SKNode? {
if let child = self.children.first?.children.first {return child}
else {return nil}
}
}
I do a similar thing to Simone above, but instead of extending the reference node, I added my extension to SKNode.
extension SKNode {
func nodeReferenced() -> SKNode? {
if self .isKind(of: SKReferenceNode.self) {
return children.first!.children.first!
}
return nil
}
}
This way there is no need for the cast if the node is not actually a reference node and make this two step process a one liner. My version would change that above code to:
if let dragonNode = childNodeWithName("DragonReference")?.nodeReferenced() as? DragonNode {
print(dragonNode)
dragonNode.fly()
}
This works for me, but Simone's answer seems more straight forward and maybe flexible than mine, so I'd give them the points. I just like clean code and since we almost never actually need that SKReferenceNode, we can ignore it. Also, when enumerating the nodes, it's easy to ask for a referenced node, get one or nothing, without having to see if the node is actually a referenceNode first, then performing the change.

SpriteKit add one node's children to another

In my sprite kit project I have a scene .sks file which contains the ground physicsBody and background graphics. The scene will be loaded from this file. It also contains an empty node called container. I have another .sks file for each level which contain blocks and enemies. When my scene is initialised I load a level from one of my level .sks files (casting the SKScene to an SKNode) and add into the empty "container" node. I use this code:
import SpriteKit
extension SKNode {
class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(SKNode.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKNode
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
class GameScene: SKScene {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.childNodeWithName("//container")?.addChild(SKNode.unarchiveFromFile("level1")!)
}
override class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as GameScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
However this means that there is an empty unused node in the node hierarchy:
Scene
-background
-ground
-container
--level scene(cast to SKNode) <- empty node here
---block
---enemy
so my question is: how do I add one node's children to another?
-I cannot directly set the children property (not assignable)
-I cannot replace the container node as I loose all its properties
Just iterate over the children array - you'll need to cast from AnyObject back to SKNode to add the node to the new parent:
for node in oldParentNode.children {
node.removeFromParent()
newParentNode.addChild(node as AnyObject)
}
This should do the trick (untested), just add them one by one:
for (CCNode* node in [oneNode.children copy])
{
[node removeFromParent];
[anotherNode addChild:node];
}
The copy is needed to avoid mutating the children array while enumerating it. Alternatively you could also use the reverseObjectEnumerator. Removing the node must be done so addChild: won't complain about the node already having a parent.