NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

How to resize SpriteKit SKScene when using with SwiftUI

Forums > SwiftUI

@Mary  

Hi,

I'm trying to use SpriteKit with SwiftUI. But I'm having trouble resizing the GameScene when I use it in Swiftui.

Here is my current coding.

First, the coding for GameScene:

import SpriteKit
import GameplayKit

@objcMembers
class GameScene: SKScene {

  let player = SKSpriteNode(imageNamed: "player-motorbike")

  var touchingPlayer = false

    override func didMove(to view: SKView) {

      view.allowsTransparency = true
      self.backgroundColor = .clear

      if let particles = SKEmitterNode(fileNamed: "Mud") {
        let farRightPt = frame.maxX // start the emitter at the far right x-point of the view
        particles.advanceSimulationTime(10)
        particles.position.x = farRightPt
        addChild(particles)
      }

      let nearLeftPt = frame.minX * 3 / 4 // start the player at a quarter of the way to the far left x-point of the view

      player.position.x = nearLeftPt
      player.zPosition = 1

      addChild(player)

    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // this method is called when the user touches the screen

      guard let touch = touches.first else { return }
      let location = touch.location(in: self)
      let tappedNodes = nodes(at: location)

      if tappedNodes.contains(player) {
        touchingPlayer = true
      }
    }

  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard touchingPlayer else { return }
    guard let touch = touches.first else { return }

    let location = touch.location(in: self)
    player.position = location

  }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        // this method is called when the user stops touching the screen

      touchingPlayer = false

    }

    override func update(_ currentTime: TimeInterval) {
        // this method is called before each frame is rendered
    }
}

And now my coding for adding that GameScene to my SwiftUI programming:

import SwiftUI
import SpriteKit
import GameplayKit

struct ContentView2: View {

  var scene: SKScene {

    let scene = GameScene()

    let width = UIScreen.main.bounds.width
    let height = UIScreen.main.bounds.height
    scene.size = CGSize(width: width, height: height)
    scene.scaleMode = .fill

    return scene
  }

  var body: some View {
    ZStack {
      Image("road")
        .resizable()
        .aspectRatio(contentMode: .fill)
        .ignoresSafeArea()
      SpriteView(scene: scene, options: [.allowsTransparency])
        .ignoresSafeArea()

    }
  }

}

struct ContentView2_Previews: PreviewProvider {
  static var previews: some View {
    ContentView2()
  }
}

The coding above pushes the GameScene down so that it only occupies about a third of the screen.

I've tried other ways to resize the GameScene. The most luck I've had is with setting:

scene.size = CGSize(width: 300, height: 400)

That allows the GameScene to fill up the entire screen, but the objects show as distorted.

I think I should probably use GeometryReader here, but since the scene isn't some View, that doesn't work either.

Does anyone know how to resize an SKScene so that it can be used in SwiftUI?

Thank you.

2      

I have the same issue - have you found a fix by any chance?

Max

1      

Not sure if this is still a problem for you, but in case anyone is interested the solution I found was: Wrap the view in a GeometryReader - you'll most likely want to have it ignore the safe areas. Actually re-create a scene (rather than re-use) if the GeometryReader provides different values

(This is from my code, but not too different from what you posted) So given my custom GameScene, the code look something like

struct GameContentView: View {

    func sizedScene(size: CGSize) -> SKScene {
        let scene = GameScene(size: size)
        scene.scaleMode = .fill
        scene.setup()
        return scene
    }

    var body: some View {
        GeometryReader { geometry in
            VStack(alignment: .leading) {
                SpriteView(scene: self.sizedScene(size: CGSize(width: geometry.size.width, height: geometry.size.height/2)))
                    .frame(width: geometry.size.width, height: geometry.size.height/2)
               // etc
            }
       }.edgesIgnoringSafeArea(.all)
    }

Here I'm using all the horizontal space and half of the vertical space for my SpriteView. If I rotate, a whole new scene is created, but it will have the new dimensions.

1      

As mentioned in Apple docs you should set scene scaleMode to resizeFill. You may handle size changing overriding the didChangeSize(_ oldSize: CGSize) function in your scene. I provide a scene using ViewModel as @StateObject.

struct GameContentView: View {

    var scene: GameScene {
        let scene = GameScene()
        scene.scaleMode = .resizeFill
        return scene
    }

    var body: some View {
        GeometryReader { geometry in
              SpriteView(scene: scene)
                  .frame(width: geometry.size.width, height: geometry.size.height)
       }.edgesIgnoringSafeArea(.all)
    }
}

2      

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.