How do I create a node using inheritance? - swift

I would like to create a node of the subclass Unit from GameScene.swift. The code can be executed, but you can't see a node.
I use the following code:
GameScene.swift
func setFirstUnit() {
let myUnit = Unit(pHealthpoints: 10, pDamage: 5, pMovement: 1)
myUnit.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
self.addChild(myUnit)
}
Unit.swift
class Unit: SKNode{
var healthPoints: Int
var damage: Int
var movement: Int
var texture: SKTexture
let knightTexture = "KnightBlueV2"
init(pHealthpoints: Int, pDamage: Int, pMovement: Int) {
healthPoints = pHealthpoints
damage = pDamage
movement = pMovement
texture = SKTexture(imageNamed: knightTexture)
let unit = SKSpriteNode(texture: texture)
unit.zPosition = 4
unit.setScale(1)
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I would be very grateful for any answer.

You are inheriting from SKNode, which has no properties to display anything on the screen. That's why nothing shows up on the screen. If you want to see something on the screen, you need to inherit from SKSpriteNode or another node class with visible properties you can see on the screen.
There's a better solution for you than inheriting from one of the SpriteKit node classes. Instead of using inheritance, use composition. Give the Unit class a property of type SKSpriteNode and use that property to load textures, move the unit, and display the unit on the screen.
class Unit {
sprite: SKSpriteNode
// Rest of class here.
}

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.

Why is init(coder:) being called when I provide an init() function

I am using SpriteKit and I am loading a SceneKit file that contains a number of sprites with custom classes. The scene never actually loads though because it reaches the first custom class and throws the fatalerror from the required init?(coder:) initializer. The custom class implements an initializer though and I am having trouble pinning down why it is choosing that initializer over the one I provided.
Custom Class:
class Bat: SKSpriteNode, GameSprite {
var initialSize: CGSize = CGSize(width: 44, height: 24)
var textureAtlas: SKTextureAtlas = SKTextureAtlas(named: "Enemies")
var flyAnimation = SKAction()
init() {
super.init(texture: nil, color: .clear, size: initialSize)
self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
self.physicsBody?.affectedByGravity = false
createAnimations()
self.run(flyAnimation)
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func createAnimations() {
let flyFrames: [SKTexture] = [textureAtlas.textureNamed("bat"),
textureAtlas.textureNamed("bat-fly")]
let flyAction = SKAction.animate(with: flyFrames, timePerFrame: 0.12)
flyAnimation = SKAction.repeatForever(flyAction)
}
func onTap() {}
}
And here is the code attempting to load the scene and then loop through the children and initialize them:
Encounter Manager:
class EncounterManager {
// Store encounter file names
let encounterNames: [String] = [
"EncounterA"
]
// Each encounter is a node, store an array
var encounters: [SKNode] = []
init() {
// Loop through each encounter scene and create a node for the encounter
for encounterFileName in encounterNames {
let encounterNode = SKNode()
// Load the scene file into a SKScene instance and loop through the children
if let encounterScene = SKScene(fileNamed: encounterFileName) {
for child in encounterScene.children {
// Create a copy of the scene's child node to add to our encounter node
// Copy the position, name, and then add to the encounter
let copyOfNode = type(of: child).init()
copyOfNode.position = child.position
copyOfNode.name = child.name
encounterNode.addChild(copyOfNode)
}
}
// Add the populated encounter node to the array
encounters.append(encounterNode)
}
}
// This function will be called from the GameScene to add all the encounter nodes to the world node
func addEncountersToScene(gameScene: SKNode) {
var encounterPosY = 1000
for encounterNode in encounters {
// Spawn the encounters behind the action, with increasing height so they do not collide
encounterNode.position = CGPoint(x: -2000, y: encounterPosY)
gameScene.addChild(encounterNode)
// Double Y pos for next encounter
encounterPosY *= 2
}
}
}
What I have noticed using breakpoints though is that it never gets past loading the scene. It fails on the line if let encounterScene = SKScene(fileNamed: encounterFileName) and the error is the fatal error in the initializer from the Bat class.
Any help understanding why it picks one initializer over the other would be greatly appreciated!
You are doing:
if let encounterScene = SKScene(fileNamed: encounterFileName)
Which calls SKScene's init(fileNamed:) which loads a file and decodes it with SKScene's coder init. That init loads the file and decodes each element in it with the node's coder init.
If you want to load from a file, you need to implement the coder init.

Is it possible to add a SKSpriteNode to a scene from another class?

Is it possible to add a SKSpriteNode to a scene from another class?
*These are examples
So I have a SKScene with a custom class ..
class MainMenu: SKScene {
code...
}
And another SKScene with a custom class ..
class GameOver: SKScene {
code...
}
I have another class called PopupMenu ...
class PopupMenu: SKScene {
func addSprite() {
theSprite = SKSpriteNode.init(imageNamed: "theImage")
theSprite.position = CGPointMake(self.frame.width/2, self.frame.height/2)
theSprite.zPosition = 2
self.addChild(theSprite)
}
So what I am trying to achieve is to be able to click the show menu button on both the MainMenu Scene and the GameOver Scene and be able to call the function from PopupMenu to add that node to the respective scene.
I can achieve this by writing the code for the popup in each Scene Class but i feel like this is not the best way to do it in terms of reusability.
Is there a better way to do this?
Look into SKReferenceNodes, you can create your popup menu via the SpriteKit Scene Builder, then save this as an sks file. When it comes time to use it, you just pull up the file of the popup menu, and add it to the scene.
Source: https://developer.apple.com/reference/spritekit/skreferencenode
Another thing you can do if you plan on not using the SKS Builder (Which I recommend using because it is just awesome and a huge time saver), is to subclass your pop up menu as an SKSpriteNode, not a scene
class PopupMenu: SKSpriteNode {
required init?(coder: aDecoder:NScoder) {
super.init(coder:aDecoder)
}
override init(texture: SKTexture!, color: SKColor!, size: CGSize) {
super.init(texture: texture, color: color, size: size) {
}
convenience init() {
self.init(imageNamed: "theImage")
self.zPosition = 2
}
}
Then in your scene code, just call
let popup = PopupMenu()
popup.position = CGPointMake(self.frame.width/2, self.frame.height/2)
self.addChild(popup)
If you want your game works well, the first thing to do is to make sure that, when you pass to a new scene, the previous scene is deallocated.
I think you can construct a common menu or a common menu button to a custom class and call it anywhere you want:
class MenuButton : SKSpriteNode
{
var length: CGFloat!
override init(texture: SKTexture!, color: SKColor!, size: CGSize) {
self.length = 50 // default lenght
super.init(texture: texture, color: color, size: size)
}
convenience init(color: SKColor, length: CGFloat = 50) {
var size = CGSize(width: length, height: length);
self.init(texture:nil, color: color, size: size)
self.length = length
}
required init?(coder aDecoder: NSCoder) {
// Decoding length here would be nice...
super.init(coder: aDecoder)
}
}

Improper Instantiation of SKSpriteNode Subclass

I have created an SKSpriteNode subclass and have attempted to instantiate it in an SKScene and set some SKSpriteNode properties with other properties that I have stored in the subclass on instantiation. Here is the class definition.
class WalkingMonster: SKSpriteNode {
var rangeOfMovement: CGFloat
var originalPosition: CGFloat
var platformNumber: Int
var imageName = "walkingAlien"
var sizes = [CGSize(width: CGFloat(30.0), height: CGFloat(30.0)), CGSize(width: CGFloat(30.0), height: CGFloat(15.0)), CGSize(width: CGFloat(30.0), height: CGFloat(7.0))]
var monsterName: String
var textureName: String
init(texture: SKTexture, color: UIColor, size: CGSize, rangeOfMovement: CGFloat, originalPosition: CGFloat, platformNumber: Int, name: String, textureName: String) {
self.rangeOfMovement = rangeOfMovement
self.originalPosition = originalPosition
self.platformNumber = platformNumber
self.monsterName = name
self.textureName = textureName
super.init(texture: texture, color: color, size: size)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is the instantiation of the class:
var walkingMonsters = [WalkingMonster(texture: SKTexture(), color: UIColor(), size: CGSize(), rangeOfMovement: CGFloat(25.0), originalPosition: CGFloat(750.0), platformNumber: -1, name: "walkingMonster1", textureName: "walkingAlien1")]
This is how I am setting its properties in the SKScene within didMoveToView():
for monster in walkingMonsters {
let texture = SKTexture(imageNamed: monster.textureName)
monster.texture = texture
monster.size = monster.sizes[0]
monster.position.x = monster.originalPosition
//If it is supposed to sit on the ground
if monster.platformNumber < 0 {
monster.position.y = self.groundHeight + super.groundBlockSize.height / 2 + monster.size.height / 2
}
monster.physicsBody?.categoryBitMask = physicsBitMasks.walkingEnemy
monster.physicsBody?.contactTestBitMask = physicsBitMasks.ground | physicsBitMasks.player | physicsBitMasks.aerialBlock
monster.physicsBody?.contactTestBitMask = physicsBitMasks.ground | physicsBitMasks.player | physicsBitMasks.aerialBlock
monster.physicsBody?.dynamic = true
monster.physicsBody?.affectedByGravity = true
self.addChild(monster)
}
}
At runtime there is a monster that spawns, but he (or at least his texture) appears to be off the ground a little. When the player (who has categoryBitMask of .player and whose contact and collision bit masks include .walkingEnemy) walks into him, nothing happens to the movement of the main character and the walkingMonster (walkingMonsters[0]) does not move either. I am not sure if there is some small thing I am missing syntactically or if there is a larger blocker.
My other method of doing this, which I have already implemented successfully, was to store those member variables of what should stylistically be a subclass of SKSpriteNode in corresponding parameter vectors, then set them after using the SKSpriteNode convenience initializer SKSpriteNode(imageNamed: "whatever"). I think subclassing is the way to go however.
The issue was that I was not initializing the physicsbody for each monster in the loop.
for monster in walkingMonsters {
let body = SKPhysicsBody(rectangleOfSize: monster.sizes[0])
monster.physicsBody = body
//rest of setting comes here
}

Sprite Kit: create node only once for all scenes

Normally in a sprite kit game, when a new scene presented, all the nodes in the old scene and their content removed automatically. Now what is, if a node like "HUD" should be not removed? Is there any way in sprite kit to create a node only once and use it in all scenes without removing and creating it again and again every time in every new scene? There must be a technique that makes it possible. that's a serious sprite kit design problem, if it is not possible. But I don't think so. The singleton technique is working great with an audio player, that created only once and used in all scenes. There is surley a way to create a node only once and use it in all scenes. Thanks for any idea.
You can't create a node that persists between scenes. Once you present a new scene, you would need to add the nodes to this new scene.
For this reason, I do not use SKScenes the way Apple describes in the documentation because of this issue. Not only is it cumbersome to have to add the nodes to the new scene each time but also extremely inefficient for nodes like background nodes that should always be present.
So what I did is create 2 scenes, one for the game scene and one for the menu (GUI).
For the menu scene I subclass SKNodes for my interface and then use SKActions on these nodes to present and dismiss them on the screen so it feels like the user is transitioning between scenes. This gives you total customization because you can present multiple nodes, you can keep nodes on the screen permanently etc.
By subclassing the SKNodes you can organize your code just as you did for the scenes. Each node will represent a "scene" in your App. Then you just need to write a method to present and dismiss these nodes.
I've added some sample code below to show one implementation of using SKNodes as "Scenes." The sample code has a base class called SceneNode which we subclass (just as you would subclass an SKScene). In this implementation, I use the GameScene to handle all transitions between scene nodes*. I also keep track of the current scene node so that I can update its layout in case the scene changes size (such as rotation or window resize on OS X**). Your game might not need this, but it's a great way to dynamically layout your nodes. Anything that you want to add to the background or keep around, simply add it to the GameScene. Anything that you want to add to a scene, simply subclass a SceneNode, transition to it and your good to go.
*You could easily present scene nodes directly from other scene nodes instead of going through the GameScene. However I have found that using the GameScene to handle transitions between nodes works very well, especially when you have many scenes with complex transitions.
**There is a bug on OS X, resizing the window does not call the scene's didChangeSize. You need to manually call it.
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size:self.view.bounds.size)
scene.scaleMode = .ResizeFill
(self.view as! SKView).presentScene(scene)
}
}
class GameScene: SKScene {
var currentSceneNode: SceneNode!
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.whiteColor()
transitionToScene(.Menu)
}
override func didChangeSize(oldSize: CGSize) {
currentSceneNode?.layout()
}
func transitionToScene(sceneType: SceneTransition) {
switch sceneType {
case .Menu:
currentSceneNode?.dismissWithAnimation(.Right)
currentSceneNode = MenuSceneNode(gameScene: self)
currentSceneNode.presentWithAnimation(.Right)
case .Scores:
currentSceneNode?.dismissWithAnimation(.Left)
currentSceneNode = ScoresSceneNode(gameScene: self)
currentSceneNode.presentWithAnimation(.Left)
default: fatalError("Unknown scene transition.")
}
}
}
class SceneNode: SKNode {
weak var gameScene: GameScene!
init(gameScene: GameScene) {
self.gameScene = gameScene
super.init()
}
func layout() {}
func presentWithAnimation(animation:Animation) {
layout()
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: invert*gameScene.size.width, y: 0)
gameScene.addChild(self)
let action = SKAction.moveTo(CGPoint(x: 0, y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.EaseInEaseOut
self.runAction(action)
}
func dismissWithAnimation(animation:Animation) {
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: 0, y: 0)
let action = SKAction.moveTo(CGPoint(x: invert*(-gameScene.size.width), y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.EaseInEaseOut
self.runAction(action, completion: {self.removeFromParent()})
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MenuSceneNode: SceneNode {
var label: SKLabelNode
var container: SKSpriteNode
override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Menu Scene")
label.horizontalAlignmentMode = .Center
label.verticalAlignmentMode = .Center
container = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.userInteractionEnabled = true
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.gameScene.transitionToScene(.Scores)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ScoresSceneNode: SceneNode {
var label: SKLabelNode
var container: SKSpriteNode
override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Scores Scene")
label.horizontalAlignmentMode = .Center
label.verticalAlignmentMode = .Center
container = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.userInteractionEnabled = true
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.gameScene.transitionToScene(.Menu)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
enum SceneTransition{
case Menu, Scores
}
enum Animation {
case Left, Right, None
}