绕过子类中的Initialization:Swift和SpriteKit

时间:2023-01-23 14:36:45

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.

我想创建一个比SpriteNode的touchesBe更高级别的SKShapeNode,所以当我想从这个sprite上的touchesBegun事件将SKShapeNode添加到屏幕时,形状已经存在,我只是从内部将它添加到屏幕touchesBegan覆盖。

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.

TL; DR,在我的SKSpriteNode中,我正在尝试预先构建SKShapeNode,当触摸Sprite时,它将用作动画环。

I'd like to create the SKShapeNode with variable/constants, so I can easily edit its values...

我想用变量/常量创建SKShapeNode,这样我就可以轻松编辑它的值...

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...

因此,在子类的根目录中,我有颜色,大小和线宽的变量,可以用于创建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.

无法在属性初始化程序中使用实例成员'ringSize',属性初始化程序在'self'可用之前运行。

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:

精细。好。我知道了。您希望在将值分配给self之前,应该在创建属性时对类进行函数调用。无论是在这里还是在那里,我认为我变得狡猾,并且可以通过将所有内容包装在函数中来解决这个问题:

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!

其中一个是进入我已经凌乱的便利初始化程序并在那里添加myRing = createRing()......这就行了!

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
    }

3 个解决方案

#1


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.

无法在属性初始化程序中使用实例成员'ringSize',属性初始化程序在'self'可用之前运行。

So one way around this problem would be to make the default ringSize available another way, e.g.

因此解决这个问题的一种方法是使默认的ringSize以另一种方式可用,例如,

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?

...但我怀疑为什么你甚至有像这样的var ringSize属性。你不应该有一个didSet观察者,所以如果你改变它的值,你可以更新戒指的形状?

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!

其中一个是进入我已经凌乱的便利初始化程序并在那里添加myRing = createRing()......这就行了!

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:

必须在super.init调用之前初始化所有类的实例变量。由于myRing有一个默认值,编译器会在你指定的初始化程序中调用super.init之前有效地插入myRing的初始化,如下所示:

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.

由于您声明了var myRing,因此可以稍后将其更改为您真正想要的自定义SKShapeNode。

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.

或许你的意思是“我讨厌Swift初始化的方式,所以我会抛出一些阴影”,在这种情况下,你还没有看到它。

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.

但我可以客观地指出你正在创建一个SKShapeNode(作为myRing的默认值),只是为了立即扔掉它并创建另一个SKShapeNode。所以那是浪费。您还在两个便利初始化程序中调用了createRing,但您可以将它们分解为指定的初始化程序。

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.

但我甚至不会这样做。 SKShapeNode的path属性是可设置的,因此您可以创建一个默认的SKShapeNode,然后在调用super.init后更改其路径。这也使得更容易处理对ringSize和其他属性的更改,因为您可以通过一个知道如何使myRing与属性匹配的方法来汇集所有更改。

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
    }
}

#2


4  

Your createRing() method is inside Ball class so you need to create an instance of Ball first.

你的createRing()方法在Ball类中,所以你需要先创建一个Ball实例。

Simple way - You can change creation of instance to

简单方法 - 您可以将实例的创建更改为

let ball = Balls()
let myRing = ball.createRing()

#3


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.

这样就可以在访问时创建myRing,这应该在实例化Balls类之后,这意味着ringSize,ringColor和ringWidth都将存在。

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.

根据您的更新,我认为您最好的选择可能是让您的三个环变量“静态让”。这样它们将在初始化主类之前存在并具有设定值。您看到的错误是因为您创建了实例变量。这些仅在实例初始化时才存在。因此,如果您尝试将ring方法作为变量的声明来调用,或者如果在调用self / super init之前在init中执行它,那么实例变量将无法访问。您添加的最新代码应该正常工作,因为您在尝试生成环之前创建了实例。我希望这是有道理和有帮助的。

#1


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.

无法在属性初始化程序中使用实例成员'ringSize',属性初始化程序在'self'可用之前运行。

So one way around this problem would be to make the default ringSize available another way, e.g.

因此解决这个问题的一种方法是使默认的ringSize以另一种方式可用,例如,

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?

...但我怀疑为什么你甚至有像这样的var ringSize属性。你不应该有一个didSet观察者,所以如果你改变它的值,你可以更新戒指的形状?

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!

其中一个是进入我已经凌乱的便利初始化程序并在那里添加myRing = createRing()......这就行了!

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:

必须在super.init调用之前初始化所有类的实例变量。由于myRing有一个默认值,编译器会在你指定的初始化程序中调用super.init之前有效地插入myRing的初始化,如下所示:

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.

由于您声明了var myRing,因此可以稍后将其更改为您真正想要的自定义SKShapeNode。

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.

或许你的意思是“我讨厌Swift初始化的方式,所以我会抛出一些阴影”,在这种情况下,你还没有看到它。

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.

但我可以客观地指出你正在创建一个SKShapeNode(作为myRing的默认值),只是为了立即扔掉它并创建另一个SKShapeNode。所以那是浪费。您还在两个便利初始化程序中调用了createRing,但您可以将它们分解为指定的初始化程序。

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.

但我甚至不会这样做。 SKShapeNode的path属性是可设置的,因此您可以创建一个默认的SKShapeNode,然后在调用super.init后更改其路径。这也使得更容易处理对ringSize和其他属性的更改,因为您可以通过一个知道如何使myRing与属性匹配的方法来汇集所有更改。

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
    }
}

#2


4  

Your createRing() method is inside Ball class so you need to create an instance of Ball first.

你的createRing()方法在Ball类中,所以你需要先创建一个Ball实例。

Simple way - You can change creation of instance to

简单方法 - 您可以将实例的创建更改为

let ball = Balls()
let myRing = ball.createRing()

#3


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.

这样就可以在访问时创建myRing,这应该在实例化Balls类之后,这意味着ringSize,ringColor和ringWidth都将存在。

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.

根据您的更新,我认为您最好的选择可能是让您的三个环变量“静态让”。这样它们将在初始化主类之前存在并具有设定值。您看到的错误是因为您创建了实例变量。这些仅在实例初始化时才存在。因此,如果您尝试将ring方法作为变量的声明来调用,或者如果在调用self / super init之前在init中执行它,那么实例变量将无法访问。您添加的最新代码应该正常工作,因为您在尝试生成环之前创建了实例。我希望这是有道理和有帮助的。