6

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
    }
Confused
  • 6,048
  • 6
  • 34
  • 75
  • My guess is that you are using var ringSize befor init the class Balls. If you want do that, you must declare static var ringSize and static other var. Or you can do that inside Balls init() func if you want instance var – Simone Pistecchia Oct 28 '16 at 12:33
  • The problem with doing it inside Balls init() is that nothing created there is available within the touchesBegan function of Balls. @SimonePistecchia – Confused Oct 28 '16 at 12:52
  • and if I use Static, the elegance is gone, and I need to make a new variable if I want to change any value at any time, for any given instance. – Confused Oct 28 '16 at 12:54
  • and static vars or lets don't solve the "expected declaration" issue – Confused Oct 28 '16 at 12:58
  • The init is the correct place to do this. If you want to create it globally to the class, then you need to declare your variables like `ringSize` static because your instance does not know what `ringSize` is before it is created. Just because you place `ringSize` before `ring` in your code does not mean the compiler will create these variables in this order. – Knight0fDragon Oct 28 '16 at 14:48
  • correct place to do what? – Confused Oct 28 '16 at 14:53
  • You can create the shape in init func, after add it in the scene when the user touches the node – Simone Pistecchia Oct 28 '16 at 15:15
  • @SimonePistecchia I know I can. Read the question, that's exactly what I discovered. Look right at the bottom, I ask the question "How and why does this work, and is this the best/right/proper way to be circling the drain of initialisation." – Confused Oct 28 '16 at 15:26
  • @Confused, you seem to have trouble understanding english. "Is this the best/right/proper way" -> 'The init is the correct place to do this'. Correct means right. "How and why it works?" Because your instance knows at this time in the life cycle that `ringSize` exists. This can be deduced from me saying that the instance does't know what `ringSize` is before it is created. This would mean that init method happens after it is created, more importantly, it exists completely after super.init (or self.init if using a convenience init) occurs, thus allowing you to use these variables. – Knight0fDragon Oct 30 '16 at 07:37
  • Also, I do not know what you mean by "Circling the drain". "Circling the drain" means you are about to fail, where "In the drain" means you have failed. – Knight0fDragon Oct 30 '16 at 07:39
  • Wait a minute... I seem to have trouble understanding English, according to you, yet you're uncertain about the meaning of a common colloquialism? If my memory serves me right, this is the third time you've questioned my ability to understand English. At this point, I think it's time you questioned your communication prowess. Now, back to the question: You said "correct place to do this." I want to know what is "this"? My deductive skills have failed me, you're right. I don't know what "this" is. – Confused Oct 30 '16 at 08:02
  • The common colloquialism is "you are about to fail", that is "what circling the drain means". Your very definition of "this" is what YOU call "this". It is not my duty to redundantly state what you are asking. If you ask, Is 1+1=2 correct?, and I reply, "this is correct", that is correct English. I do not need to say "1+1=2 is correct" and you shouldn't be saying "I do not know what this is", so yes, I will question your understanding of English when I need to explain these matters. This is fine, not everybody understands English, but at least if I know you do not get it, I can help better – Knight0fDragon Oct 30 '16 at 21:07
  • your comments exist in a stream without context. Surely you realise this. "this" could, therefore, be anything because you didn't contextualise, as I've demonstrated in the prior sentence. English is not your strong suit, no matter how much you protest. – Confused Oct 31 '16 at 00:55
  • and "circling the drain" and "you are about to fail" have about as much in common as your command of 'english' and your belief in your abilities of communication. – Confused Oct 31 '16 at 00:57
  • Whatever man, live in your own bubble, I do not need to "contextualise" , you have already provided the context, and ok smart guy, what do YOU think circling the drain means. – Knight0fDragon Oct 31 '16 at 00:58
  • You don't need to do anything on SO. But when you choose to make ambiguous and condescending comments, be prepared to be called out on it, questioned about their context and queried about the cryptic lack of meaning you expect others to deduce from your offerings on high. – Confused Oct 31 '16 at 01:00
  • And I say this in general, as a life lesson, regardless of whatever rules and administrative garble you want to hide behind to "assert" your significance and 'brilliance'. I'm well aware of your intellect, knowledge and understanding. Also aware of your inability to sagely communicate any of it. – Confused Oct 31 '16 at 01:03
  • I don't start out as condescending, you make me go there because you choose to not understand how a conversation works, and then you yell at everybody because you can never "understand" anybody, not even tutorials, because they have to talk to you like you are a 2 year old with absolutely no knowledge of anything in this world – Knight0fDragon Oct 31 '16 at 01:03
  • When you're done talking like the 2 year old you reference, take a deep breath, and contextualise "this". What were you talking about? What were you referring to? It's a legit question because your "this" exists without context, and you're admonishing me for asking it. Perhaps you know it, but I don't. Have just a little compassion for the ignorant, oh wondrous master. – Confused Oct 31 '16 at 01:05
  • Look at the question: "How and why does this work?" have you, at any point, answered this question? If you have, I missed it. – Confused Oct 31 '16 at 01:08
  • This is your question: "How and why does this work, and is this the best/right/proper way to be circling the drain of initialisation?" You have already provided the context:"One of them is heading into my already messy convenience initialiser and adding myRing = createRing() in there... and this WORKS!" My response is: "the init is the correct place to this." Why must I provide context in a comment, we are having an informal conversation, redundancy of the context is not necessary. – Knight0fDragon Oct 31 '16 at 01:12
  • You asked 3 questions, how this works, why this works, and is this best. Obviously "this is correct" would only answer one of these questions, you can obviously deduce which one this belongs to – Knight0fDragon Oct 31 '16 at 01:13
  • I then go onto explain the how and why by explaining the reason that the static does work. You could then apply some critical thinking on your own, and actually learn how to problem solve, instead of being given the answer. The reason why static works is because at compile time ringsize exists before init happens, this would mean if it is not static, then ringsize would not exist before init happens, which means ringsize exists after init happens. You now have the Why answered. You then look at your code to find out when init happens, and you create afterwards. You now have How. – Knight0fDragon Oct 31 '16 at 01:20
  • Congratulations, you were able to figure something out without having your hand held, you can now apply this newly found technique of learning to other aspects of programming, and next thing you know, you are teaching yourself how to solve your own problems. – Knight0fDragon Oct 31 '16 at 01:22
  • I'm more confused than ever before. Do you think you could lay this out as an answer? @Knight0fDragon, that would be a great help. I might be able to see through the haze of my own stupidity, then. – Confused Oct 31 '16 at 12:49

3 Answers3

4

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()
Saranjith
  • 11,242
  • 5
  • 69
  • 122
  • To quote myself, the question is: "One of them is heading into my already messy convenience initialiser 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 initialisation?" – Confused Mar 02 '18 at 05:17
  • Can you pls add relevant things in comments to your question so that we can understand what your pblm exactly is. – Saranjith Mar 03 '18 at 05:58
  • Looking at a specific problem might make it easier for you to find a solution to that problem. This isn't that type of question. It's a question about the hows and whys of initialisation in Swift. The problem, such as it is, only serves as a framework to discuss and explain. It is an example. Feel free to formulate your own example to explain and discuss how to best use and perform initialisation in a similar manner/situation. – Confused Mar 03 '18 at 06:30
2

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.

Steve
  • 921
  • 1
  • 7
  • 18
1

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
    }
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • This is amazing. Here's the full reasoning, such as it is, for the use of ring sizes... https://github.com/dissidently/CircleExplodeRing/blob/master/animationTestGround/Ring.swift – Confused Mar 03 '18 at 11:20
  • It is quite fair to say I have NO IDEA what I'm doing with initialisation and was simply exploiting the copy facility to get decent "particle-like" performance from these animations. My layout of code probably speaks for itself... I favour tables and visual coding over actually coding. Thank you very much for this amazing answer and your time and consideration – Confused Mar 03 '18 at 11:25
  • on "circling the drain", it doesn't necessarily mean failure. It's an odd idiom. When used to talk about self (that's me), it's an attempt to describe how it feels to be fighting with the rules and regulations (and obligations) of initialisation in Swift, and succeeding, but not knowing WHY or HOW I succeeded. In this case I'm trying to convey that I'm still circling the drain of initialisation because although something's successfully fallen through the drain... it has gone done a drain, for me, and I'm none the wiser, and going to be doing this again, soon, because I don't know why it works. – Confused Mar 03 '18 at 11:32
  • Soon after this I kind of left using Swift and SpriteKit, for various reasons. But both still intrigue me. They're more work than they should be, I feel, so I use other game engines now, and get a lot more done in a lot less time, with far greater ease. But Swift still holds a special place in my heart, and I always hold out (a very naive) hope that Apple will invest some of its billions in making a great pair of game engines - for everyone to enjoy using. – Confused Mar 03 '18 at 11:36