如何让 SKEmitterNode 在 SwiftUI 中工作?

How can I get SKEmitterNode to work in SwiftUI?

我正在尝试更新 UIViewRepresentableSKEmitterNode 的颜色。 hue值是从父View上的state传入的,当父state中的hue值更新时,发射器颜色应该更新。

它最初显示,虽然它在第一次调用 updateUIView 时更新,但它不响应任何后续调用,即使该函数肯定被调用时每次都使用 hue 的新值时间.

有人知道发射器为什么不更新吗?我正处于撕毛阶段...

  import SwiftUI
  import UIKit
  import SpriteKit

  struct EmitterView: UIViewRepresentable {
    private let view = SKView()

    let scene = SKScene(fileNamed: "myScene")!
    var emitter: SKEmitterNode = SKEmitterNode()
    var hue: Double

    func makeUIView(context: UIViewRepresentableContext<EmitterView>) -> SKView {

      // Lets make it manually
      emitter.particleTexture = SKTexture(imageNamed: "spark")
      emitter.particleBirthRate = 80
      emitter.particleLifetime = 2.5
      emitter.particlePositionRange = CGVector(dx: 200, dy: 150)
      emitter.particleScale = 0.2
      emitter.particleScaleSpeed = 0.45
      emitter.particleColor = SKColor.blue
      emitter.particleColorBlendFactor = 1.0

      scene.addChild(emitter)
      view.presentScene(scene)

      return view
    }

    func updateUIView(_ uiView: SKView, context: UIViewRepresentableContext<EmitterView>) {

      let color: SKColor = SKColor(
        hue: CGFloat(hue),
        saturation: 1.0,
        brightness: 1.0,
        alpha: 1.0
      )

      print("Hue is now", hue)

      emitter.resetSimulation()
      emitter.particleColor = color
    }
  }

  struct EmitterView_Previews: PreviewProvider {
    static var previews: some View {
      EmitterView(hue: 0.5)
    }
  }

现在 11 出来了,我终于可以玩 Swift UI。

遇到的问题是您的 UIViewRepresentable 是一个结构。

结构的工作方式是写时复制。

我假设在您的场景委托中,您在设置色调时没有重新分配根控制器中的结构。

您需要将代码移至 class 以获得最佳结果(不是协调器,它们用于应用程序流程。研究协调器模式以获取更多信息。)

最好的 class 就是你的 SKScene。

我建议制作一个 GameScene class 并更改您的 sks 文件以指向它。

然后您可以为这个游戏场景创建一个函数 class,这样您就可以在不改变结构的情况下改变发射器。

import SwiftUI
import UIKit
import SpriteKit

struct EmitterView: UIViewRepresentable {
    private let view = SKView()
    let scene = GameScene(fileNamed: "myScene")!
    var hue : Double{
        get{
            return scene.hue
        }
        set{
            scene.hue = newValue
        }
    }
    func makeUIView(context: UIViewRepresentableContext<EmitterView>) -> SKView {
        view.presentScene(scene)
        return view
    }

    func updateUIView(_ uiView: SKView, context: UIViewRepresentableContext<EmitterView>) {


    }
}

struct EmitterView_Previews: PreviewProvider {
    static var previews: some View{
          EmitterView()
    }
}


class GameScene : SKScene{
    var hue : Double = 0{
        didSet{
            let color: SKColor = SKColor(
                hue: CGFloat(hue),
                saturation: 1.0,
                brightness: 1.0,
                alpha: 1.0
            )

            print("Hue is now", hue)

            emitter.resetSimulation()
            emitter.particleColor = color
        }
    }

    var emitter: SKEmitterNode = SKEmitterNode()
    override func didMove(to view: SKView) {
        emitter.particleTexture = SKTexture(imageNamed: "spark")
        emitter.particleBirthRate = 80
        emitter.particleLifetime = 2.5
        emitter.particlePositionRange = CGVector(dx: 200, dy: 150)
        emitter.particleScale = 0.2
        emitter.particleScaleSpeed = 0.45
        emitter.particleColor = SKColor.blue
        emitter.particleColorBlendFactor = 1.0
        addChild(emitter)
    }

}

之所以可行,是因为在复制时,view 将复制指针,而 hue 将复制值。

如果您需要在更新UI视图中执行操作,您可以随时将 uiView.scene 投射到 GameScene 并以这种方式访问​​它。

为了使 UIViewRepresentable 结构可变,请将 @Binding 前缀添加到 hue 变量。

@Binding var hue: Double

然后您可以通过以下方式从父视图更改粒子的颜色:

struct ContentView: View {
    @State var hue: Double = 0.5
    var body: some View {
        VStack {
            EmitterView(hue: self.$hue)
            Slider(value: $hue)
        }
    }
}