Passing Information To A Class On Initialisation - swift

I'm struggling to understand how class initialisation works. From looking at solutions to other problems I have this as an example of the classes in my app.
import Foundation
import SpriteKit
class Ground : SKSpriteNode {
override init(texture: SKTexture!, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.zPosition = -20
self.name = "Ground";
}
func configure (size: CGSize, position: CGPoint) {
self.size = size
self.position = position
// Set up the Physics
self.physicsBody = SKPhysicsBody(rectangleOf: size)
self.physicsBody?.contactTestBitMask = PhysicsCategory.Player
self.physicsBody?.categoryBitMask = PhysicsCategory.Ground
self.physicsBody?.collisionBitMask = PhysicsCategory.All
self.physicsBody?.affectedByGravity = false
self.physicsBody?.allowsRotation = false
self.physicsBody?.isDynamic = false
self.physicsBody?.mass = 1.99999
}
convenience init(color: SKColor, isActive: Bool = false) {
let size = CGSize(width: 10, height: 10);
self.init(texture:nil, color: color, size: size)
}
required init?(coder aDecoder: NSCoder) {
// Decoding length here would be nice...
super.init(coder: aDecoder)
}
}
I've put the 'configure' function as a kludge fix to let me pass the scene to the class so I can set sizes depending on the size of the device screen. Ideally I would like to just pull this information on initialisation but everything I try throws up errors that I don't understand.
Im not sure which way would be correct but I was wondering firstly, how would I pass arguments to the class to start with.. e.g..
let myClass = Ground(scene: self)
or can I somehow pull the scene information from directly within the class? I can pass info into functions/methods as I did with 'configure' but I couldn't get it to work on initialisation which would certainly be cleaner.
How would you guys do it?

Size
You should not programmatically change the size of a sprite depending on the current device, just use the same size and then let SpriteKit resizing it for you.
Loading from .sks
This initializer
init?(coder aDecoder: NSCoder)
is used when the Sprite is loaded from a .sks file. However in Xcode 7 you cannot pass values from the .sks to the sprite to set the attributes. I'm not sure you are using a .sks file so for now I am just throwing a fatal_error here. In Xcode 8 however you will be able to pass values from the .sks file to your own class.
required init?(coder aDecoder: NSCoder) {
fatalError("Not implemented")
}
Scene
You don't need to pass a reference of the current scene to your class. Every SKNode has the scene property, just use it to retrieve the scene where the node lives. Of course keep in mind that if you invoke .scene before the node has been added to a scene it will returns nil.
Code
This is the final code
class Ground : SKSpriteNode {
init(color: SKColor = .clearColor(), isActive:Bool = false) {
let texture = SKTexture(imageNamed: "ground")
super.init(texture: texture, color: color, size: texture.size())
self.name = "Ground"
self.zPosition = -20
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody.contactTestBitMask = PhysicsCategory.Player
physicsBody.categoryBitMask = PhysicsCategory.Ground
physicsBody.collisionBitMask = PhysicsCategory.All
physicsBody.affectedByGravity = false
physicsBody.allowsRotation = false
physicsBody.dynamic = false
physicsBody.mass = 1.99999
self.physicsBody = physicsBody
}
required init?(coder aDecoder: NSCoder) {
fatalError("Not implemented")
}
}

Unless I'm missing something else you're trying to do, you should be able to move the initialization of the PhysicsBody and the other properties from you configure() method and into your init() for the class. Then you can pre-size the texture you pass in on instantiation to result in the proper size for the device.
Also, the position property of the SKSpriteNode can be set by the instantiating class. This means you can accomplish the sizing based on device and the setting of the position property right after instantiating it in your scene and before adding it as a child of the scene.
ADDED:
class Ground : SKSpriteNode {
override init(texture: SKTexture!, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
}
convenience init(texture: String, scene: SKScene, isActive: Bool = false) {
let texture = SKTexture(imageNamed: "\(yourtexturename)")
self.init(texture:teture, color: color, size: scene.size)
self.size = size
self.position = [YOU CAN NOW SET POSITION BASED ON THE SCENE]
self.physicsBody = SKPhysicsBody(rectangleOf: size)
self.physicsBody?.contactTestBitMask = PhysicsCategory.Player
self.physicsBody?.categoryBitMask = PhysicsCategory.Ground
self.physicsBody?.collisionBitMask = PhysicsCategory.All
self.physicsBody?.affectedByGravity = false
self.physicsBody?.allowsRotation = false
self.physicsBody?.isDynamic = false
self.physicsBody?.mass = 1.99999
self.zPosition = -20
self.name = "Ground";
}
required init?(coder aDecoder: NSCoder) {
// Decoding length here would be nice...
super.init(coder: aDecoder)
}
}
You might not need to override the original init() method but I didn't test anything.

Related

Creating an SKSpriteNode class with Swift 4

I've coded a simple game using Swift 4 and XCode, and I've coded everything in the GameScene. All my elements (the monsters, the player, the projectile, etc.) are coded in the GameScene.
I want to transfer my code into dedicated classes (Class Player, class monster, etc.)
I would like to know what the basic structure of a SKSpriteNode class and the call of that class in the GameScene, to be more efficient at adapting my code.
Here's an example of what I've tried :
class Vaisseau: SKSpriteNode /*: Creatures */{
var coeur: Int = 0
init(texture: SKTexture, size: CGSize)
{
let texture = SKTexture(imageNamed: "player")
super.init(texture: texture, color: UIColor.clear, size: texture.size())
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
And the initialization in the GameScene :
let player = Vaisseau()
Here's how it is actually defined in the GameScene :
let player = SKSpriteNode(imageNamed: "player")
you are declaring your init to have two parameters (texture: SKTexture, size: CGSize) but you are not passing the parameters in your initialization call
let player = Vaisseau()
you either need to change the initialization to...
let texture = SKTexture(imageNamed: "player")
let player = Vaisseau(texture: texture, size: texture.size())
and change the init to
init(texture: SKTexture, size: CGSize) {
super.init(texture: texture, color: UIColor.clear, size: size)
}
OR change the init in the class to...
init() {
let texture = SKTexture(imageNamed: "player")
super.init(texture: texture, color: UIColor.clear, size: texture.size())
}
and leave your initialization call as...
let player = Vaisseau()
player.position = CGPoint(x: 500, y: 500)
addChild(player)
EDIT added the above 2 lines to show you that those need to be in the scene
but other items such as alpha, zPosition, actions, zRotation etc. can be inside of the class
What you need to ask yourself to figure out which one to use is "will the texture for the player ever be different?" if so you may want to consider the first option where you pass in the texture.

Sprite is missing on scene

I'm new in Sprite Kit and I have a strange problem with my GameScene. Can't figure out, what causes the problem. I present my scene from controller in viewWillAppearMethod in this way:
let atlas = SKTextureAtlas(named: "Sprites")
atlas.preload { [unowned self] in
DispatchQueue.main.async {
self.gameScene = GameScene(level: self.level, size: self.gameSKView!.bounds.size)
self.gameScene.scaleMode = .resizeFill
self.gameSKView?.presentScene(self.gameScene)
self.gameSKView?.ignoresSiblingOrder = true
self.gameSKView?.showsNodeCount = true
}
}
My sprite atlas content looks like: link
Than i create my spaceship:
final class SpaceshipSpriteNode: SKSpriteNode {
required init(size: CGSize) {
let texture = SKTexture(image: #imageLiteral(resourceName: "Spaceship"))
super.init(texture: texture, color: .white, size: size)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
func configureSpaceship() {
let middleRow = Int(Double(unwrappedMatrix.rowsCount) / 2)
let middleColumn = Int(Double(unwrappedMatrix.columnsCount) / 2)
let xOffset = CGFloat(level.startPoint.column - middleColumn)
let yOffset = CGFloat(level.startPoint.row - middleRow)
spaceship = SpaceshipSpriteNode(size: spaceshipSize)
spaceship.position = CGPoint(x: frame.midX + (spaceshipSize.width * xOffset), y: frame.midY + (spaceshipSize.height * yOffset))
spaceshipObject.addChild(spaceship)
addChild(spaceshipObject)
}
configureSpaceship method is called in didMove(to view: SKView)
The problem is that sometimes(1 per 3/4/5/6 cases) my spaceship is missing from the scene. Visibility, position, size are always the same, a count of the nodes on scene is the same too. Some images here link
According to comments, I have changed zPosition for my objects:
tile.zPosition = 0
spaceship.zPosition = 1.0
backgroundSpriteNode.zPosition = -1
And everything start working correct, thanks guys.

Sprite Kit Creating Separate Class

Attempting to design a game using Sprite-Kit I found that it would be easier to create a seperate class for one of my game objects being a laser from a ship. Within that class I would have some functions maybe such as updating, shooting etc. But whenever I create another class none of the variables I make are "declared". This is my code
import UIKit
import SpriteKit
class laser: SKSpriteNode {
let laser : SKSpriteNode = SKSpriteNode(imageNamed:"playerShip")
laser.position = CGPoint(x: 100, y: 200)//This is where it says no declaration
}
This is because you need to initiate the class.
In your example you would need to do the following which would allow you instantiate a laser like this laser()
class laser: SKSpriteNode {
let laser: SKSpriteNode = SKSpriteNode(imageNamed: "playerShip")
init() {
super.init(texture: nil, color: .clear, size: laser.size)
laser.position = CGPoint(x: 100, y: 200)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
But you probably really wanted this, which allows you to instantiate a laser like this let aLaser = laser("playerShip"). Then you can change the position like this alaser.position = CGPoint(x: 100, y: 200).
This method allows you to change the sprite and position easily for different lasers. Unless your game only has one laser.
class laser: SKSpriteNode {
init(_ imageName: String) {
let texture: SKTexture = SKTexture(imageNamed: imageName)
super.init(texture: texture, color: .clear, size: texture.size())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Is there a simple way to store two objects of different type in a container?

I'm trying to position an SKCameraNode at the center of multiple objects in my scene. I'm thinking of placing all objects of interest into some sort of container, and then calculating the centroid every update(). I have two classes, Ball and Car, that inherit from PhysicalObject, which inherits from SKSpriteNode. What I'd like to do is something like
var cameraObjects: Array<Ball, Car>!
...
var ball1 = Ball()
var car1 = Car()
cameraObjects.append(ball1)
cameraObjects.append(car1)
...
// Loop through objects and calculate centroid
But this crashes at the first append().
Note that I'd rather not create a dummy protocol for each class, as mentioned here. Is there a simple way to do this?
PhysicalObject.swift
import SpriteKit
class PhysicalObject: SKSpriteNode {
var objectMass: CGFloat!
override init(texture: SKTexture!, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.name = "physicalObject"
//Physics setup
physicsBody?.usesPreciseCollisionDetection = true
physicsBody?.affectedByGravity = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Ball.swift
import SpriteKit
class Ball: PhysicalObject {
let MAXBALLSPEED :CGFloat = 0.08
init(spawnPosition: CGPoint) {
/* Temporary initialization.. will have further customization for different classes */
let ballTexture = SKTexture(imageNamed: "ball")
let ballRadius = CGFloat(50)
let ballSize = CGSize(width: ballRadius, height: ballRadius)
super.init(texture: ballTexture, color: UIColor.clear, size: ballSize)
self.position = spawnPosition
self.name = "ball"
//Physics Setup
//self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(origin: spawnPosition, size: carSize) )
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.height/2)
self.objectMass = CGFloat(0.005)
physicsBody?.isDynamic = true // Default is true
physicsBody?.restitution = 1.0
self.physicsBody?.categoryBitMask = PhysicsCategory.Ball
self.physicsBody?.contactTestBitMask = PhysicsCategory.Boards | PhysicsCategory.Car
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Car.swift
import SpriteKit
class Car: PhysicalObject {
let MAXCARSPEED: CGFloat = 500
var steering: Bool
var diagonalLength: CGFloat
init(spawnPosition: CGPoint) {
/* Temporary initialization.. will have further customization for different classes of cars */
let carTexture = SKTexture(imageNamed: "BasicCar")
let carWidth = CGFloat(screenSize.width/20)
let carScale = CGFloat(183.0/140.0)
let carSize = CGSize(width: (carWidth*carScale), height: carWidth)
// Initialize movement variables
self.steering = false
self.diagonalLength = hypot(carSize.width, carSize.height)
//self.steerRight = false
//self.steerLeft = false
super.init(texture: carTexture, color: UIColor.clear, size: carSize)
self.speed = CGFloat(MAXCARSPEED)
self.position = spawnPosition
self.name = "car"
//Physics Setup
//self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(origin: spawnPosition, size: carSize) )
let carPhysicsSize = CGSize(width: self.size.width, height: self.size.height)
self.physicsBody = SKPhysicsBody(rectangleOf: carPhysicsSize)
self.objectMass = CGFloat(2000)
self.physicsBody?.friction = 0.5
self.physicsBody?.angularDamping = 1.0
self.physicsBody?.linearDamping = 1.0
self.physicsBody?.restitution = 1.0
physicsBody?.isDynamic = true // Default is true
self.physicsBody?.categoryBitMask = PhysicsCategory.Car
self.physicsBody?.contactTestBitMask = PhysicsCategory.Car | PhysicsCategory.Ball
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You can make an Array<PhysicalObject> (a.k.a. [PhysicalObject]), which can store both Ball objects, and Car objects.
You may find that the enumerateChildNodes method of SKNode is a better way to enumerate over all the objects with a certain name. That way, you don't need to manage the array yourself.
Take a look at Apple's documentation.
All you need to do is place all the objects you want be involved with the camera into a new parent SKNode, and use calculateAccumulatedFrame to get the frame around all the nodes, then just use the center of the parent's frame.

add custom property to custom class and initialize class

I am trying to initialize a custom class that inherits from SKSpriteNode. Currently the class definition looks like this
import SpriteKit
class PassengerNode: SKSpriteNode {
let passengerStart: Int = 2
let passengerDestination: Int = 0
init() {
let texture = SKTexture(imageNamed: "passenger1")
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
self.zPosition = 100
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateTexture(newTexture: SKTexture) {
self.texture = newTexture
}
}
I want to have passengerStart and passsengerDestination assigned to the Object, respectively to the instance of the class. And ideally these both values can be different with every instantiation.
When I instantiate the Class Object I think of something like
let newObject = PassengerNode(start: 1, destination: 0, texture: "passenger1")
But I dont get it working... How do I need to change my init?
Does someone have a helping thought?
Thanks in advance.
You have to write an init method for your subclass which takes those
three parameters:
class PassengerNode: SKSpriteNode {
let passengerStart: Int
let passengerDestination: Int
init(start: Int, destination: Int, texture: String) {
let texture = SKTexture(imageNamed: texture)
passengerStart = start
passengerDestination = destination
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
self.zPosition = 100
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// ... more methods ...
}
The init method must initialize the properties of the subclass and
then call super.init.