Reuse same sprite node within multiple scenes in sprite kit using Swift - swift

I create sprite node in my GameScene as the following. I would like to reuse createNodeA1 or nodeA1 in other SKScene. How can I do that?
import SpriteKit
class GameScene: SKScene {
var nodeA1: SKNode!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(size: CGSize) {
super.init(size: size)
// Add sprite node to the scene
nodeA1 = createNodeA1()
addChild(nodeA1)
}
}
// Create dot 1
func createNodeA1() -> SKNode {
let spriteNode = SKNode()
spriteNode.position = CGPointMake(CGRectGetMidX(self.frame)/1.5, CGRectGetMidY(self.frame)/2.0)
let sprite = SKSpriteNode(imageNamed: "dot_1")
sprite.zPosition = 3.0
sprite.name = "A1_Broj"
spriteNode.addChild(sprite)
return spriteNode
}
}

There is a few ways to do this.
You could subclass your other scenes to be subclass of the scene with the loadNode function which gives those scenes access to that function.
I asked a question about this last year
Swift multiple level scenes
Another way that might be a bit easier if you are not comfortable with scene subclassing is to just create a subclass of the node itself.
So you create a class
enum EnemyType {
case Normal
case Special
}
class NodeA1: SKSpriteNode {
init(imageNamed: String, enemyType: EnemyType) {
let texture = SKTexture(imageNamed: imageNamed)
if enemyType == .Normal {
super.init(texture: texture, color: SKColor.clearColor(), size: texture.size())
else {
// other init
}
self.zPosition = 1
self.name = ""
// add physics body, other properties or methods for the node
}
}
Than in your SKScenes you can add the node in the init method like so
nodeA1 = NodeA1(imageNamed: "ImageName", enemyType: .Normal)
nodeA1.position = ....
addChild(nodeA1)
this way ever scene where you add the node will use the subclass and therefore include all the properties, set up etc for that node. Another benefit with subclassing is that you could loop through all your nodes using
self.enumerateChildNodesWithName...
and than call custom methods on all nodes.
If you want to subclass your scenes than you would create your baseScene
class BaseScene: SKScene {
// set up all shared stuff in didMoveToView
// have your node function here
// touches began
// physics word and contact collision
// all other stuff that needs to be shared between all level scenes
}
Than your subsequent level scenes would look something like this
class Level1Scene: BaseScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view) // This lines imports all stuff in BaseScene didMoveToView
// do level 1 specific setUps.
// you can call any function or property from BaseScene, e.g the loadNode function.
}
You than load you level scenes as usual, e.g you transition to level 1 scene and it will automatically use/have access to all the superclass methods and sprites (BaseScene).
So you never call baseScene directly, its gets called automatically.
This applies for other methods in baseScene too, so say you have a Update method in BaseScene.
override func update(currentTime: CFTimeInterval) {.... }
This will work across all your level scenes which are subclasses of BaseScene.
But what happens if you need to add some specific stuff to the update method only relevant in 1 level scene and not all level scenes?
It would be the same process, you create a new update func in the LevelScene and call super.
override func update(currentTime: CFTimeInterval) {
super.update(currentTime) // this calls the baseScene Update method
/// specific stuff for that level only
}
Super simply means the super class of the currentScene, which is BaseScene if the scene is a subclass of it.
Is this helping?

This is additional answer information in terms of subclass of the baseScene. We can create node1thru node10 all in baseScene. Then in Leve1Scene which is subclass of the baseScene, all we have to do is in didMoveToView function state node1.position = CGPointMake(....) for each node that we need in Level1Scene where we would specify node's position.
If we do not need to load all of the 10 nodes in Level1Scene, for example, let's say we don't need to load to the scene node10 we can simply in didMoveToView function just state node10.removeFromParent() and this node will not be loaded to Level1Scene but rest of 9 nodes will.
Note that this example uses only 10 nodes, but you can go with any number of nodes in your baseScene.
This way of subclassing will save you a lot repeatable code in subclasses.

Related

Use multiple classes to control a single SKScene [duplicate]

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)
}
}
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 add a sprite an existing scene

What is the best way to add a sprite node after a scene has been fully loaded? The sequence looks like this:
1) I build the scene, GameScene().
2) Some time later, I download backend data and I use this info to build a SKSpriteNode in a different class, NodeBuilder().
3) I want to add this node to the instance of my scene that I'd already loaded.
What's the best way to achieve step 3)?
In GameScene:
addChild(yourNode)
For getting and keeping a reference to NodeBuilder:
Creation of NodeBuilder in GameScene:
class GameScene : SKScene {
var nodeBuilder = NodeBuilder() // Create an instance of NodeBuilder
func didMoveToView(skView: SKView) {
nodeBuilder.gameScene = self // Add self as the instance of GameScene that nodeBuilder has reference to
}
}
In NodeBuilder:
class NodeBuilder {
var gameScene : GameScene! // This is how you keep your reference
func addNodeToGameScene(node: SKNode) {
self.gameScene.addChild(node)
}
addNodeToGameScene(aNode) // This is how you would call the method to add a node to GameScene from NodeBuilder
}

Subclassing with custom initializer from .sks file

I have a custom class and I create nodes in the .sks file that I want to be instances of that custom class using a custom initializer.
custom class:
Class Enemy: SKSPritenode {
init(name: String, image: String, health: Int) {
// stuff here
}
}
SKScene:
// error at runtime:
var enemy1 = childNodeWithName("Enemy1") as! Enemy
// error:
var enemy1 = childNodeWithName("Enemy1") as! Enemy(name: "enemy1, image: "enemy1", health: 100)
Is there a way to use the custom initializer with the .sks file?
Associating your Custom Class to a Node
If you want to associate a node you create into your SKS file to a custom class, you need to:
Add an empty node into your SKS file
Select the Node
Open the Custom Class inspector in Xcode
Type the name of your class into the Custom Class field
Type the name of your project into the Module field
Using the right initializer
When SpriteKit does load the SKS file and start building the objects to populate the scene it does not call you custom initializer but this one
required init?(coder aDecoder: NSCoder)
so your Enemy class should be defined like this
class Enemy: SKSpriteNode {
let health: Int
required init?(coder aDecoder: NSCoder) {
self.health = 10
let texture = SKTexture(imageNamed: "Spaceship")
super.init(texture: texture, color: .clearColor(), size: texture.size())
}
}
Test
You can now test that you have a real Enemy object into your scene defining this method into your GameScene
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let enemy = (children.filter { $0 is Enemy }).first else { fatalError("No Enemy found") }
print(enemy)
}
Using your custom initializer
Right now there is no way of passing parameter from the SKS file to your custom initializer.
However such a technique was available into CocosBuilder, an old Game Level editor available for Cocos2d so I believe sometime in the future we will be able to pass parameters from the SKS file to our custom classes.
Maybe this will be announced with the next version of SpriteKit in a few days during the WWDC 2016.

is it a good practice to create an SKSpriteNode within a subclassed SKSpriteNode?

This code here subclasses SKSpriteNode and initializes it that accepts SKScene
import SpriteKit
class Spaceship: SKSpriteNode{
var spaceship:SKTexture
var hitpoint = 100
var thescene:SKScene
var lazer5:SKSpriteNode?
var lazer5_pathofdestruction:SKSpriteNode?
init(skScene:SKScene) {
thescene = skScene
self.spaceship = SKTexture(imageNamed:"Spaceship")
super.init(texture: spaceship, color: SKColor.clearColor(), size: spaceship.size())
self.name = "Spaceship"
self.setScale(0.10)
self.position = CGPointMake(CGRectGetMidX(skScene.frame), CGRectGetMidY(skScene.frame))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
.......
This Class (Spaceship) has a method that will fire the dualcanon:
func firedualcanon() {
let canonposition = [10.00 , -10.00]
let fireSound = SKAction.playSoundFileNamed("dualcanon.wav", waitForCompletion: false)
let targeting = SKAction.sequence([
SKAction.runBlock{
for position in canonposition {
let canon = SKSpriteNode(color: SKColor.whiteColor(), size: CGSize(width:4, height: 3))
canon.name = "weapon"
canon.position = CGPointMake(self.position.x - CGFloat(position), self.position.y)
let projectile = SKAction.moveTo(CGPoint(x: self.position.x - CGFloat(position), y: self.thescene.frame.height + 200 ),duration: 1.50)
GlobalReference.setPhysicsBody(canon,collidertype: GlobalVariable.ColliderType.Light)
self.thescene.addChild(canon)
let bulletaction = SKAction.sequence([projectile,SKAction.removeFromParent()])
canon.runAction(bulletaction)
}
}
, SKAction.waitForDuration(0.10)
])
self.thescene.runAction(
SKAction.repeatActionForever(SKAction.group([fireSound,targeting])),
withKey: "fireweapons")
}
as you can see in the initialization I used the SKTexture but now in the method firedualcanon() I created a canon using SKSpriteNode.
Is this a good Swift Programming practice?
When coding sprite-based games, it is quite common practice to have your game objects be subclasses of the sprite class (or more generic nodes), even before Swift and SpriteKit (e.g., Cocos2d/Objective-C).
Some purist might argue that you should decouple the model (data), views (sprites) and controllers (game logic) into separate objects, but in simple games that can lead to having a huge number of classes, each of which does very little.
(In my opinion, it is really about preference and what is convenient for your particular app)
If you still wish to go in that direction, you could have each object's logic/state represented by a non-SpriteKit class (e.g., Swift root class or subclass of NSObject), with each object somehow linked to the sprite that represents it on screen (a reference, unique id, etc.), the details up to you.
Then, on each frame, update the visual state (position, etc.) of each sprite based on the logical (game) state of the model object (e.g., "spaceship") they represent.
Hope it makes sense.
I agree with NicolasMiari. It depends on your particular game but it's usual to have a SKNode class that consist on one, two or more SKSpriteKitNode in order to represent it properly. For example, what if your spaceship can have a little spaceship as a satellite with its particular actions, animations, collisions etc? In cases like that is easier to have it a as separate sprite.

Calling custom class methods after collision detection, which returns a SKPhysicsBody

I figured out how to do collision detection using PhysicsBody and this method works:
// Method call:
projectileDidCollideWithMonster(firstBody.node as SKSpriteNode, monster: secondBody.node as SKSpriteNode)
// Method:
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode!) {
projectile.removeFromParent()
monster.removeFromParent()
}
The method call was placed in a method that serves as the SKPhysicsContact receiver. It gets a pair of SKPhysicsBody objects passed to it, checks that they are the right type, and then calls the other method to have those objects removed.
However, I want change the Monster to have hit points.
I created a separate class called Monster, which has the following code:
import Foundation
import SpriteKit
class Monster : SKSpriteNode {
var hp : Int
override init () {
let color = UIColor()
let texture = SKTexture(imageNamed: "monster")
let size = texture.size()
hp = 5
super.init (texture: texture, color: color, size: size)
self.physicsBody = SKPhysicsBody(rectangleOfSize: size)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func subtract () {
hp--
println(hp)
}
}
and so I want to run this code, customized for Monster:
projectileDidCollideWithMonster(firstBody.node as SKSpriteNode, monster: secondBody.node as Monster)
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:Monster!) {
projectile.removeFromParent()
monster.subtract();
}
In Java, I could just cast it, such as (Monster)secondBody.node. I know that the secondBody is a Monster. The Monster holds reference to it's PhysicsBody, and inherits SKSpriteNode.
I tried this: let theMonster = secondBody.node as Monster but that didn't work, either.
The sprite kit physics engine seems to return PhysicsBody objects, on which I can call built-in methods like removeFromParent(). But I want to call my own subtract() method instead, which is part of Monster!
How can I trigger a custom method on an object that I received via collision detection?
I figured it out!
let theMonster = monster as Monster
projectile.removeFromParent()
theMonster.subtract();
Thanks!!