I want to call a method defined inside a SpriteKit class with a SwiftUI button. I am using the @Observable
macro on the class, and @State
for the class instance inside SwiftUI.
Some of the class properties are accessible, but not the method I want to use. Calling it throws 3 compiler errors:
Using @Bindable
instead of @State
doesn't solve the issue.
What am I missing?
SwiftUI code:
import SwiftUI
import SpriteKit
struct ActionReplay: View {
@State var myActionScene: SKScene = ActionScene(size: CGSize(width: 1000, height: 1000))
var body: some View {
ZStack {
SpriteView(scene: myActionScene, debugOptions: [.showsFPS, .showsNodeCount])
.ignoresSafeArea()
VStack {
Spacer()
HStack {
Button(action: {
myActionScene.resetToInitialConditions() // Errors here
}, label: {
Text("Reset")
})
.buttonStyle(.bordered)
}
}
}
}
}
Data and SpriteKit:
struct InitialConditions {
var position: CGPoint = .zero
var rotation: CGFloat = 5
var velocity: CGVector = CGVector(dx: 0, dy: 0)
var angularVelocity: CGFloat = 0
}
@Observable class ActionScene: SKScene {
var initialConditions = InitialConditions()
var baseObject: SKSpriteNode!
var ground: SKSpriteNode!
override init(size: CGSize) {
super.init(size: size)
self.scaleMode = .resizeFill
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMove(to view: SKView) {
backgroundColor = .white
createObjects()
}
func createObjects() {
baseObject = SKSpriteNode(color: .red, size: CGSize(width: 60, height: 60))
baseObject.anchorPoint = CGPoint(x: 0.5, y: 0.5)
baseObject.position = initialConditions.position
baseObject.zRotation = initialConditions.rotation
baseObject.physicsBody = SKPhysicsBody(rectangleOf: baseObject.size)
baseObject.physicsBody?.restitution = 1.1
baseObject.physicsBody?.linearDamping = 0
addChild(baseObject)
ground = SKSpriteNode(color: .darkGray, size: CGSize(width: 370, height: 10))
ground.position = CGPoint(x: .zero, y: -300)
ground.physicsBody = SKPhysicsBody(rectangleOf: ground.size)
ground.physicsBody?.affectedByGravity = false
ground.physicsBody?.isDynamic = false
addChild(ground)
}
func resetToInitialConditions() {
baseObject.physicsBody?.velocity = initialConditions.velocity
baseObject.physicsBody?.angularVelocity = initialConditions.angularVelocity
baseObject.position = initialConditions.position
baseObject.zRotation = initialConditions.rotation
}
// Testing the method I want to call
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
resetToInitialConditions()
}
}
You've defined your @State
as:
@State var myActionScene: SKScene = ActionScene(size: CGSize(width: 1000, height: 1000))
This is telling the compiler that myActionScene
is a SKScene
. But, in reality, you have a subclass of SKScene
, called ActionScene
.
Because you're telling the compiler that it's really just a SKScene
(which, technically it is -- that is the superclass of ActionScene
), it thinks that the only methods/properties available are those defined on SKScene
. If you want access to what is defined on ActionScene
, you must tell the compiler that's what it is:
@State var myActionScene: ActionScene = ActionScene(size: CGSize(width: 1000, height: 1000))
Or, the compiler will even infer the type for you:
@State var myActionScene = ActionScene(size: CGSize(width: 1000, height: 1000))