Instantiating a base class inheriting from SKSpriteNode in Swift - swift

I'm trying to write a generic Alien 'blueprint' class for my first iPhone game. This class will contain a handful of properties and methods that will be inherited by all the actual alien subclasses. That said, Alien really shouldn't have a texture and shouldn't have its own set of defined values.
//Generic alien type: a blue-print of sorts
class Alien:SKSpriteNode{
let velocityVector:CGVector
let startPos:CGPoint
init(texture:SKTexture, startPosition startPos:CGPoint,moveSpeed: CGFloat,velocityVector:CGVector){
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
self.velocityVector = normalizeVector(velocityVector)
self.position = position
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Every time I try setting this up I run into a handful of errors such as :
"Use of instance member 'normalizeVector' on type GameScene; did you mean to use a value of type GameScene instead?"
normalizeVector is a function that is written above the Alien class (within GameScene). I'm not sure what this means, as this is a normal function within the GameScene. If I remove this bit, I still receive the error:
"Property self.velocityVector is not initialized at super.init call"
I'm confused because I had thought that super.init() was only necessary because I'm making a subclass of SKSpriteNode and that it wouldn't need to have the subclass-specific properties passed to it.
Lastly, is there a way to call super.init() without having an actual texture as this blueprint class isn't ever going to be "displayed"?
After looking through Apples Documentation on classes/initialization I'm still stuck. Any help would be great, thanks.

You have a couple of issues, first of all, as the error message says, you need to initialize all variables before you call super.init:
init(texture:SKTexture, startPosition startPos:CGPoint,moveSpeed: CGFloat,velocityVector:CGVector){
self.velocityVector = Alien.normalizeVector(velocityVector)
self.startPos = startPos
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
self.position = position
}
Second, it sounds like you're defining normalizeVector in a subclass or somewhere else? What I did was to define it as a static method (since it's used before this is initialized, it can't be an instance method) and then you can use it in the init method. Put it all together and it looks like:
class Alien:SKSpriteNode {
static func normalizeVector(vector:CGVector) -> CGVector {
let len = sqrt(vector.dx * vector.dx + vector.dy * vector.dy)
return CGVector(dx:vector.dx / len, dy:vector.dy / len)
}
let velocityVector:CGVector
let startPos:CGPoint
init(texture:SKTexture, startPosition startPos:CGPoint,moveSpeed: CGFloat,velocityVector:CGVector){
self.velocityVector = Alien.normalizeVector(velocityVector)
self.startPos = startPos
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
self.position = position
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

You declared velocityVector as a non optional property so of course if your don't populate it within your initializer you get a compiler error.
This is why when you comment this line your get a compile error.
self.velocityVector = normalizeVector(velocityVector)

Related

Initialize a Custom SKSpriteNode with its properties from the editor

I made my own simple custom class called SKGlowNode as a subclass of SKSpriteNode. I originally wanted to be able to create a custom class and input all the init parameters in the editor like you can with a SKSpriteNode. However, I found out that this is not possible yet and I had to try to do it with user data. I set up my node in the editor like this:
And simply did the custom class like this:
Here is my class for SKGlowNode
import Foundation
import SpriteKit
class SKGlowNode: SKSpriteNode{
let colors = [UIColor.blue,UIColor.green,UIColor.red,UIColor.orange,UIColor.purple,UIColor.yellow,UIColor.white,UIColor.magenta]
required init?(coder aDecoder: NSCoder) {
super.init(texture: texture, color: .black, size: size)//error here
let glowIndex = self.userData?.value(forKey: "glowColorIndex") as? Int ?? 0
let glowTexture = self.userData?.value(forKey: "glowTexture") as? String ?? "squareBg"
addGlow(radius: 75, texture: SKTexture(imageNamed: glowTexture), color: colors[glowIndex])
//This glow function works and is an extension from a SKSpriteNode where it creates a glow from a texture with certain color
}
}
Obviously I get an error on the line specified because I cant initialize the superclass using those variables, but I have no idea what to put in order to get it to initialize with the values already defined in the editor as it would work if there was no custom class. Thanks
You need to have the appropriate Super.init for your class init.
If you are instantiating the subclassed SKSpriteNode in an init() whether it be the default or a custom init with custom parameters you use
super.init(texture: SKTexture, color: SKColor, size: CGSize)
if you are instantiating it from an sks scene file then required init?(coder aDecoder: NSCoder) gets called and you need to have
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

Editing properties of an SKSpriteNode custom class?

In my custom SKSpriteNode class, I want to be able to change properties such as anchorPoint, posistion, etc. within the custom class so I don't need to elsewhere.
import Foundation
import SpriteKit
open class Crank:SKSpriteNode {
init() {
super.init(texture: SKTexture(imageNamed: "crank"), color:
NSColor.white, size: CGSize(width: 155.0, height: 188.0))
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
How would I edit other properties?
import Foundation
import SpriteKit
class Crank: SKSpriteNode {
init(imageNamed image: String, position at: CGPoint, withAnchor anchor: CGPoint) {
super.init(imageNamed: image)
self.position = position
self.anchorPoint = anchor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
(Written from memory, so may contain small errors, but is broadly correct).
and then in your main code:
let myCrank = Crank(imageNamed: "Crank.png", at: CGPoint(300, 500), withAnchor: CGpointZero)
My answer notwithstanding, Alessandro has a point. I think that it's better to set 'standard' properties in the normal place.
If you are going to set any of the standard SKSpriteNode properties inside the actual class, then I think specifying them in an initialiser is good practice as it makes the m more visible,. Debugging a program where you don't appears to set a node's position, or texture etc. would be problematic.
Another way to do it would be to create a protocol and apply it to the standard SpriteKit classes that you are subclassing (or their ancestor).
For example:
protocol CustomSKNode
{
var position:CGFloat { get set }
var zRotation:CGFloat { get set }
// ...
}
extension SKNode: CustomSKNode {}
Once you've done this somewhere in your project, you'll be able to access these properties of on any of your custom SKNode or SKSpriteNode without having to import SpriteKit.

Circling the drain of Initialization in subclass: Swift and SpriteKit

I want to create a SKShapeNode at a higher level than the touchesBegan of a SpriteNode so when I want to add the SKShapeNode to the screen from the touchesBegun event on this sprite, the shape already exists, and I simply add it to the screen from within the touchesBegan override.
TL;DR, in my SKSpriteNode, I'm trying to pre-build the SKShapeNode that will be used as an animated ring when the Sprite is touched.
I'd like to create the SKShapeNode with variable/constants, so I can easily edit its values...
So in the root of the subclass I have variables for color, size, and linewidth, ready to be used in the creation of the SKShapeNode...
class Balls: SKSpriteNode {
var ringSize: CGFloat = 64
var ringColor: SKColor = SKColor.white
var ringWidth: CGFloat = 16
....
Further down, but still at the root of the class, I create my ring:
let ring = SKShapeNode(circleOfRadius: ringSize)
And am instantly greeted with the lovingly cryptic:
Can not use instance member 'ringSize' within property initializer,
property initializers run before 'self' is available.
Fine. Ok. I get it. You want to think that a functional call to a class to create a property should be done before values are assigned to self. Neither here nor there, I think I'm getting cunning and can get around that by wrapping everything in a function:
class Balls: SKSpriteNode {
var ringSize: CGFloat = 64
var ringColor: SKColor = SKColor.white
var ringWidth: CGFloat = 16
var myRing = SKShapeNode()
func createRing() -> SKShapeNode{
let ring = SKShapeNode(circleOfRadius: ringSize)
ring.strokeColor = ringColor
ring.lineWidth = ringWidth
return ring
}
This generates no errors, and my excitement builds.
So I add another line, to create the actual ring:
....
myRing = createRing()
Dead again:
! Expected
declaration
I have absolutely no idea what this means and began to randomly attempt weird things.
One of them is heading into my already messy convenience initializer and adding myRing = createRing() in there... and this WORKS!
How and why does this work, and is this the best/right/proper way to be circling the drain of initialization?
:: EDIT:: UPDATE :: Full Code Context ::
Here's the full class with my bizarre and misunderstood initialisers.
import SpriteKit
class Circle: SKSpriteNode {
var ringSize: CGFloat = 96
var ringColor: SKColor = SKColor.white
var ringWidth: CGFloat = 8
var myRing = SKShapeNode()
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
}
convenience init() {
self.init(color: SKColor.clear, size: CGSize(width: 100, height: 100))
myRing = createRing()
addChild(myRing)
print("I'm on the screen")
explodeGroup = create_explosionActionGroup()
}
convenience init(color: UIColor, size: CGSize, position: CGPoint) {
self.init(color: color, size: size)
self.position = position
myRing = createRing()
explodeGroup = create_explosionActionGroup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createRing() -> SKShapeNode{
let ring = SKShapeNode(circleOfRadius: ringSize)
ring.strokeColor = ringColor
ring.lineWidth = ringWidth
return ring
}
Your createRing() method is inside Ball class so you need to create an instance of Ball first.
Simple way - You can change creation of instance to
let ball = Balls()
let myRing = ball.createRing()
I'm slightly confused as to where you placed the
myRing = createRing()
line of code but I'm wondering if this setup would help solve your problem
lazy var myRing: SKShapeNode = {
let ring = SKShapeNode(circleOfRadius: ringSize)
ring.strokeColor = ringColor
ring.lineWidth = ringWidth
return ring
}()
This way myRing would be created when it was accessed which should be after the Balls class is instantiated which would mean that ringSize, ringColor and ringWidth would all exist.
Based on your update I think your best bet might be to just make your three ring variables ‘static let’ instead. That way they will exist and have the set value before initializing the main class. The errors you’re seeing are because you created instance variables. Those will only exist when the instance has been initialized. So if you tried to call the ring method as the declaration of the variable or if you did it within the init before self/super init is called then the instance variables wouldn’t be accessible. The most recent code you’ve added should be working because you create the instance before attempting to generate the ring. I hope that makes sense and helps.
And am instantly greeted with the lovingly cryptic:
Can not use instance member 'ringSize' within property initializer, property initializers run before 'self' is available.
So one way around this problem would be to make the default ringSize available another way, e.g.
static let defaultRingSize: CGFloat = 64
var ringSize: CGFloat = Circle.defaultRingSize
let ring = SKShapeNode(circleOfRadius: Circle.defaultRingSize)
... but I question why you even have a var ringSize property like that. Shouldn't you have a didSet observer on it, so that if you change its value, you can update the shape of ring?
Dead again:
! Expected declaration
You weren't clear, in your question, how you actually triggered this, but I guess you tried something like this:
class Circle: SKSpriteNode {
var ringSize: CGFloat = 96
var myRing = SKShapeNode()
myRing = createRing() // “Expected declaration” error on this line
The problem here is that you've placed a statement in the body of your class, but only declarations are allowed in the body.
One of them is heading into my already messy convenience initializer and adding myRing = createRing() in there... and this WORKS!
How and why does this work
All of your class's own instance variables must be initialized before a super.init call. Since myRing has a default value, the compiler effectively inserts the initialization of myRing before the call to super.init in your designated initializer, like this:
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
// Compiler-inserted initialization of myRing to the default
// value you specified:
myRing = SKShapeNode()
super.init(texture: texture, color: color, size: size)
}
Since you declared var myRing, you can then change it later to the customized SKShapeNode you really want.
is this the best/right/proper way to be circling the drain of initialization?
Well, “circling the drain” means “failing”, so I guess you're asking if this is “the best/right/proper way” to fail at initialization… I suppose it's not the best way to fail, since you didn't actually fail in the end.
Or maybe you meant “I hate the way Swift does initialization so I'm going to throw some shade”, in which case, you ain't seen nothin' yet.
But maybe you really meant “is this the best/right/proper way to initialize my instance”, in which case, well, “best” and “right” and “proper” are pretty subjective.
But I can objectively point out that you're creating an SKShapeNode (as the default value of myRing) just to immediately throw it away and create another SKShapeNode. So that's a waste. You've also got calls to createRing in both of your convenience initializers, but you could factor them out into the designated initializer.
But I wouldn't even do it quite like that. SKShapeNode's path property is settable, so you can just create a default SKShapeNode and then change its path after the call to super.init. That also makes it easier to handle changes to ringSize and the other properties, because you can funnel all the changes through a single method that knows how to make myRing match the properties.
Here's how I'd probably write your class:
import SpriteKit
class Circle: SKSpriteNode {
var ringSize: CGFloat = 96 {
// Use an observer to update myRing if this changes.
didSet { configureMyRing() }
}
var ringColor = SKColor.white {
didSet { configureMyRing() }
}
var ringWidth: CGFloat = 8 {
didSet { configureMyRing() }
}
// This can be a let instead of a var because I'm never going to
// set it to a different object. Note that I'm not bothering to
// initialize myRing's path or any other property here, because
// I can just call configureMyRing in my init (after the call to
// super.init).
let myRing = SKShapeNode()
override init(texture: SKTexture?, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
// Call this now to set up myRing's path and other properties.
configureMyRing()
}
convenience init() {
self.init(color: SKColor.clear, size: CGSize(width: 100, height: 100))
// No need to do anything to myRing now, because my designated
// initializer set it up completely.
addChild(myRing)
print("I'm on the screen")
// Commented out because you didn't provide this property
// or method in your question.
// explodeGroup = create_explosionActionGroup()
}
convenience init(color: SKColor, size: CGSize, position: CGPoint) {
self.init(color: color, size: size)
self.position = position
// Commented out because you didn't provide this property
// or method in your question.
// explodeGroup = create_explosionActionGroup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configureMyRing() {
myRing.path = CGPath(ellipseIn: CGRect(x: -ringSize / 2, y: -ringSize / 2, width: ringSize, height: ringSize), transform: nil)
myRing.strokeColor = ringColor
myRing.lineWidth = ringWidth
}
}

Using a class (or instance) property inside of another classes initilizer in swift

I'm relatively new to Swift, and I wanted to know if there was a way to reference a class's property inside of a separate class initializer? For example: if I have a class Person with the property position, is there a way to initialize a Pants class such that its position is the same as Person's? Here's my code:
class Pants:SKSpriteNode{
init(){
let pants = SKTexture(imageNamed: "Sprites/pants.jpg")
pants.setScale(0.5)
super.init(texture: pants, color: UIColor.clearColor(), size: pants.size())
//self.position.x = aPerson.position.x + (aPerson.size.width / 2)
//self.position.y = aPerson.position.y - (aPerson.size.height * 0.04)
self.position = Person.getPos()//CGPoint(x: 200,y: 200)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
At first I tried referencing aPerson which is an instance of Person but I received the error: Instance member aPerson cannot be used on type GameScene. I think understand why it doesn't make much sense to reference an instance in this case- as the instance may not exist by the time of reference? But I don't really know what this error message means- any clarification would be great. I then thought to use a static getter method within the Person class that just returned it's position property. This also doesn't seem to work. Any suggestions would be awesome!
One solution is to add a parameter to your initializer (as suggested by Paul Griffiths in a comment above):
class Pants: SKSpriteNode {
init(aPerson: Person) {
let pants = SKTexture(imageNamed: "Sprites/pants.jpg")
pants.setScale(0.5)
super.init(texture: pants, color: UIColor.clearColor(), size: pants.size())
self.position.x = aPerson.position.x + (aPerson.size.width / 2)
self.position.y = aPerson.position.y - (aPerson.size.height * 0.04)
self.position = aPerson.getPos()//CGPoint(x: 200,y: 200)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then wherever you want to create a Pants instance, you must pass a person:
let somePerson = Person()
let pants = Pants(aPerson: somePerson)
I assume Pants are worn by Person? so instead, work relative, not absolute.
Make Pants a child node of person, then all you need to worry about is the distance from the center of Person, to the Pant line. If this will always be a constant number (Like 10 pixels below center) then hard code it. If the Pant line changes, then pass in the pant line like #Santa Claus suggests
====Assume some code here please======
person.pantline = -10;
person.addChild(Pants(pantline:person.pantline))
=====================================
class Pants: SKSpriteNode {
convenience init(pantline: Int) {
self.init(imageNamed: "Sprites/pants.jpg")
self.setScale(0.5) //Why?
self.anchorPoint = CGPointMake(0.5,1)
self.position.y = pantline
}
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
}
override init (texture: SKTexture, color: UIColor, size: CGSize)
{
super.init(texture: texture, color: color, size: size)
}
}

Is it possible for a subclass of SKShapeNode ot use one of its convenience initializers?

I have a class that inherits from SKShapeNode because I want to add additional properties. The problem I am having is that I want to be able to create a "circle" by using SKShapeNode's "convenience" initializer SKShapeNode.init(circleOfRadius:CGFloat), but since it is a convenience initializer the compiler complains with a message " Must call a designated initializer of the class SKShapeNode", because it only allows calling of a "designated initializer" of the parent class, but the only "designated" initializer of SKShapeNode is SKShapeNode.init(), which would not create the circle shape i want.
Below is my code with the compiler error.
So the question is, can I subclass SKShapeNode and still somehow have access to the "convenience" initializer to initialize it with a circle shape? Seems to me that it is not possible. And so are there any workarounds?
thanks
The designated initializer for SKShapeNode is just plain old init, so that's what we have to call in our custom init method. To create a circle shape then, we will have to create the path ourselves. Fortunately, CGPath has a convenience initializer we can use to create the circle. Here ya go:
import SpriteKit
class CustomSKShapeNode:SKShapeNode{
var groupName:String!
init(groupName:String,radius:CGFloat){
super.init()
self.groupName = groupName
let rect = CGRect(x:-radius,y:-radius,width:radius * 2,height:radius * 2)
self.path = CGPath(ellipseIn: rect, transform: nil)
self.fillColor = UIColor.yellow
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var node = CustomSKShapeNode(groupName:"group1",radius:40)
node.position = CGPoint(x:40,y:40) // bottom left corner
addChild(node)