How to add an .sks files to existing Swift/Sprite-Kit project? - ios

I started following Ray Wenderlich's 'Space Invaders' tutorial, but have diverged considerably. I now have 3 SKScenes - my title screen, my main game screen and my end level/game over screen. The title screen and the end game scene I added and these both have .sks files; the main game screen does not and all elements (SKSpriteNodes etc) are placed programatically. The flow of my program is as follows:
I now would actually like to place some events of the main game screen via the scene editor, so I created a .sks file for it and tried to change my titleScene.swift as follows:
from:
let gameScene = GameScene(size:CGSize(width: 1536, height: 2048))
to:
let gameScene = SKScene(fileNamed: "GameScene.sks") as! GameScene!
However, this then gives:
I tried to remove the required init(coder aDecoder: NSCoder) but Xcode then complains that
required init(coder: must be supplied by subclass of SKScene
However my titleScene and gameOverScene are also sub-classes of SKScene and they don't have init(coder:)
I really can't see the difference in what I'm doing to display my titleScreen and my gameOverScene via (fileNames:) and their .sks file and trying to do the same for my gameScene.

The reason why you are getting the required is because you have variables that are not optional or not initialized before init takes place.
If you have variables that need to be assigned inside of an init function, then you would do:
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
But then you will ask me: Mr Knight0fDragon, it is telling me to replace fileNamed with coder, and it is not compiling when I switch it.
Well this is because init(fileNamed:) is a convenience init, not a designated init. In order to be able to subclass a class and get all of it's convenience inits, you need to override all of it's designated inits.
Now with SKScene, you have 3, and you already know about 1.
Let's override the other 2:
override init() {
super.init()
}
override init(size: CGSize) {
super.init(size: size)
}
Alright, now this puppy should be ready to compile, we just need to get the variables assigned.
Well what I like to do is create a setup method for any variable that has to be assigned in any version of initialization after the super is called.
Unfortunately we can't do this for constants before super is called, so those we would need to set up in each method. The reason being is that self does not fully exist yet.
This would end up looking like this:
let constant : String
required init?(coder aDecoder: NSCoder)
{
constant = "hi"
super.init(coder: aDecoder)
setup()
}
override init() {
constant = "hi"
super.init()
setup()
}
override init(size: CGSize) {
constant = "hi"
super.init(size: size)
setup()
}

Related

When is "required init?(coder aDecoder: NSCoder)" called on a UIView or UIViewController?

When I create a subclass of UIView or UIViewController with a stored property, Xcode will not compile my project unless I include an implementation of required init?(coder aDecoder: NSCoder). Currently, I have the following implementation to shut the compiler up:
required init?(coder aDecoder: NSCoder) {
fatalError()
}
I understand why I'm required to include this initializer; my subclass needs to conform to the NSCoding protocol because its superclass conforms to it, and this initializer is part of the NSCoding protocol so it needs to work with my class, i.e. initialize all of my class's stored properties (which the superclass version of the initializer won't do).
I imagine that a correct implementation would look something like this:
class MyView: UIView {
let label: UILabel
override init(frame: CGRect) {
label = UILabel()
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
if let label = aDecoder.decodeObject() as? UILabel {
self.label = label
} else {
return nil
}
super.init(coder: aDecoder)
}
override func encode(with aCoder: NSCoder) {
aCoder.encode(label)
super.encode(with: aCoder)
}
}
However, considering that my application has over 50 custom views and view controllers, correctly implementing this function in every custom view and view controller is a lot of work.
So, I'm wondering if it's necessary to implement this initializer correctly, or if I can just leave it throwing a fatal error. In other words, will this initializer ever be called if I don't call it in my own code? I think I read that it might be called by a Storyboard, but my app doesn't use any Storyboards.
This initialiser will be called if an instance of your view is used in a storyboard scene.
It is up to you whether to create a functioning initialiser or not, but it should mostly be a matter of copying code from init(frame:)
It provides an NSCoder instance as a parameter, which you need only if you are using iOS serialization APIs. This is not used often, so you can ignore it. If you are curious to learn, serialisation converts an object in a byte stream that you can save on disk or send over the network.
During the initalization of a view controller, you usually allocate the resources that the view controller will need during its lifetime. So, this include model objects or other auxiliary controllers, like network controllers.

custom SKSpriteNode classes for use in .sks file?

I'm making a custom SKSpriteNode Class, which I want to use in my .sks file. I want it to have #IBInspectable property. Is it possible? And how can I implement its init(coder:) method, or there is no need to implement it?
no it's not possible to use #IBInspectable, that only works with classes in the storyboard editor.
you can create your custom class with an init and instantiate it from code. If you want to instatiate your custom object from the Scene editor you MUST use the init(coder:) func
you can have both init's in your class in case you wish to instantiate your object in code at some point as well as creating in a scene sks file.
init() {
super.init(texture: nil, color: .clear, size, CGSize.zero)
setup()
}
required init?(code aDecoder: NSCoder) {
super.init(code: aDecoder)
setup()
}
func setup() {
//add some setup code here
}
Or if you ONLY want to instantiate the object in the scene file you can eliminate the normal init().

Cannot convert value of type CGSize to expected argument type NSCoder

I am getting a couple of errors like this in a few of my programs and been trying to fix it. Example: When I put size instead of coder it will tell me its incorrect and change it back to coder.
func reset() {
self.removeAllChildren()
var viewSize = view?.bounds.size
backgrounMusicPlayer.stop()
replayButton.isHidden = true
let gameScene = GameScene(coder: viewSize) //where error is
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
gameScene.scaleMode = SKSceneScaleMode.aspectFill
self.scene!.view?.presentScene(gameScene, transition: transition)
}
The problem is in your initialization of the GameScene object ... as the error implies.
let gameScene = GameScene(coder: viewSize) //where error is
You are passing in a CGSize object, when this initializer expects an NSCoder object. I'm assuming that GameScene is a subclass of SKScene, you should be initializing with
let gameScene = GameScene(size: viewSize)
To avoid to write wrong syntax you could use the XCode autocompletion.
Control + SpaceBar triggers XCode’s autocomplete functionality, also when you want to know the available init methods about a generic class like SKScene you simply write:
Doing this you can immediatly see input properties and the required types.
About your case you have a class named GameScene that subclass SKScene and you try to make:
SKScene.init(coder: <#T##NSCoder#>)
Obviusly you cannot pass viewSize as the input parameter for this init method. You should use init(size: CGSize) method
Details:
Now we speak about the init(coder: NSCoder).
The procedure to save the state of your view to disk is known as serialization. The reverse is deserialization - restoring the state of the object from disk.
override func encode(with aCoder: NSCoder) {
// Serialize your object here
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
// Deserialize your object here
fatalError("init(coder:) has not been implemented")
}
These methods are needed in your custom class to speak with Interface Builder, for example objects to the storyboard, that serialize the state of that object on to disk and deserialize it when you use the storyboard but if you don't add any new properties to your subclass you can also avoid to write your own serialization and deserialization code for the subclass.
Assuming your GameScene class is a subclass of SKScene, it doesn't have an initializer with a coder label.
Just change GameScene(coder: viewSize) to GameScene(size: viewSize)
If this is a SpriteKit Project, the GameScene class does not need the size and only requires the name of the .sks file which represent the scene. (the size would be set as property on that file)
Try this, it would reset and represent the game scene again
if let scene = GameScene(fileNamed:"GAME_SCENE_FILE_NAME") {
scene.replayButton.isHidden = true
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
scene.scaleMode = .aspectFill
self.view!.presentScene(scene, transition: transition)
}
I have this line in my GameViewController.swift. It was trying to make me "correct" my code to use "coder" instead of "size" and NScoder was the type.:
let scene = GameScene(size: view.bounds.size)
I commented out this part of my code in my GameScene.Swift. That fixed my problem.
/*
required init?(coder aDecoder: NSCoder) {
// theSelf = self
// fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder) //VL:4/1/2015 had to comment out for some reason
}
*/
Intuition helped me figure it out. I did a search for NSCoder in my project and found this. Which essentially was changing how the GameScene inits. This project started at least as far back as xcode 6, which is probably why.

GameplayKit: suggestion for implementing a GKComponent to fire bullets

I am trying to use GameplayKit to implement all the entities/components of a 2D game made with SpriteKit. The first component that I have created is the SpriteComponent:
class SpriteComponent:GKComponent
{
let node:SKSpriteNode
init(texture:SKTexture) {
node = SKSpriteNode(texture: texture, color: SKColor.clear, size: texture.size())
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Which is meant to be used by all the components that needs to be added to the scene. Next I wanted to add a component to fire bullets, which I called FiringComponent. Since a bullet is a graphical item, it could be an entity implementing a SpriteComponent, so I wrote this code:
class Bullet:GKEntity
{
var velocity:CGPoint
var node:SKSpriteNode
{
return self.component(ofType: SpriteComponent.self)!.node
}
init(withVelocity velocity:CGPoint)
{
self.velocity = velocity
super.init()
let spriteComponent = SpriteComponent(texture: SKTexture(imageNamed: "Bullet"))
addComponent(spriteComponent)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func update(deltaTime seconds: TimeInterval)
{
self.node.position += self.velocity * CGFloat(seconds)
// I have overridden the CGPoint += and * operators
}
}
Next comes the doubt: should the FiringComponent add directly the bullet entity to the scene? a possible implementation of the FiringComponent could be of adding a Bullet entity that should be added to the game hierarchy and updated at each frame. But I think that this logic is flawed, because this way the component needs to have a variable that points not only to the game scene, but also to the object that holds all the entities and updates them periodically. This way I have to write contorted code just to fire a bullet. I am searching for a neat and clean way to implement that: any suggestion?

Get property from NSCoder in required init(coder aDecoder: NSCoder)

I am implementing a Circle class (subclass of UIView) in Swift that sets its radius in its initializer according to the frame that is passed in init(frame: CGRect) like so:
override init(frame: CGRect)
{
radius = frame.width/2.0
super.init(frame: frame)
}
I also want to ensure for the case when the circle is instantiated from Interface Builder, so I also implement 'required init(coder aDecoder: NSCoder)` (which I am forced to do by Xcode anyway).
How can I retrieve the frame property of the view that is somehow contained in aDecoder. What I want to achieve basically would look like this:
required init(coder aDecoder: NSCoder)
{
var theFrame = aDecoder.someHowRetrieveTheFramePropertyOfTheView // how can I achieve this?
radius = theFrame.width/2.0
super.init(coder: aDecoder)
}
You could compute the radius after the frame has been set by super.init():
required init(coder aDecoder: NSCoder)
{
radius = 0 // Must be initialized before calling super.init()
super.init(coder: aDecoder)
radius = frame.width/2.0
}
Martin's answer is the correct one. (Voted). You might be able to find the way that the base class encodes the frame value and extract it, but that is fragile. (It relies on private details of the implementation of the base class, which might change and break your app in the future.) Don't develop code that depends on non-public implementation details of another class, or of your base class. That's a future bug just waiting to happen.
The pattern in initWithCoder is to first call super to get the values for the ancestor class, then extract the values for your custom class.
When you do it that way, the ancestor class will have already set up your view's frame for you, and you can just use that.

Resources