【问题标题】:Circling the drain of Initialization in subclass: Swift and SpriteKit在子类中循环初始化的消耗:Swift 和 SpriteKit
【发布时间】:2016-10-28 12:25:11
【问题描述】:

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

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

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

所以在子类的根中,我有颜色、大小和线宽的变量,准备好用于创建 SKShapeNode...

class Balls: SKSpriteNode {

    var ringSize: CGFloat = 64
    var ringColor: SKColor = SKColor.white
    var ringWidth: CGFloat = 16

....

再往下,但仍然是类的根,我创建了我的环:

 let ring = SKShapeNode(circleOfRadius: ringSize)

我立即以可爱的神秘方式迎接:

不能在属性初始化器中使用实例成员“ringSize”, 属性初始化程序在 'self' 可用之前运行。

很好。行。我得到它。您想认为应该在将值分配给 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
        }

这不会产生任何错误,而且我的兴奋感越来越强。

所以我添加了另一行来创建实际的环: ....

 myRing = createRing()

又死了:

!预期的 声明

我完全不知道这意味着什么,并开始随机尝试奇怪的事情。

其中一个正在进入我已经很乱的便利初始化程序,并在其中添加 myRing = createRing()...这可行!

这是如何以及为什么起作用的,这是绕过初始化消耗的最佳/正确/正确方法吗?

:: EDIT:: UPDATE :: 完整代码上下文 ::

这是我奇怪和被误解的初始化程序的完整课程。

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
    }

【问题讨论】:

  • 我的猜测是你在初始化类 Balls 之前使用了 var ringSize。如果你想这样做,你必须声明 static var ringSize 和 static other var。或者,如果您需要实例 var,您可以在 Balls init() func 中执行此操作
  • 在 Balls init() 中执行此操作的问题在于,在 Balls 的 touchesBegan 函数中没有任何可用的内容。 @SimonePistecchia
  • 如果我使用静态,优雅就没有了,如果我想随时更改任何值,对于任何给定的实例,我需要创建一个新变量。
  • 和静态变量或者让我们不解决“预期声明”问题
  • init 是执行此操作的正确位置。如果你想在类中全局创建它,那么你需要声明你的变量,比如ringSizestatic,因为你的实例在创建之前不知道ringSize是什么。仅仅因为您在代码中将ringSize 放在ring 之前并不意味着编译器会按此顺序创建这些变量。

标签: swift sprite-kit initialization subclass skspritenode


【解决方案1】:

您的 createRing() 方法在 Ball 类中,因此您需要先创建 Ball 的实例。

简单方式 - 你可以将实例的创建改为

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

【讨论】:

  • 引用我自己的话,问题是:“其中一个正在进入我已经凌乱的便利初始化程序并在其中添加 myRing = createRing() ......这很有效!这个工作原理和原因,这是循环初始化消耗的最佳/正确/正确方法吗?”
  • 您能否在您的问题中添加 cmets 中的相关内容,以便我们了解您的 pblm 到底是什么。
  • 查看特定问题可能会让您更容易找到该问题的解决方案。这不是那种类型的问题。这是一个关于 Swift 初始化的方式和原因的问题。问题,例如它,只是作为一个框架来讨论和解释。这是一个例子。随意制定自己的示例来解释和讨论如何以类似的方式/情况最好地使用和执行初始化。
【解决方案2】:

我对你把它放在哪里有点困惑

 myRing = createRing()

代码行,但我想知道此设置是否有助于解决您的问题

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

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

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

【讨论】:

  • 查看我在哪里/在做什么的更新。抱歉,不清楚。
  • 我已经更新了我的评论,试图解释您看到的错误发生的原因,希望对您有所帮助。
【解决方案3】:

我立即以可爱的神秘方式迎接:

不能在属性初始化器中使用实例成员“ringSize”,属性初始化器在“self”可用之前运行。

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

static let defaultRingSize: CGFloat = 64

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

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

又死了:

!预期声明

在你的问题中,你并不清楚你是如何触发这个的,但我猜你尝试过这样的事情:

class Circle: SKSpriteNode {

    var ringSize: CGFloat = 96

    var myRing = SKShapeNode()
    myRing = createRing() // “Expected declaration” error on this line

这里的问题是您在类的主体中放置了一个语句,但主体中只允许声明。

其中一个正在进入我已经很乱的便利初始化程序,并在其中添加 myRing = createRing()... 这样就可以了!

这如何以及为什么起作用

您的类的所有实例变量都必须在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)
}

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

这是绕过初始化消耗的最佳/正确/正确方法吗?

嗯,“循环排水”意味着“失败”,所以我猜你是在问这是否是初始化失败的“最佳/正确/正确方式”......我想这不是最好的失败方式,因为你最终并没有真​​正失败。

或者您的意思可能是“我讨厌 Swift 进行初始化的方式,所以我要抛出一些阴影”,在这种情况下,you ain't seen nothin' yet.

但也许您的真正意思是“这是初始化我的实例的最佳/正确/正确的方法吗”,在这种情况下,“最佳”、“正确”和“正确”是相当主观的。

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

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

我可能会这样写你的课:

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

【讨论】:

  • 这太棒了。这是使用戒指尺寸的完整理由...github.com/dissidently/CircleExplodeRing/blob/master/…
  • 公平地说,我不知道我在初始化做什么,只是利用复制工具从这些动画中获得不错的“类似粒子”的性能。我的代码布局可能不言自明......我更喜欢表格和视觉编码而不是实际编码。非常感谢您的精彩回答以及您的时间和考虑
  • 关于“循环排水”,并不一定意味着失败。这是一个奇怪的成语。当用来谈论自我(就是我)时,它试图描述在 Swift 中与初始化的规则和规定(和义务)作斗争并成功,但不知道为什么或如何成功的感觉。在这种情况下,我试图传达我仍在循环初始化的下水道,因为虽然有些东西已经成功地从下水道中掉了下来……对我来说,它已经完成了下水道,而且我一点也不聪明,而且还在继续很快就会再次这样做,因为我不知道它为什么会起作用。
  • 此后不久,出于各种原因,我放弃了使用 Swift 和 SpriteKit。但两者仍然让我感兴趣。我觉得它们的工作量超出了应有的水平,所以我现在使用其他游戏引擎,并在更短的时间内完成更多的工作,并且更加轻松。但 Swift 在我心中仍然占有特殊的位置,我一直抱有(非常天真)希望 Apple 将其数十亿美元用于制作一对出色的游戏引擎 - 让每个人都喜欢使用。
猜你喜欢
  • 1970-01-01
  • 2018-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多