I'm using SpriteKit .sks file Can I make a sprite in .sks into an instance of subclass of SKSpriteNode?
This is the init method in my subclass:
init(imageNamed: String) {
let blockTexture = SKTexture(imageNamed: imageNamed)
super.init(texture: blockTexture, color: nil, size: blockTexture.size())
}
In GameScene.swift I can create an instance like this:
var myObj = Block(imageNamed: "Block")
My question is how can I relate this instance with .sks file?
I tried this line of code but it doesn't work.
myObj = childNodeWithName("block1") as Block
Any help?
Thanks.
There are a couple of issues here to address...
How .sks
loading works
When you load a .sks
file, SpriteKit instantiates everything within using NSKeyedUnarchiver
. This means that all the nodes inside are loaded as whatever base classes they were specified as by Xcode when it created the .sks
file — SKSpriteNode
for sprites with texture art, SKLabelNode
for text, SKFieldNode
for physics fields, etc. And Xcode doesn't currently provide an option for setting custom classes for the nodes inside a .sks
file.
(The one exception for this is changing the runtime class of the scene itself — the top level container for everything in the .sks
file. And that's only because of the custom SKNode.unarchiveFromFile
implementation provided for you in the project template. Its technique for changing classes at load time works when you have one and only one instance of a particular class in an archive — good for SKScene
, not so good for the many nodes in a scene.)
How casting works
When you write something like:
myObj = childNodeWithName("block1") as Block
You're telling the compiler something like: "Hey, you know that thing you got from childNodeWithName
? All you know is that it's an SKNode
, but I know it's really a Block
, so please let me call Block
methods on it." (And the compiler says, "Okay, whatever.")
But then at run time, that thing you got had better really be a Block
, or your app will crash because you tried to do something Block
y with something that's not a Block
. And, per the bit about .sks
loading above, that thing isn't and can't be a Block
— Xcode doesn't know how to put Block
s into a .sks
file. So you can't get a Block
out of it, so your app is guaranteed to crash.
Workarounds
So, if you can't put custom classes into a .sks
file, what can you do? It depends a bit on what exactly you're trying to accomplish. But there's a good trick that might also be good game/app design in general: use the .sks
file for general layout and configuration, and use a second pass to bring in things that need custom behavior.
For example, if you're building a level in a 2D platform game, you wouldn't really want to have the .sks
file contain an instance of your Plumber
class even if you could — that class probably has lots of details about how tall or fat the guy is, how high he jumps, the shape of his mustache, etc, and you don't want to have to set those up again every time you make a new level, much less have them saved again in each level's .sks
file. Instead, the only thing you really need to know in each level file is the position he starts at. So, drag out an "Empty Node" in Xcode, and at load time, replace that node with an instance of your Plumber
class, like so:
let spawnPoint = childNodeWithName("spawnPoint")
let player = Plumber()
player.position = spawnPoint.position
addChild(player)
spawnPoint.removeFromParent()
If you have more configuration details that you want to set in the .sks
file, you might consider automating that process.
- Make a method that does the above node-swapping trick. (Call it something like
replaceNode(_:withNode:)
.)
- Make an initializer for your custom class that takes a
SKNode
or SKSpriteNode
, and have it set all its inherited properties (or at least the ones you care about, like color and texture) from that node.
Use enumerateChildNodesWithName:usingBlock:
with a search pattern to find all the nodes in your scene with a certain kind of name, and replace them with a new node created using your initializer. Something like:
enumerateChildNodesWithName("//brick_[0-9]*") { node, stop in
self.replaceNode(node, withNode: BrickBlock(node))
}
enumerateChildNodesWithName("//question_[0-9]*") { node, stop in
self.replaceNode(node, withNode: QuestionBlock(node))
}