8

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)
    }
}
Confused
  • 6,048
  • 6
  • 34
  • 75
  • 1
    Your init method calls itself recursively, that causes a *stack overflow* :) – Martin R Nov 09 '16 at 20:45
  • Compare http://stackoverflow.com/questions/30798642/overriding-init-in-a-customuiview-crashes-app-exc-bad-access, which seems to be about the same problem in a UIView subclass. – Martin R Nov 09 '16 at 20:51
  • I wish it were that easy, for me to understand. If I change that self.init() to super.init() I get error: "Convenience initializer for 'SecondScene' must delegate with self.init rather than chaining to a superclass... – Confused Nov 09 '16 at 20:54
  • but, @MartinR, you're spot on about it being a stack overflow, I think. Might be the first time I've caused one. That I know of ;) – Confused Nov 09 '16 at 20:55
  • Going through that question's "answer" only made me more confused. – Confused Nov 09 '16 at 20:56
  • I have not much experience with SpriteKit, but I would assume that you just put the customization in the `override init(size: CGSize)` method and don't override `init()` – Martin R Nov 09 '16 at 20:57
  • 1
    Can you reword your question a bit, as your nickname suggests i am slightly confused. You wrote "I am trying to make GameManager type class, and making individual classes for each of my GameScenes." But than you simply show a regular SKScene class called Second Scene. I dont understand what you are trying to achieve? I also dont think its possible to create a SKScene without a size. – crashoverride777 Nov 09 '16 at 20:57
  • This is one of the scenes, not the game manager, that's a separate thing, and it's trying to instantiate these game scenes.... without knowledge of the view. So I'm probably doing a lot of things wrong, @crashoverride777 – Confused Nov 09 '16 at 21:00
  • I will help you no problem but brute forcing is not always the way to go. Can you please rewrite your question exactly describing what you want to do. What is the game manager class supposed to do? All I know so far is GameManager and I see some code for a SKScene. – crashoverride777 Nov 09 '16 at 21:02
  • Which all compounds to a situation where I have this dream of a game manager that may just be a pure nightmare, and/or completely delusional. – Confused Nov 09 '16 at 21:02
  • attempted to add info about GameManager. I might be completely wrongheaded in this approach. @crashoverride777 – Confused Nov 09 '16 at 21:06
  • So for now as far as I understand game manager is just supposed to manage loading some SKScenes? Its very broad question to answer unless I know some examples. Because a GameManager class for managing SpriteNodes will be different to one managing GameData and so on. Its not a set answer to give. As you also read in my other answer you commented on you should not set the scene size to the size of the view, you should give it a fixed CGSize. That already solves 1 of your problems. – crashoverride777 Nov 09 '16 at 21:11
  • 1
    I think your example is maybe not ideal because a scene loader manager is not really needed since you can transition from 1SKScene to another SKScene with like 2-3 lines of code. I will post an answer of how I handle scene loading and maybe give you some example of what a GameManager can be. – crashoverride777 Nov 09 '16 at 21:17
  • @crashoverride777 this is not a "real world" example, I'm trying to understand how to make a GameManager with this example, just setting up a test project, and working away at it from the situation in the question. – Confused Nov 09 '16 at 21:21
  • This example is maybe not ideal. I will post an answer for you with various examples of what I understand as a GameManager class. – crashoverride777 Nov 09 '16 at 21:23
  • About to post an answer, it will be quite massive but I hope it helps. You might know some of this stuff already. Please feel free to ask for another example if you got one in mind. – crashoverride777 Nov 09 '16 at 22:02
  • This is amazing. I'm going to need digest this, and make some more tests. Perhaps my biggest problem is that I'm trying to make the buttons do the jumping between scenes, and that's the reason for the GM, in my "demo" attempt. It's a contrived attempt to treat touchesBegan on a button as the thing that makes the call to change scenes. – Confused Nov 09 '16 at 22:07
  • And, @crashoverride777 I've since discovered the joys of this problem. That I'm creating references to these buttons, and getting a conflict cause they get added to another scene... etc. I think my entire idea was faulty. But it's so close to working. – Confused Nov 09 '16 at 22:08
  • Yeah but the problem is in your game you might transition between say GameScene and GameOverScene a bunch of times. Your static property means you would use the same instance of the scene and not a fresh one as you should. You are kind of trying to use the Singleton approach for SKScenes and that will not work – crashoverride777 Nov 09 '16 at 22:15
  • You need to create a new instance of a SKScene every time you transition to one and thus cannot use static or global properties to store them. – crashoverride777 Nov 09 '16 at 22:18

1 Answers1

12

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.

crashoverride777
  • 10,581
  • 2
  • 32
  • 56
  • You are very welcome. As I mentioned in the other comment the main thing is that you need to create a new instance of a SKScene every time you transition to one and therefore you cannot use static or global properties to store references to them. Thats why your approach will not work. – crashoverride777 Nov 09 '16 at 22:23
  • I was really hoping to create the global static "instances" of the scenes to prevent any need to load, so I could do super fast transitions. DOH! – Confused Nov 09 '16 at 22:24
  • Yeah but a Scene is meant to load in a fresh state. Otherwise imagine you die in GameScene and move to GameOverScene. When you transition back to GameScene all your stuff like enemies etc would not be reset. Also you can use super fast transitions, as in instantly basically. Just set the transition time to 0 or use no transition at all, just say view?.presentScene(scene) – crashoverride777 Nov 09 '16 at 22:27
  • Each of the scenes (in the final game concept) have to load a lot of textures for their first presentation, so I was trying to "cache" in this manner, in a test project. Lots of moronic ideas in my head... years of fighting with 3D renderers, animation apps and design software has created a compulsive cheater. – Confused Nov 09 '16 at 22:29
  • But you're right. I had the concept of how SK works wrong. I'd forgotten the purpose of the `didMove(to view: ... ` was/is to do a flush and start-over of a level. – Confused Nov 09 '16 at 22:30
  • btw, your Protocol approach might be the first time I use Protocols and, with a bit of luck, will become the moment I figure them out. That Crusty video has done me permanent damage. – Confused Nov 09 '16 at 22:34
  • SpriteKit in general handles texture loading for you, so once you loaded a SKTexture it will stay in memory and should be accessed faster the 2nd time around. You could make a helper for that if you want, I do for preloading loads of textures on app launch. For things like textures your GameManager approach with static properties is actually a perfect example. Create a class called Textures and add static properties such as arrays of all the textures you need. – crashoverride777 Nov 09 '16 at 22:35
  • In regards to protocols the default usage is a bit more tricky, however protocol extensions are quite cool and fairly easy. Essentially I create the SceneManger protocol and create an extension of it that confirms to SKScene. Than any SKScene that implements the protocol has access to that scene loading method. And the nice bit is that in that method of the protocol extension you can do anything you can in a SKScene, its as if you are in a SKScene. – crashoverride777 Nov 09 '16 at 22:38
  • It will be a good 24 hours before I have tried all of this, and can start articulating the next round of problems I have in conceptual comprehension and understanding. Again, THANK YOU!!! – Confused Nov 09 '16 at 22:42
  • You are very welcome, I am having a look at your other question now. – crashoverride777 Nov 09 '16 at 22:43
  • First SUPER STUPID question: What am I supposed to put in here: `loadScene(withIdentifier identifier: SceneIdentifier) {` as the identifiers etc? – Confused Nov 09 '16 at 22:50
  • I'm getting an "expected declaration" error, and assume I'm supposed to be putting in some indicators of my scenes here. But I don't know what identifiers means, at all. Sorry. very ignorant. – Confused Nov 09 '16 at 22:51
  • Check my answer, you need to create the enum with your SceneIdentifiers. Than you can call the method like so loadScene(withIdentifier: .game). Please ready my answer carefully and look through the code first. – crashoverride777 Nov 09 '16 at 22:53
  • In my example when you call loadScene(withIdentifier... you can call 1 of the 3 cases in the enum SceneIdentifier. In my sample I am loading gameScene by saying ....withIdentifier: .game). Have a look at enumerations if that confuses you, apples has some very good documentation. Enums are very important to fully understand. – crashoverride777 Nov 09 '16 at 22:58
  • should it have a "func" before it? – Confused Nov 09 '16 at 22:59
  • Are you trying to call the method? Or are you creating the protocol? I described this in my answer with sample code. – crashoverride777 Nov 09 '16 at 23:00
  • I'm trying to create the protocol. I've dumped the code into a new Swift file, as per your instructions, and added screen size and transition effect type etc. But stuck at this line in the protocol. I think it should be a func definition, but it's just like a call. – Confused Nov 09 '16 at 23:03
  • Yeah, duh I am so sorry. Of course its a func, so add func before both of them. Sorry about that, its such a massive post I missed that. – crashoverride777 Nov 09 '16 at 23:04
  • Yeah xCode has been hit and miss for me as well. I get crashes when using the visual level editor and sometimes it just goes nuts in general and doesnt code auto complete. – crashoverride777 Nov 09 '16 at 23:09
  • yeah, the autocomplete failures REALLY kill me. Bring me to a standstill. The crashes are without rhyme, rhythm or reason. And I refuse to use the Scene Editor because it's buggier than a 3rd world country backpacker's mattress. – Confused Nov 09 '16 at 23:14
  • On the plus side, I might have just discovered and subsequently developed an addiction to static functions. Are these frowned upon in the OOP world? – Confused Nov 09 '16 at 23:16
  • Lets go to the chat, we are killing the comment section. – crashoverride777 Nov 09 '16 at 23:18
  • I've made an `SKLabelNode` subclass called `MenuButton`, that conforms to this `SceneManager` protocol, and in the `touchesBegan` of MenuButton instances am attempting to call `loadScene(...)`, but getting this error: "MenuButton is not a subtype of SKScene". Why would it need to be, and why is this error coming up? I'm (perhaps wrongly) imagining the fact that my `MenuButton` conforms to SceneManager is enough for it to call loadScene. – Confused Nov 10 '16 at 08:45
  • Sorry, figured the above out: Needed to add this: `where Self: MenuButton` as we discussed in comments last night... DOH!, then added `scene` to this, so that it could find where to use the view to make the transition: `scene.view?.presentScene(scene, transition: transition)` – Confused Nov 10 '16 at 08:53
  • One more edit: Actually needed to add self to get it to work, like so: `self.scene?.view?.presentScene(scene, transition: transition)` – Confused Nov 10 '16 at 09:28
  • Wow, this answer... solid gold. Why can't we get all the SpriteKit game-makers together in the same room? :) Any more gems like this? I'm making my first game and having many problems, I think because my underlying "architecture" of structuring & organizing the scenes/views/controllers is very bad. I'm trying to come up with a good structure so that I can build things out reliably. This answer is extremely helpful. – peacetype Nov 14 '17 at 02:32
  • Hey thanks for your kind words. I am glad the answer helped you. Please feel free to ask any question if you have troubles. Also as a small tip for game architecture, we should be doing pretty much everything with SKnodes, SKLabelNodes etc UI wise within SKScenes. Do not use ViewControllers for this, we usually only have 1 View Controller in sprite kit games which only really handles loading the 1st scene (like default game template). That’s a good tip to avoid common pitfalls of making games. Happy coding – crashoverride777 Nov 14 '17 at 07:51
  • Haha good tip! Coincidentally, I just built some menus in UIKit because I wanted to take advantage of the visual layout (the SKS file visual editor seems to lack the equivalent of UIButton) but now I'm having problems integrating those menus with my game scenes. I was also wondering if you have a procedure for dismissing your scenes when you transition to another scene, or if you trust the system to handle it for you. My scene appears to persist after I transition to the UIKit menu, but that may be one of the pitfalls you mentioned. @crashoverride777 – peacetype Nov 14 '17 at 09:25
  • Hey, yes the problem with UIKit in SpriteKit is that UIKit elements get added to your GameViewController. That game View controller also handles all your scenes presentation. Therefore it will show in each scene and you would have to manually hide it etc when you change scene. I strongly discourage that approach. Make your menu with SKNodes, SKSpriteNodes etc and just add it to the scene. To check if your scene is deallocating add a deinit method to each scene with a print statement and see if it gets called. If not that means you have a memory leak somewhere. Best regards – crashoverride777 Nov 14 '17 at 13:05