Create Skscene Subclasses Programmatically, Without Size Info

create SKScene subclasses programmatically, without size info?

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.

How to subclass SKScene

This is happening as you're trying to access properties before the class has initialised.

If you move that to after the super.init line, it'll work e.g.:

...
super.init(size: fixedSize)
self.name = name
self.buttons = buttons

edit Just thought, there is an exception to this though and I've just confirmed this. Playground doesn't like it, but an actual app does:

class someClass: NSObject {

    let a = 2
var b = 3

override init(){
let c = self.a
let d = self.b
super.init()
print("c: \(c), d:\(d)")
}

func foo(){
print("bar \(a)")
}

}

Load Spritekit scene from a subclass

It's not necessary as in .sks there is a custom class. You just put "Spielfeld" inside.

Then in viewController:

if let sceneNode = SKScene.init(fileNamed: "Spielfeld(Whatever your name is here)") { 
(sceneNode as! Spielfeld).property = "anyValueToUse"

if let view = self.view as! SKView? {
view.presentScene(sceneNode) //present the scene.
}

If you decided to load from a file, there is no reason to init again. You may add init functions by overriding the following:

   override func sceneDidLoad() {  
} //called after .sks is loaded.

override func didMove(to view: SKView) {
//change the size here.
} // called after presentation.

Animation In SpriteKit using skscene editor

you can access the Media Library using CMD + Shift + M (or View > Libraries > Show Media library). Just open it and drag your textures as usual.

How to create another screen on my app

You can create new scenes by subclassing SKScene or any of its subclasses you create. You can switch between scenes by calling the SKView method presetScene(scene:SKScene). This is how I implemented it in my last game:

//handles all scene transitions
protocol SceneTransitionDelegate {
//this passes the scene as a class (not object)
func transitionToScene(sceneClass:Scene.Type)
}

//make sure all you scenes are a subclass of this (so they inherit sceneDelegate):
class Scene: SKScene { //Scene inherits from SKScene
var sceneDelegate:SceneTransitionDelegate!
required override init(size: CGSize) {
super.init(size: size)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

class GameScene:Scene {
//call this method within the scene to return to the main menu
func gameOver() {
sceneDelegate.transitionToScene(MainMenuScene.self)
}
}

class MainMenuScene: Scene {
//call this method to transition to a game scene
func newGame() {
sceneDelegate.transitionToScene(GameScene.self)
}
}

//the VC that presents your scenes
class GameViewController: UIViewController, SceneTransitionDelegate {

var skView:SKView { return view as! SKView }

override func viewDidLoad() {
super.viewDidLoad()

//load first scene (e.g. MainMenu)
transitionToScene(MainMenuScene.self)
}

//SceneTransitionDelegate method
func transitionToScene(sceneClass:Scene.Type) {
let scene = sceneClass(size: skView.bounds.size)
scene.scaleMode = .AspectFill
scene.sceneDelegate = self
skView.presentScene(scene)
}
}

You can improve this by adding a method for custom transitions to the SceneTransitionDelegate.

protocol SceneTransitionDelegate {
func transitionToScene(sceneClass:Scene.Type)
func transitionToScene(sceneClass:Scene.Type, transitionAnimation:SKTransition)
}

and add the new method to the view controller like this:

 func transitionToScene(sceneClass: Scene.Type, transitionAnimation: SKTransition) {
let scene = sceneClass(size: skView.bounds.size)
scene.scaleMode = .AspectFill
scene.sceneDelegate = self
skView.presentScene(scene, transition: transitionAnimation)
}

and call it from within scenes like this:

 func gameOverAnimated() {
let transition = SKTransition.crossFadeWithDuration(0.5)
sceneDelegate.transitionToScene(MainMenuScene.self, transitionAnimation: transition)
}

I made a quick demo xcode project here.

How do I go from SKScene to UIViewController by code?

You cannot present a viewController from within a SKScene as it is actually only being rendered on a SKView. You need a way to send a message to the SKView's viewController, which in turn will present the viewController. For this, you can use delegation or NSNotificationCenter.

Delegation

Add the following protocol definition to your SKScene's .h file:

@protocol sceneDelegate <NSObject>
-(void)showDifferentView;
@end

And declare a delegate property in the interface:

@property (weak, nonatomic) id <sceneDelegate> delegate;

Then, at the point where you want to present the share screen, use this line:

[self.delegate showDifferentView];

Now, in your viewController's .h file, implement the protocol:

@interface ViewController : UIViewController <sceneDelegate>

And, in your .m file, add the following line before you present the scene:

scene.delegate = self;

Then add the following method there:

-(void)showDifferentView
{
[self performSegueWithIdentifier:@"whateverIdentifier"];
}

NSNotificationCenter

Keep the -showDifferentView method as described in the previous alternative.

Add the viewController as a listener to the notification in it's -viewDidLoad method:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showDifferentView) name:@"showDifferenView" object:nil];

Then, in the scene at the point where you want to show this viewController, use this line:

[[NSNotificationCenter defaultCenter] postNotificationName:@"showDifferentView" object:nil];

Creating a custom initalizer for SKScene that overrides convenience init?(fileNamed:)

Couldn't it be as simple as this?

if let gameScene = GameScene(fileNamed: "GameScene") {

self.gameScene = gameScene
self.gameScene.stage = 1
self.gameScene.setupBasedOnStage()
self.gameScene.scaleMode = .aspectFill
self.gameScene.gameSceneDelegate = self.menuSceneDelegate as! GameSceneDelegate!
self.view?.presentScene(self.gameScene, transition: SKTransition.reveal(with: .down, duration: 1.0))
}

You are able to set the stage property before revealing the page, and if you needed to you can call a setup function to load info/graphics based on the stage.

I know it's not as elegant as what you are trying to do, but maybe sometimes the easiest answer is the best?



Related Topics



Leave a reply



Submit