create SKScene subclasses programmatically, without size info?

落花浮王杯 提交于 2020-06-22 12:05:22

问题


I'm trying to learn how to make a GameManager type class, and making individual classes for each of my GameScenes... probably the wrong thing to do, but for the sake of this question, please accept this as the way to do things.

My GameManager looks like this, having a reference to each of the scenes, that's static:

import SpriteKit

class GM {

    static let scene2 = SecondScene()
    static let scene3 = ThirdScene()
    static let home = SKScene(fileNamed: "GameScene")
}

How do I create a SKScene programmatically, without size info, since they're in a subclass of SKScene and don't have any idea what the view size is, and I don't want them to need worry about this:

I'm doing this, but getting a EXC_BAD_Access at convenience override init()

class SecondScene: SKScene {

    override init(size: CGSize){
        super.init(size: size)
    }

    convenience override init(){
        self.init()
        self.backgroundColor = SKColor.red
        self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    }
}

回答1:


As I mentioned your question is a bit vague but lets do some examples of what a GameManager class can be.

Before I start lets differentiate between calling this

let scene = StartScene(size: ...)

and this

let scene = SKScene(fileNamed: "StartScene")

The 1st method, with size, is when you create your scenes all in code and you are not using the xCode visual level editor.

The 2nd method is when you are using the Xcode level editor, so you would need to create a StartScene.sks file. Its that .sks file that it looks for in fileNamed.

Now for some game manager example, lets first imagine we have 3 SKScenes.

class StartScene: SKScene { 

      override func didMove(to view: SKView) { ... }
 }

class GameScene: SKScene { 

      override func didMove(to view: SKView) { ... }
 }

class GameOverScene: SKScene { 

      override func didMove(to view: SKView) { ... }
 }

Lets say you want to transition from StartScene to GameScene, you would add this code in your StartScene at the correct spot e.g when the play button is pressed. Thats the simplest way to move from one SKScene to the next, directly from the SKScene itself.

 // Code only, no xCode level editor
 let gameScene = GameScene(size: CGSize(...))
 let transition = SKTransition...
 gameScene.scaleMode = .aspectFill
 view?.presentScene(gameScene, transition: transition)

 // With xCode level editor (returns an optional so needs if let
 // This will need the GameScene.sks file with the correct custom class set up in the inspector
 // Returns optional 
 if let gameScene = SKScene(fileNamed: "GameScene") {
      let transition = SKTransition...
      gameScene.scaleMode = .aspectFill
      view?.presentScene(gameScene, transition: transition)
 }

Now for some actual examples of GameManagers, Im sure you know about some of them already.

EXAMPLE 1

Lets say we want a scene loading manager. You approach with static methods will not work because a new instance of SKScene needs be created when you transition to one, otherwise stuff like enemies etc will not reset. Your approach with static methods means you would use the same instance every time and that is no good.

I personally use a protocol extension for this. Create a new .swift file and call it SceneLoaderManager or something and add this code

enum SceneIdentifier: String {
   case start = "StartScene"
   case game = "GameScene"
   case gameOver = "GameOverScene"
}

private let sceneSize = CGSize(width: ..., height: ...)

protocol SceneManager { }
extension SceneManager where Self: SKScene {

     // No xCode level editor
     func loadScene(withIdentifier identifier: SceneIdentifier) {

           let scene: SKScene

           switch identifier {

           case .start:
              scene = StartScene(size: sceneSize)
           case .game:
              scene = GameScene(size: sceneSize)
           case .gameOver:
              scene = GameOverScene(size: sceneSize)
           }

           let transition = SKTransition...\
           scene.scaleMode = .aspectFill
           view?.presentScene(scene, transition: transition)
     }

      // With xCode level editor
     func loadScene(withIdentifier identifier: SceneIdentifier) {

           guard let scene = SKScene(fileNamed: identifier.rawValue) else { return }
           scene.scaleMode = .aspectFill
           let transition = SKTransition...
           view?.presentScene(scene, transition: transition)
     }
}

Now in the 3 scenes conform to the protocol

class StartScene: SKScene, SceneManager { ... }

and call the load method like so, using 1 of the 3 enum cases as the scene identifier.

 loadScene(withIdentifier: .game)

EXAMPLE 2

Lets make a game manager class for game data using the Singleton approach.

class GameData {

    static let shared = GameData()

    private init() { } // Private singleton init

    var highscore = 0

    func updateHighscore(forScore score: Int) {
       guard score > highscore else { return }
       highscore = score
       save()
    }

    func save() {
       // Some code to save the highscore property e.g UserDefaults or by archiving the whole GameData class
    }
}

Now anywhere in your project you can say

 GameData.shared.updateHighscore(forScore: SOMESCORE)

You tend to use Singleton for things where you only need 1 instance of the class. A good usage example for Singleton classes would be things such as helper classes for Game Center, InAppPurchases, GameData etc

EXAMPLE 3

Generic helper for storing some values you might need across all scenes. This uses static method approach similar to what you were trying to do. I like to use this for things such as game settings, to have them in a nice centralised spot.

class GameHelper {

     static let enemySpawnTime: TimeInterval = 5
     static let enemyBossHealth = 5
     static let playerSpeed = ...
}

Use them like so in your scenes

 ... = GameHelper.playerSpeed

EXAMPLE 4

A class to manage SKSpriteNodes e.g enemies

 class Enemy: SKSpriteNode {

     var health = 5

     init(imageNamed: String) {
         let texture = SKTexture(imageNamed: imageNamed)
         super.init(texture: texture, color: SKColor.clear, size: texture.size())
     }

     func reduceHealth(by amount: Int) {
        health -= amount
     }
 }

Than in your scene you can create enemies using this helper class and call the methods and properties on it. This way you can add 10 enemies easily and individually manage their health etc. e.g

 let enemy1 = Enemy(imageNamed: "Enemy1")
 let enemy2 = Enemy(imageNamed: "Enemy2")

 enemy1.reduceHealth(by: 3)
 enemy2.reduceHealth(by: 1)

Its a massive answer but I hope this helps.



来源:https://stackoverflow.com/questions/40515711/create-skscene-subclasses-programmatically-without-size-info

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!