How to create a SKSpriteNode with Custom GKComponent? - ios

So im currently working on a game with spriteKit and gameplayKit. I've created a custom GKComponent class that I'm giving to certain SKSpriteNode via the scene inspector in Xcode (image). Now, when I want to re-create random SKSpritenodes with the same GKComponent, it doesn't seems to work because the overridden update method of my custom GKComponent is not working. Any clues?
I've tried to give the node a new entity object (since it was returning nil) and I gave that entity my custom GKComponent but nothing is working
// In scene
let node = SKSpriteNode(texture: "coin", color: UIColor.clear, size: CGSize(width: 60, height: 60))
let entities: GKEntity! = GKEntity()
let component: CollectableComponent = CollectableComponent()
component.itemType = 1
node.anchorPoint = CGPoint(x: 0.5, y: 0.5)
node.zPosition = 6
node.setScale(0.5)
node.size = CGSize(width: 60, height: 60)
node.position = CGPoint(x: 90, y: 90)
entities.addComponent(component)
node.entity = entities
self.scene?.addChild(node)
// GKComponent
class CustomComponent: GKComponent {
var node: SKSpriteNode?
#GKInspectable var itemType: Int = 0
#GKInspectable var isStatic: Bool = false
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func update(deltaTime seconds: TimeInterval) {
if node == nil {
if let nodeComponent = self.entity?.component(ofType: GKSKNodeComponent.self){
node = nodeComponent.node as? SKSpriteNode
}
}
}
}
Desired behaviour: if the nodeComponent variable in CustomComponent is not nil, I would like to make it run an animation like this:
node = nodeComponent.node as? SKSpriteNode; node.run(SKAction.moveBy(x: 0, y: 15, duration: 0.3));
but with the code, the action would not be running

This is the way you want to be doing this.
Take note that you need to retain your entities, so a global variable is needed.
Also, when you add a node to GKSKNodeComponent, the node.entity variable is set automatically.
class GameScene : SKScene{
var entities: [GKEntity] = [GKEntity]()
func doSomething(){
let node = SKSpriteNode(texture: "coin", color: UIColor.clear, size: CGSize(width: 60, height: 60))
node.anchorPoint = CGPoint(x: 0.5, y: 0.5)
node.zPosition = 6
node.setScale(0.5) //<--- YUCK!!!!!
node.size = CGSize(width: 60, height: 60)
node.position = CGPoint(x: 90, y: 90)
self.scene?.addChild(node)
let entity = GKEntity()
let nodeComponent : GKSKNodeComponent = GKSKNodeComponent(node:node)
let component: CollectableComponent = CollectableComponent()
component.itemType = 1
entity.addComponent(nodeComponent)
entity.addComponent(component)
entities.append(entity)
}
}
// GKComponent
class CustomComponent: GKComponent {
//We want this line to crash if node is empty, so do not use ?
lazy var node: SKSpriteNode! = {return self.entity!.component(ofType: GKSKNodeComponent.self).node as? SKSpriteNode! }
#GKInspectable var itemType: Int = 0
#GKInspectable var isStatic: Bool = false
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func update(deltaTime seconds: TimeInterval) {
}
}

Related

Why is my SKShapeNode subclass not appearing when I run my app?

I am trying to have objects fall from the top of the screen so I created a FallingBlock subclass that inherits from SKShapeNode. However, whenever I run my code, the blocks do not appear on the screen. I know these blocks are on screen because they come in contact with other objects, but they are unable to be seen. Below is my code:
import SpriteKit
class FallingBlock: SKShapeNode {
let color = [UIColor.red,UIColor.cyan,UIColor.green,UIColor.yellow,UIColor.orange,UIColor.systemPink].randomElement() as! UIColor
init(side: CGPoint) {
super.init()
self.path = CGPath(roundedRect: CGRect(x: side.x, y: side.y, width: 20, height: 20), cornerWidth: 3, cornerHeight: 3, transform: nil)
self.fillColor = color
self.strokeColor = color
self.position = side
self.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 20, height: 20))
self.physicsBody?.affectedByGravity = true
self.zPosition = 1
self.physicsBody?.categoryBitMask = PhysicsCategories.obstacleCategory
self.name = ObjectNames.obstacle
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("falling block reference deleted")
}
}
Within my GameScene I am running the following function:
func newObstacle() {
let side = [25,frame.width-25].randomElement() as! CGFloat
obstacle = FallingBlock(side: CGPoint(x: side, y: frame.maxY+15))
worldNode.addChild(obstacle)
deallocateObstacle()
}
I couldn't figure out a solution when the class inherits from an SKShapeNode; however, I was able to figure out how to do it when it inherits from an SKNode. All that is required is to initialize the SKNode and then add an SKShapeNode - with your desired attributes - as a child of the SKNode like what is done below:
class FallingBlock: SKNode {
init(side: CGPoint) {
super.init()
self.addNewBlock(side)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("falling block reference deleted")
}
private func addNewBlock(_ side: CGPoint) {
var color = [UIColor.red,UIColor.cyan,UIColor.green,UIColor.yellow,UIColor.orange,UIColor.systemPink].randomElement() as! UIColor
let obstacle = SKShapeNode(rectOf: CGSize(width: 20, height: 20), cornerRadius: 3)
obstacle.fillColor = color
obstacle.strokeColor = color
obstacle.position = side
obstacle.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 20, height: 20))
obstacle.physicsBody?.affectedByGravity = true
obstacle.zPosition = 1
obstacle.physicsBody?.categoryBitMask = PhysicsCategories.obstacleCategory
obstacle.name = ObjectNames.obstacle
self.addChild(obstacle)
}
}

iOS animation using spritekit

I'm trying to create an animation on my iOS application using spritekit.
so far the code that i have done for this is
import UIKit
import SpriteKit
import GameplayKit
class TygaScene: SKScene {
var tygaFrames: [SKTexture]? // array of images
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(size: CGSize) { // init function then set size
super.init(size: size)
self.backgroundColor = UIColor.white // setting colour to background
var frames:[SKTexture] = [] // create place holder for texture to initialise
let tygaAtlas = SKTextureAtlas(named: "tyga")
for index in 1...5 { // adding all images from folder
let textureName = "tyga_\(index)" // naming the first bit of the file
let texture = tygaAtlas.textureNamed(textureName)
frames.append(texture)
}
self.tygaFrames = frames
}
func moveTyga() {
let texture = self.tygaFrames![0]
let tyga = SKSpriteNode(texture: texture)
tyga.size = CGSize(width: 280, height: 280)
let randometygaYPositionGenerator = GKRandomDistribution(lowestValue: 50, highestValue: Int(self.frame.size.height))
let yPosition = CGFloat(randometygaYPositionGenerator.nextInt())
let rightToLeft = arc4random() % 2 == 0
let xPosition = rightToLeft ? self.frame.size.width + tyga.size.width / 2 : -tyga.size.width / 2
tyga.position = CGPoint(x: xPosition, y: yPosition)
if rightToLeft {
tyga.xScale = -1
}
self.addChild(tyga)
tyga.run(SKAction.repeatForever(SKAction.animate(with: self.tygaFrames!, timePerFrame: 0.05, resize: false, restore: true)))
var distanceToCover = self.frame.size.width + tyga.size.width
if rightToLeft {
distanceToCover += -1
}
let time = TimeInterval(abs(distanceToCover/140))
let moveAction = SKAction.run {
tyga.removeAllActions ()
tyga.removeFromParent()
}
let allActions = SKAction.sequence([moveAction, removeAction])
tyga.run(allActions)
}
}
The part that is giving an error is the line:
let allActions = SKAction.sequence([moveAction, removeAction])
tyga.run(allActions)
}
could you please advise me on how i could overcome this problem?
Thanks.

SpriteKit - Adding enemy causes frame drop

My game is a side scroller using SpriteKit. The player stays at the same x-position and the obstacles are moving towards him. But whenever I add an enemy the frame rate drops for a short moment. I tried to change many things, like removing the enemy's dynamics or animation. But nothing works.
Here's some code:
func addZombie() {
let zombie = Zombie()
zombie.position = CGPoint(x: screenWidth + zombie.size.width * 0.5, y: screenHeight * 0.5)
zombies.append(zombie)
addChild(zombie)
}
The above method creates the enemies. It's called by using:
func generateZombies() {
let wait = SKAction.waitForDuration(2.5)
let run = SKAction.runBlock {
self.addZombie()
}
runAction(SKAction.repeatActionForever(SKAction.sequence([wait, run])))
}
I also tried NSTimer, but nothing works. Please help me.
Zombie():
class Zombie: SKSpriteNode {
var textureArrayWalking = [SKTexture]()
init() {
let mySize = CGSize(width: Player().size.width, height: Player().size.height * (4/3))
super.init(texture: nil, color: UIColor.greenColor(), size: mySize)
orderTextureAtlases()
texture = textureArrayWalking[0]
zPosition = 1
physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: (2/3) * self.size.width, height: self.size.height))
physicsBody?.restitution = 0.0
physicsBody?.categoryBitMask = enemyCategory
physicsBody?.collisionBitMask = groundCategory
physicsBody?.contactTestBitMask = playerCategory | bulletCategory | groundCategory
physicsBody!.allowsRotation = false;
walk()
}
func walk() {
runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(textureArrayWalking, timePerFrame: 0.07)), withKey: walkingActionKeyZombie)
}
func orderTextureAtlases() {
let textureAtlasWalking = SKTextureAtlas(named: "ZombieWalking")
for index in 0..<textureAtlasWalking.textureNames.count {
let name = "ZombieWalking_JeromeWalk_\(index)"
let tex = SKTexture(imageNamed: name)
tex.filteringMode = .Nearest
textureArrayWalking.append(tex)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Accessing SceneKit-Node from ViewController

I'm trying to build an SceneKit-App in Swift. This App should have an SpriteKit-Overlay. My problem is, that i can access the LabelNode from SpriteKit inside my ViewController, but cannot change the position-value of the Cube-Node in SceneKit, although i can access the node. Because i'm a newbie it would be very nice, if anyone could point me in the right direction.
class GameViewController: UIViewController {
var sceneView: SCNView!
var spriteScene: SpriteKitOverlay!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
self.sceneView.scene = SceneKitScene()
self.sceneView.backgroundColor = UIColor.brownColor()
self.view.addSubview(self.sceneView)
self.spriteScene = SpriteKitOverlay(size: self.view.bounds.size)
self.sceneView.overlaySKScene = self.spriteScene
SceneKitScene().cubeNode.position = SCNVector3(0,10,0)
spriteScene.labelTest.text = "Changed label"
print(SceneKitScene().cubeNode.position)
}
}
class SpriteKitOverlay: SKScene {
var labelTest: SKLabelNode!
override init(size: CGSize) {
super.init(size: size)
self.labelTest = SKLabelNode(text: "Label text")
self.labelTest.fontColor = UIColor.whiteColor()
self.labelTest.fontSize = 24
self.labelTest.position = CGPoint(x: size.width/2, y: size.height/2)
self.addChild(self.labelTest)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
class SceneKitScene: SCNScene {
var cubeNode: SCNNode!
var cameraNode: SCNNode!
var lightNode: SCNNode!
override init() {
super.init()
let cube = SCNBox(width: 3, height: 3, length: 3, chamferRadius: 0)
let cubeMaterial = SCNMaterial()
cubeMaterial.diffuse.contents = UIColor.blueColor()
cube.materials = [cubeMaterial]
self.cubeNode = SCNNode(geometry: cube)
self.cubeNode.position = SCNVector3(0,0,0)
self.cubeNode.runAction(SCNAction.repeatActionForever(SCNAction.rotateByX(0, y: 0.01, z: 0, duration: 1.0/60.0)))
let camera = SCNCamera()
camera.xFov = 60
camera.yFov = 60
let ambientLight = SCNLight()
ambientLight.type = SCNLightTypeAmbient
ambientLight.color = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
let cameraConstraint = SCNLookAtConstraint(target: self.cubeNode)
cameraConstraint.gimbalLockEnabled = true
self.cameraNode = SCNNode()
self.cameraNode.camera = camera
self.cameraNode.constraints = [cameraConstraint]
self.cameraNode.light = ambientLight
self.cameraNode.position = SCNVector3(x: 5, y: 5, z: 5)
let omniLight = SCNLight()
omniLight.type = SCNLightTypeOmni
self.lightNode = SCNNode()
self.lightNode.light = omniLight
self.lightNode.position = SCNVector3(x: -3, y: 5, z: 3)
self.rootNode.addChildNode(self.cubeNode)
self.rootNode.addChildNode(self.cameraNode)
self.rootNode.addChildNode(self.lightNode)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
When you call SceneKitScene().cubeNode.position = SCNVector3(0,10,0), you are trying to change cubeNode.position on a newly created SceneKitScene.
Try changing your GameViewController to:
class GameViewController: UIViewController {
var sceneView: SCNView!
// create new instance variable to hold current sceneKitScene
var sceneKitScene: SceneKitScene!
var spriteScene: SpriteKitOverlay!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
// save SceneKitScene to instance variable
self.sceneKitScene = SceneKitScene()
self.sceneView.scene = self.sceneKitScene
self.sceneView.backgroundColor = UIColor.brownColor()
self.view.addSubview(self.sceneView)
self.spriteScene = SpriteKitOverlay(size: self.view.bounds.size)
self.sceneView.overlaySKScene = self.spriteScene
// update cubeNode position in active sceneKitScene
sceneKitScene.cubeNode.position = SCNVector3(0,10,0)
spriteScene.labelTest.text = "Changed label"
print(SceneKitScene().cubeNode.position)
}
}
This creates a new instance variable to hold your current SceneKitScene (same as you did with your current SpriteKitOverlay). Then you can use this variable to access and update the position of the current cubeNode on screen.

Why doesn't my snow fall to the ground?

I am trying to make a application (in swift) where snow falls in the background. The only problem is I have added the gravity animation, however the snow just stays where it is.
Here is my code:
ViewController.swift
import UIKit
#IBDesignable
class ViewController: UIViewController {
#IBInspectable var BgColor:UIColor = UIColor.whiteColor()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = BgColor
/*listSubviewsOfView(self.view)*/ /*Not needed to answer this*/
var snow = Snow(frame: CGRect(x: 0, y: 0, width: 5, height: 5))
snow.opaque = false
self.view.addSubview(snow)
let animator = UIDynamicAnimator(referenceView: self.view)
let gravity = UIGravityBehavior(items: [snow])
let direction = CGVectorMake(0.0, 1.0)
gravity.gravityDirection = direction
animator.addBehavior(gravity)
/*
var snow = Snow(frame: CGRect(x: 0, y: 0, width: 5, height: 5))
snow.opaque = false
snow.viewHeight = UIScreen.mainScreen().bounds.height
snow.addSubview(snow)
let animator = UIDynamicAnimator(referenceView: view)
let gravity = UIGravityBehavior(items: [snow])
animator.addBehavior(gravity)
*/
}
/*Not needed to solve this*/
/*
func listSubviewsOfView(views: UIView) {
var index = 0
let randomNumbers = [Int](1...24).shuffle()
for view in views.subviews
{
if let _ = view.restorationIdentifier
{
view.setValue(String(Int(randomNumbers[index])), forKey: "updateText")
index++
}
if index == randomNumbers.count {
break
}
}
}
*/
}
Snow.swift:
import Foundation
import UIKit
#IBDesignable
class Snow:UIView
{
var viewHeight = CGFloat(0)
/*
required init(coder aDecoder: NSCoder) {
//Initilse UIView
super.init(coder: aDecoder)!
}
*/
override func drawRect(rect: CGRect) {
let path = UIBezierPath(ovalInRect: rect)
UIColor.whiteColor().setFill()
path.fill()
}
}
The question is, why does my snow stay on top of the screen at (0,0) and not fall down even though I have told it to have the gravity affect?
Make animator and gravity properties of your view controller.
import UIKit
#IBDesignable
class ViewController: UIViewController {
#IBInspectable var BgColor:UIColor = UIColor.whiteColor()
var animator: UIDynamicAnimator? = nil;
let gravity = UIGravityBehavior()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blackColor()
let snow = Snow(frame: CGRect(x: 0, y: 0, width: 5, height: 5))
snow.opaque = false
self.view.addSubview(snow)
animator = UIDynamicAnimator(referenceView:self.view);
animator?.addBehavior(gravity)
gravity.addItem(snow)
let direction = CGVectorMake(0.0, 1.0)
gravity.gravityDirection = direction
}
}

Resources