I'm working on a small project using SpriteKit and GameplayKit. It's my first time using an entity/component system and I'm loving it. But now, I'm at a point where I need to keep track of my entities states (Spawning, Normal, Removing) so they don't interact during their spawning phase (which may or may not include actions to animate the thing) and their removing phase.
So far, I've created a EntityStateComponent which instanciate a GKStateMachine with the different states and since there is no need for per-frame updates it's not that complicated. The thing is that this state is more entity related than component related and I'm wondering if it would make sense to subclass from GKEntity and add the state machine in there instead of in a component.
Your thoughts?
PS: I'm already sub-classing from GKEntity just to have a convenience init() that creates all the components
You are right, state is entity related, not component related. Put the state machine into the entity directly, or create an entity base class that all your entities inherit from.
class VisualEntityBase : GKEntity, VisualEntity {
var node: SKSpriteNode!
var stateMachine: GKStateMachine!
// MARK: Initialization
init(imageNamed: String, atStartPosition: CGPoint) {
super.init()
// Initialise Texture
let texture = SKTexture(imageNamed: imageNamed)
// Initialise Node
self.node = SKSpriteNode(texture: texture, size: texture.size())
self.node.position = atStartPosition
// Initialise StateMachine
self.stateMachine = GKStateMachine(states: [
VisualEntityIdle(),
VisualEntityPendingMove(),
VisualEntityMoving()
])
self.stateMachine.enter(VisualEntityIdle.self)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Related
I tried making two identical characters (two classes that extend from SKSpriteNode) for the game, the only difference is the SKTexture they're using. I'll refer to them as A and B. Whenever I let A shoot a bullet, it doesn't lag but when I let B shoot a bullet, it lags just a little bit. The problem became clear when I spam shoot from A, it stays at a constant 60 FPS but when I spam shoot from B, it lags, down to around 50 FPS.
A's texture is "Lampy.png" and I tried setting B's texture as "Lampy2.png". Those 2 images are exactly the same, the only difference is the file name, but B lags.
override init(){
super.init()
playerBody.texture = SKTexture(imageNamed: "Lampy2")
}
Code above is from B and it lags
override init(){
super.init()
playerBody.texture = SKTexture(imageNamed: "Lampy")
}
Code above is from A and it does not lag
Other parts of the two classes are exactly the same. Did I import the images incorrectly or something? I can't remember how I imported Lampy but for Lampy2 I dragged it directly from Finder to the assets folder.
Edit - Here's the full class code
import Foundation
import SpriteKit
class Lampy: Player{
override init(){
super.init()
playerBody.texture = SKTexture(imageNamed: "Lampy")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func shoot(){
let thiccBullet = Thicc()
thiccBullet.position = position
thiccBullet.bulletBody.zRotation = playerBody.zRotation
thiccBullet.owner = self;
parent?.addChild(thiccBullet)
}
}
At the line with playerBody.texture = SKTexture(imageNamed: "Lampy")
if I change Lampy to Lampy2 then it lags.
I create a class of kind SKShapeNode. in the class I create a ball var that implements some properties. one of the properties that I need is 'circleOfRadius' so the ball will get a specific size. I look at the question and the answer here: here but I don't really get it. here is my code:
class BallNode: SKShapeNode{
var lastPosition: CGPoint?
init(circleOfRadius: CGFloat){
super.init()
let radius = 25
self.init(circleOfRadius: radius)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
no matter what I try I get an error. how can I init the ball size inside the class?
thanks!
In my custom SKSpriteNode class, I want to be able to change properties such as anchorPoint, posistion, etc. within the custom class so I don't need to elsewhere.
import Foundation
import SpriteKit
open class Crank:SKSpriteNode {
init() {
super.init(texture: SKTexture(imageNamed: "crank"), color:
NSColor.white, size: CGSize(width: 155.0, height: 188.0))
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
How would I edit other properties?
import Foundation
import SpriteKit
class Crank: SKSpriteNode {
init(imageNamed image: String, position at: CGPoint, withAnchor anchor: CGPoint) {
super.init(imageNamed: image)
self.position = position
self.anchorPoint = anchor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
(Written from memory, so may contain small errors, but is broadly correct).
and then in your main code:
let myCrank = Crank(imageNamed: "Crank.png", at: CGPoint(300, 500), withAnchor: CGpointZero)
My answer notwithstanding, Alessandro has a point. I think that it's better to set 'standard' properties in the normal place.
If you are going to set any of the standard SKSpriteNode properties inside the actual class, then I think specifying them in an initialiser is good practice as it makes the m more visible,. Debugging a program where you don't appears to set a node's position, or texture etc. would be problematic.
Another way to do it would be to create a protocol and apply it to the standard SpriteKit classes that you are subclassing (or their ancestor).
For example:
protocol CustomSKNode
{
var position:CGFloat { get set }
var zRotation:CGFloat { get set }
// ...
}
extension SKNode: CustomSKNode {}
Once you've done this somewhere in your project, you'll be able to access these properties of on any of your custom SKNode or SKSpriteNode without having to import SpriteKit.
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.
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.