Error: "Property 'self.gameArea' not initialised at super.init call" - sprite-kit

I get the error written in the title. I'm new to this all. Any help appreciated.
This is the GameScene.swift file:
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed: "shuttle")
let bulletSound = SKAction.playSoundFileNamed("torpedo.mp3", waitForCompletion: false)
let gameArea: CGRect
override init(size: CGSize) {
let maxRatio: CGFloat = 16.0/9.0
let playableWidth = size.height / maxRatio
let margin = (size.width - playableWidth) / 2
gameArea = CGRect(x: margin - size.width / 2, y: size.height / 2, width: playableWidth, height: size.height)
super.init(size: size)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

I would recommend you to read about initialization process. Swift preforms two step initialization. First phase is intialization of all stored properties. Also Swift compiler perform few safety checks to make sure two phase initialization is performed correctly. This is from the docs:
Safety check 1
A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a
superclass initializer.
So you can either initialize your property inside of an initializer:
required init?(coder aDecoder: NSCoder) {
self.gameArea = CGRect()
super.init(coder: aDecoder)
}
or at property declaration (and optionally declaring it as var):
var gameArea = CGRect()
If you don't make it var, after initialization (at declaration) you won't be able to change it later on. Same goes when you initialize a property inside of an initializer. If it is a let constant, further modifications are not possible.

Related

Am unable to subclass SKSpriteNode using an image name

I am having trouble subclassing SKSpriteNode when I need to use an .png image and all the help I can find on Google only mentions SKTexture.
In my regular class this code works:
let circle = SKSpriteNode(imageNamed: "slot")
circle.setScale(1.0)
circle.anchorPoint = CGPoint(x: 0, y: 0.5)
circle.position = CGPoint(x: 1000, y: 500)
self.addChild(circle)
I would like to move it to a subclass, but no matter what combination I try am always getting errors such as:
Cannot convert value of type 'SKTexture' to expected argument type
'String'
or
Must call a designated initializer of the superclass 'SKSpriteNode'
I am able to subclass SKSpriteNode if I want to use SKTexture. Yet at this juncture the init I want to subclass is SKSpriteNode(imageNamed: String)
Here is what I am trying to do, but of course I get errors
class MyBall : SKSpriteNode{
init(iNamed: String){
super.init(imageNamed: iNamed)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Just use the full initializer on the super, like so...
class MyBall : SKSpriteNode{
init(iNamed: String) {
let texture = SKTexture(imageNamed: iNamed)
super.init(texture: texture, color: .clear, size: texture.size())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

What is the problem with this init(coder:) function?

I don't get it guys.
I'm trying to create a 2D game and i'm stuck with a very basic issue.
Here is my code:
import SpriteKit
class GameScene: SKScene {
let box: SKSpriteNode
let hero: SKSpriteNode
override init(size: CGSize){
hero = SKSpriteNode(color: UIColor.red, size: CGSize(width: size.width / 5, height: size.height / 5))
box = SKSpriteNode(color: UIColor.blue, size: CGSize(width: size.width, height: size.height))
super.init(size: size)
self.addChild(box)
self.addChild(hero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func didMove(to view: SKView) {
// Setting up the scene
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
Xcode indicates me the following error :"Property 'self.box' not initialized at super.init call" at the init (coder:) function.
Then i tried to replace it with:
required init?(coder a Decoder: NSCoder){
fatalError("init(coder:) has not been implemented)
}
But guess what... when i launch the game it indicates me that i have a fatal error.
When i delete all initializers it tells me that i need initialisers.
Do you have any idea of how to do it?
Given that box and hero are not optional, they must be initialized in all initializers.
You have done it for your custom initializer but you also need to do it for init with coder since that initializer can be invoked separate from your custom initializer.

Custom Class Not Drawing at Center - Swift

I have a UIView with a custom class of TimerView. I am trying to draw a dot in the exact center of the UIView but it is appearing at the bottom slightly off center:
Custom class is here:
import UIKit
class TimerView: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
drawDot()
}
func drawDot() {
let midViewX = self.frame.midX
let midViewY = self.frame.midY
let dotPath = UIBezierPath(ovalIn: CGRect(x: midViewX, y: midViewY, width: 5, height: 5))
let dotLayer = CAShapeLayer()
dotLayer.path = dotPath.cgPath
dotLayer.strokeColor = UIColor.blue.cgColor
self.layer.addSublayer(dotLayer)
}
}
Here is the re-implementation of TimerView class. For UIView, init is not the best place to get the frame/bounds values because it might change once the autolayout applies the constraints in runtime. layoutSubviews is the best place to get the correct frame/bounds values for parent/child views and to setup the child space properties. Secondly you should be using parent view bounds to setup the child's frame.
class TimerView: UIView {
private var dotLayer: CAShapeLayer?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
dotLayer = CAShapeLayer()
dotLayer?.strokeColor = UIColor.blue.cgColor
self.layer.addSublayer(dotLayer!)
}
override func layoutSubviews() {
super.layoutSubviews()
drawDot()
}
func drawDot() {
let midViewX = self.bounds.midX
let midViewY = self.bounds.midY
let dotPath = UIBezierPath(ovalIn: CGRect(x: midViewX, y: midViewY, width: 5, height: 5))
dotLayer?.path = dotPath.cgPath
}
}

Passing Information To A Class On Initialisation

I'm struggling to understand how class initialisation works. From looking at solutions to other problems I have this as an example of the classes in my app.
import Foundation
import SpriteKit
class Ground : SKSpriteNode {
override init(texture: SKTexture!, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.zPosition = -20
self.name = "Ground";
}
func configure (size: CGSize, position: CGPoint) {
self.size = size
self.position = position
// Set up the Physics
self.physicsBody = SKPhysicsBody(rectangleOf: size)
self.physicsBody?.contactTestBitMask = PhysicsCategory.Player
self.physicsBody?.categoryBitMask = PhysicsCategory.Ground
self.physicsBody?.collisionBitMask = PhysicsCategory.All
self.physicsBody?.affectedByGravity = false
self.physicsBody?.allowsRotation = false
self.physicsBody?.isDynamic = false
self.physicsBody?.mass = 1.99999
}
convenience init(color: SKColor, isActive: Bool = false) {
let size = CGSize(width: 10, height: 10);
self.init(texture:nil, color: color, size: size)
}
required init?(coder aDecoder: NSCoder) {
// Decoding length here would be nice...
super.init(coder: aDecoder)
}
}
I've put the 'configure' function as a kludge fix to let me pass the scene to the class so I can set sizes depending on the size of the device screen. Ideally I would like to just pull this information on initialisation but everything I try throws up errors that I don't understand.
Im not sure which way would be correct but I was wondering firstly, how would I pass arguments to the class to start with.. e.g..
let myClass = Ground(scene: self)
or can I somehow pull the scene information from directly within the class? I can pass info into functions/methods as I did with 'configure' but I couldn't get it to work on initialisation which would certainly be cleaner.
How would you guys do it?
Size
You should not programmatically change the size of a sprite depending on the current device, just use the same size and then let SpriteKit resizing it for you.
Loading from .sks
This initializer
init?(coder aDecoder: NSCoder)
is used when the Sprite is loaded from a .sks file. However in Xcode 7 you cannot pass values from the .sks to the sprite to set the attributes. I'm not sure you are using a .sks file so for now I am just throwing a fatal_error here. In Xcode 8 however you will be able to pass values from the .sks file to your own class.
required init?(coder aDecoder: NSCoder) {
fatalError("Not implemented")
}
Scene
You don't need to pass a reference of the current scene to your class. Every SKNode has the scene property, just use it to retrieve the scene where the node lives. Of course keep in mind that if you invoke .scene before the node has been added to a scene it will returns nil.
Code
This is the final code
class Ground : SKSpriteNode {
init(color: SKColor = .clearColor(), isActive:Bool = false) {
let texture = SKTexture(imageNamed: "ground")
super.init(texture: texture, color: color, size: texture.size())
self.name = "Ground"
self.zPosition = -20
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody.contactTestBitMask = PhysicsCategory.Player
physicsBody.categoryBitMask = PhysicsCategory.Ground
physicsBody.collisionBitMask = PhysicsCategory.All
physicsBody.affectedByGravity = false
physicsBody.allowsRotation = false
physicsBody.dynamic = false
physicsBody.mass = 1.99999
self.physicsBody = physicsBody
}
required init?(coder aDecoder: NSCoder) {
fatalError("Not implemented")
}
}
Unless I'm missing something else you're trying to do, you should be able to move the initialization of the PhysicsBody and the other properties from you configure() method and into your init() for the class. Then you can pre-size the texture you pass in on instantiation to result in the proper size for the device.
Also, the position property of the SKSpriteNode can be set by the instantiating class. This means you can accomplish the sizing based on device and the setting of the position property right after instantiating it in your scene and before adding it as a child of the scene.
ADDED:
class Ground : SKSpriteNode {
override init(texture: SKTexture!, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
}
convenience init(texture: String, scene: SKScene, isActive: Bool = false) {
let texture = SKTexture(imageNamed: "\(yourtexturename)")
self.init(texture:teture, color: color, size: scene.size)
self.size = size
self.position = [YOU CAN NOW SET POSITION BASED ON THE SCENE]
self.physicsBody = SKPhysicsBody(rectangleOf: size)
self.physicsBody?.contactTestBitMask = PhysicsCategory.Player
self.physicsBody?.categoryBitMask = PhysicsCategory.Ground
self.physicsBody?.collisionBitMask = PhysicsCategory.All
self.physicsBody?.affectedByGravity = false
self.physicsBody?.allowsRotation = false
self.physicsBody?.isDynamic = false
self.physicsBody?.mass = 1.99999
self.zPosition = -20
self.name = "Ground";
}
required init?(coder aDecoder: NSCoder) {
// Decoding length here would be nice...
super.init(coder: aDecoder)
}
}
You might not need to override the original init() method but I didn't test anything.

Attemped to add a SKNode which already has a parent - Swift

Ok. this code is driving me crazy. It just don't work. The only message I received is "Attemped to add a SKNode which already has a parent". Yes I know that there has been some discussions here, but none of them give the solution I need.
This is the code. I really appreciate any help.
import SpriteKit
class MyScene: SKScene {
let intervalShapeCreation:NSTimeInterval = 2.0 // Interval for creating the next Shape
let gravitationalAcceleration:CGFloat = -0.5 // The gravitational Y acceleration
let shapeSequenceAction = SKAction.sequence([
SKAction.scaleTo(1.0, duration: 0.5),
SKAction.waitForDuration(2.0),
SKAction.scaleTo(0, duration: 0.5),
SKAction.removeFromParent()
])
override init(size: CGSize) {
super.init(size: size)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
addBackground()
initializeScene()
}
// MARK: Level Building
func initializeScene() {
self.physicsWorld.gravity = CGVectorMake(0.0, gravitationalAcceleration)
runAction(SKAction.repeatActionForever(
SKAction.sequence([SKAction.runBlock(self.createShape),
SKAction.waitForDuration(intervalShapeCreation)])))
}
func addBackground() {
let backgroundAtlas = SKTextureAtlas(named: "background")
let background = SKSpriteNode(texture: backgroundAtlas.textureNamed("background"))
background.position = CGPoint(x: size.width/2, y: size.height/2)
background.anchorPoint = CGPointMake(0.5, 0.5)
background.zPosition = -1
background.name = "background"
self.addChild(background)
}
func createShape() {
let newShape = sSharedAllPossibleShapes[0]
print("\n shape creada: \(newShape.name)")
newShape.position = CGPointMake(size.width / 2, CGFloat( Int.random(fromZeroToMax: 500)))
self.addChild(newShape)
newShape.runAction(shapeSequenceAction)
}
}
createShape doesn't actually create a SKShapeNode. It gets the first shape from the sSharedAllPossibleShapes array, then adds it as child to self. The second time you call this method that shape already has a parent and can't be added again.
You have to create a new instance of SKShapeNode. The way I see it your array here really needs to contain the CGPath objects that define the shape, not the nodes themselves because you can't reuse nodes the way you intended to.