Switch Initialization Stream to Subclass: Swift and SpriteKit

I want to create SKShapeNode at a higher level than touchsBegan from SpriteNode, so when I want to add SKShapeNode to the screen from the touchhesBegun event on this sprite, the form already exists, and I just add it to the screen from the inside of the touchsBegan override.

TL DR, in my SKSpriteNode, I'm trying to pre-build an SKShapeNode that will be used as an animated ring when Sprite touches.

I would like to create an 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 line width, ready to use when creating 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 own ring:

  let ring = SKShapeNode(circleOfRadius: ringSize) 

And immediately I greet with a mysterious love:

You cannot use an instance of the "ringSize" member in a property initializer. Property initializers start before "I."

Fine OK. I understood. You want to think that a functional class call to create a property must be executed before the values ​​are assigned to self. Neither here nor there, I think I get the trick and can get around this 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 } 

It makes no mistakes, and my excitement builds.

So, I am adding another line to create the actual ring: ....

  myRing = createRing() 

Dead again:

! expected declaration

I absolutely do not understand what this means and began to randomly try to wander.

One of them goes into my already messy convenience initializer and adds myRing = createRing() there ... and this WORKS!

How and why does this work, and is this the best / right / right way to bypass the initialization stream?

:: EDIT :: UPDATE :: Full code context ::

Here is a complete class with my weird and obscure initializers.

 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 } 
+6
source share
3 answers

And immediately I greet with a mysterious love:

You cannot use an instance of "ringSize" in a property initializer; property initializers start before "self".

Thus, one way to solve this problem would be to make ringSize available by default ringSize different way, for example.

 static let defaultRingSize: CGFloat = 64 var ringSize: CGFloat = Circle.defaultRingSize let ring = SKShapeNode(circleOfRadius: Circle.defaultRingSize) 

... but I doubt why you even have a var ringSize property. If you don't have a didSet observer, so if you change its value, can you update the ring form?

Dead again:

! Expected Declaration

It was not clear in your question how you actually caused this, but I think 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 is that you put the statement in the body of your class, but only declarations are allowed in the body.

One of them goes into my already messy convenience initializer and adds myRing = createRing () there ... and this WORKS!

How and why this work

All your own class instance variables must be initialized before calling super.init . Since myRing is the default, the compiler effectively inserts the myRing initialization before calling super.init in the designated initializer, for example:

 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 subsequently change it to the configured SKShapeNode that you really need.

Is this the best / right / right way to bypass the initialization stream?

Well, “circling the drain” means “failure”, so I think you ask if this is the “best / right / right way” to fail during initialization ... I suppose this is not the best way to fail, since you didn't really work at the end.

Or maybe you meant "I hate how Swift does initialization, so I'm going to cast some kind of shade", in which case you have not seen anything yet.

But perhaps you really meant “this is the best / right / right way to initialize my instance”, in which case the “best” and “right” and “right” are quite subjective.

But I can objectively indicate that you are creating an SKShapeNode (as the default value of myRing ) to throw it away immediately and create another SKShapeNode . So what a waste. You also received calls to createRing in both of your convenience initializers, but you can decompose them into a designated initializer.

But I wouldn’t even do it that way. SKShapeNode path property is configurable, so you can simply create a default SKShapeNode and then change its path after calling super.init . It also makes it easier to handle changes to ringSize and other properties, since you can make all changes with a single method that knows how to myRing properties.

This is how I will probably write my 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 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 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 } } 
+1
source

Your createRing () method is inside the Ball class, so you need to instantiate the Ball first.

The easy way . You can change instance creation to

 let ball = Balls() let myRing = ball.createRing() 
+4
source

I'm a little confused as to where you posted

  myRing = createRing() 

a line of code, but I'm wondering if this setting will help to solve your problem.

 lazy var myRing: SKShapeNode = { let ring = SKShapeNode(circleOfRadius: ringSize) ring.strokeColor = ringColor ring.lineWidth = ringWidth return ring }() 

This myRing method will be created when it is accessed, which should be after the Balls class instance is created, which means that ringSize, ringColor and ringWidth will exist.

Based on your update, I think your best bet might just be to make your three cyclic variables “static”. Thus, they will exist and have a given value before initializing the main class. The errors you see are related to the fact that you created instance variables. They will exist only when the instance has been initialized. Therefore, if you tried to call the ring method as a variable declaration, or if you did this in init before , then self / super init is called, so instance variables will not be available. The most recent code you add should work because you create an instance before attempting to generate a ring. Hope this makes sense and helps.

+2
source

Source: https://habr.com/ru/post/1011814/


All Articles