3
votes

I'm making an a game in XCode 7 using Swift 2. I have a variable that I want to pass from the start screen (which is an UIViewController) to the game scene (which is an SKScene). I want the player to select a character in a UIView and play with it in the SKScene. I also want the score that's in the SKScene to show in the game-over screen that's an UIView. I've seen tutorials for passing data between two UIViewControllers and between two SKScenes, but none of them work for this case.

How can I pass a variable from an UIViewController to a SKScene (and vice versa)?

2
Should be something along the lines of setting a variable of the game scene from the view controller (look in the view controller's code for where the game scene is created). For setting a variable in the view controller from the game scene, pass in a reference to the view controller to the game scene, and then set variables using the view controller. - Gliderman
Isn't your SKView that presents your SKScene controlled by the View Controller? It's just like using subViews... Gliderman has the right idea - MaxKargin
@MaxKargin No, what I actually want to do is select a character in an UIView and show that character in the actual game controlled by the SKScene file. - Gabe12
@Gabe12 Then do what I was saying (I don't have Swift 2 handy to give proper syntax, which is why this is written as a comment). Make a method or something in the SKScene that takes a string and uses that as the filename for creating a SKSpriteNode (or whatever you are using). When you've selected the character, call the method with the image file name, and start running your game. You may need to move some code from didMoveToView, but you probably won't have to too much. - Gliderman

2 Answers

1
votes

If you haven't already found the answer, I did find a way to do this. I was doing something very similar.

In my case I have a UIView that gets called as a pause menu from my scene. I have made the class and variable names a little more ambiguous so they can apply to your situation as well.

class MyView: UIView {
    var scene: MyScene!
    var button: UIButton!
    init(frame: CGRect, scene: MyScene){
        super.init(frame: frame)
        self.scene = scene
        //initialize the button
        button.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "buttonTapped"))
    }

    func buttonTapped(){
        self.removeFromSuperview() // this removes my menu from the scene
        scene.doTheThing() // this calls the method on my scene
}

Here are the relevant parts of my scene.

class MyScene: SKScene {

    func doTheThing(){
         //this is a function on the scene. You can pass any variable you want through the function. 
    }
}

In your situation, it sounds like the first screen is a UIView and the second screen is the SKScene.

You may want to make your SKScene first, pause it, and then add the UIView in front of the Scene. Once the character is selected, you can remove the UIView and add your character into the scene.

I hope that this answers your question. If it doesn't let me know.

1
votes

I ran over this same problem yesterday.

Swift 5.2, xcode 12 and targeting iOS 14. Searched high and low. Eventually found the userData attribute of the SKScene.

Note: The app is based on SwiftUI and the SKScene is inside a UIViewRepresentable:

struct SpriteKitContainer: UIViewRepresentable {
    typealias UIViewType = SKView
    
    var skScene: SKScene!
    @Binding var timerData :  ModelProgressTimer

Not that I am passing a binding into the View - this contains a number of data items I wanted to make available to the scene.

I placed code in two places to populate skScene.userData:

func makeUIView(context: Context) -> SKView {
    self.skScene.scaleMode = .resizeFill
    self.skScene.backgroundColor = .green
    debugPrint("setting user data")

    self.skScene.userData = [:]
    self.skScene.userData!["color"] = UIColor(timerData.flatColorTwo!)
    self.skScene.userData!["running"] = timerData.isRunning
    self.skScene.userData!["percent"] = timerData.percentComplete

    let view = SKView(frame: .zero)
    view.preferredFramesPerSecond = 60
   // view.showsFPS = true
   // view.showsNodeCount = true
    view.backgroundColor = .clear
    view.allowsTransparency = true
    
    return view
}

func updateUIView(_ view: SKView, context: Context) {
    self.skScene.userData = [:]
    
    self.skScene.userData!["running"] = timerData.isRunning
    self.skScene.userData!["color"] = UIColor(timerData.flatColorTwo!)
    self.skScene.userData!["percent"] = timerData.percentComplete
    view.presentScene(context.coordinator.scene)
}

Then, inside the skScene object, I am able to retrieve the userData values:

   var item: SKShapeNode! = nil

   override func sceneDidLoad() {
       let scaleUp = SKAction.scale(by: 2.0, duration: 1.0)
       let scaleDown = SKAction.scale(by: 0.5, duration: 1.0)
        
       scaleUp.timingMode = .easeInEaseOut
       scaleDown.timingMode = .easeInEaseOut
       let sequence = SKAction.sequence([scaleUp,scaleDown])

       actionRepeat = SKAction.repeatForever(sequence)

       item = SKShapeNode(circleOfRadius: radius)
       addChild(item)
    }

override func didChangeSize(_ oldSize: CGSize) {

   item.fillColor = self.userData!["color"] as! UIColor
   item.strokeColor = .clear
                
   let meRunning = self.userData!["running"] as! Bool
                
   if meRunning {
         item.run(actionRepeat)        
   } else {
        item.removeAllActions()  
   }
}

This draws a circle that "pulses" if the "running" boolean is set in the userdata (a Bool value).