SKEmitterNode iOS 8 vs iOS 9 How to get the same result? - swift

I have a game similar to fruit ninja using Swift -> SpriteKit. Everything is working fine on iOS 8 but on iOS 9 SKEmitterNode is having a bit strange behavior. This is what I get for my blade effect on both:
func emitterNodeWithColor(color:UIColor)->SKEmitterNode {
let emitterNode:SKEmitterNode = SKEmitterNode()
emitterNode.particleTexture = SKTexture(imageNamed: "spark.png")
emitterNode.particleBirthRate = 3000
emitterNode.particleLifetime = 0.2
emitterNode.particleLifetimeRange = 0
emitterNode.particlePositionRange = CGVectorMake(0.0, 0.0)
emitterNode.particleSpeed = 0.0
emitterNode.particleSpeedRange = 0.0
emitterNode.particleAlpha = 0.8
emitterNode.particleAlphaRange = 0.2
emitterNode.particleAlphaSpeed = -0.45
emitterNode.particleScale = 0.5
emitterNode.particleScaleRange = 0.001
emitterNode.particleScaleSpeed = -1
emitterNode.particleRotation = 0
emitterNode.particleRotationRange = 0
emitterNode.particleRotationSpeed = 0
emitterNode.particleColorBlendFactor = 1
emitterNode.particleColorBlendFactorRange = 0
emitterNode.particleColorBlendFactorSpeed = 0
emitterNode.particleColor = color
emitterNode.particleBlendMode = SKBlendMode.Add
return emitterNode
}
let emitter:SKEmitterNode = emitterNodeWithColor(color)
emitter.targetNode = target
emitter.zPosition = 0
tip.addChild(emitter)
This is the method I am using with all the options. It is the same for both but the result is different. Any ideas how can I make the effect in iOS 9 to be the same as iOS 8 ?

I'm facing the exact same issue in my project.
The emitter's performance is low in iOS9 (Metal version not finished?), so Apple shut off the interpolation of the drawing to get back the performance a little (The drawing rate is limited to 60 fps, anything between two frames is not rendered).
My solution is to implement the tail myself, which is simple:
class TailNode: SKSpriteNode {
var tailTexture: SKTexture!
var tailSize: CGSize! = CGSizeMake(30, 30)
var tailColor: SKColor!
var tailBlendMode: SKBlendMode!
var initialAlpha: CGFloat = 0.6
var initialScale: CGFloat = 0
var finalScale: CGFloat = 1
var particleLife: NSTimeInterval = 0.1
var running: Bool = false
var particleAction: SKAction!
var lastParticle: SKSpriteNode?
var battleScene: BattleScene {
return self.scene as! BattleScene
}
convenience init(tailTexture: SKTexture, tailSize: CGSize, tailColor: SKColor, tailBlendMode: SKBlendMode, initialAlpha: CGFloat, initialScale: CGFloat, finalScale: CGFloat, particleLife: NSTimeInterval) {
self.init(texture: nil, color: SKColor.whiteColor(), size: CGSize(width: 0, height: 0))
self.tailTexture = tailTexture
self.tailSize = tailSize
self.tailColor = tailColor
self.tailBlendMode = tailBlendMode
self.initialAlpha = initialAlpha
self.initialScale = initialScale
self.finalScale = finalScale
self.particleLife = particleLife
let fadeAction = SKAction.fadeAlphaTo(0, duration: particleLife)
let scaleAction = SKAction.scaleTo(finalScale, duration: particleLife)
let removeAction = SKAction.removeFromParent()
self.particleAction = SKAction.sequence([SKAction.group([fadeAction, scaleAction]), removeAction])
}
func updateWithTimeSinceLastUpdate(interval: NSTimeInterval) {
if running {
let particlePosition = battleScene.convertPoint(battleScene.convertPoint(self.position, fromNode: self.parent!),
toNode:battleScene.worldLayers[.UnderCharacter]!)
if lastParticle == nil || lastParticle!.parent == nil {
lastParticle = nil
} else {
let lastPosition = lastParticle!.position
let x = lastPosition.x + (particlePosition.x - lastPosition.x)*0.5
let y = lastPosition.y + (particlePosition.y - lastPosition.y)*0.5
newParticleAtPosition(CGPointMake(x, y), withDelay: interval*0.5)
}
lastParticle = newParticleAtPosition(particlePosition, withDelay: interval)
}
}
func newParticleAtPosition(position: CGPoint, withDelay delay: NSTimeInterval) -> SKSpriteNode {
let myParticle = SKSpriteNode(texture: tailTexture, color: tailColor, size: tailSize)
myParticle.colorBlendFactor = 1
myParticle.blendMode = tailBlendMode
myParticle.alpha = initialAlpha
myParticle.setScale(initialScale)
myParticle.position = position
battleScene.addNode(myParticle, atWorldLayer: .UnderCharacter)
myParticle.runAction(SKAction.sequence([SKAction.waitForDuration(delay), particleAction]))
return myParticle
}
}

Related

Swift spritekit didbegincontact being called with delay

This is my GameScene code.
class GameScene: SKScene, SKPhysicsContactDelegate {
let orcWidth = UIScreen.main.bounds.width / 5
var orcCategory:UInt32 = 0x1 << 0
var knightCategory:UInt32 = 0x1 << 1
private var orc = SKSpriteNode()
private var knight = SKSpriteNode()
private var orcWalkingFrames: [SKTexture] = []
private var knightIdleFrames: [SKTexture] = []
private var knightAttackFrame: [SKTexture] = []
var background = SKSpriteNode(imageNamed: "game_background1")
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
setupbackground()
startGame()
}
func setupbackground() {
background.zPosition = 0
background.size = self.frame.size
background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
addChild(background)
}
func startGame() {
buildRandomOrcs()
buildKnight()
}
func stopGame() {
}
func buildOrc(yposition: CGFloat) {
var orcWalkFrames: [SKTexture] = []
let orcAnimatedAtlas = SKTextureAtlas(named: "OrcWalking")
let numImages = orcAnimatedAtlas.textureNames.count
for i in 0...numImages - 1 {
let orcTextureName = "0_Orc_Walking_\(i)"
orcWalkFrames.append(orcAnimatedAtlas.textureNamed(orcTextureName))
}
self.orcWalkingFrames = orcWalkFrames
let firstFrameTexture = orcWalkingFrames[0]
orc = SKSpriteNode(texture: firstFrameTexture)
orc.name = "orc"
orc.position = CGPoint(x: frame.minX-orcWidth/2, y: yposition)
self.orc.zPosition = CGFloat(self.children.count)
orc.scale(to: CGSize(width: orcWidth, height: orcWidth))
orc.physicsBody = SKPhysicsBody(rectangleOf: orc.size, center: orc.position)
orc.physicsBody?.affectedByGravity = false
orc.physicsBody?.isDynamic = true
orc.physicsBody?.categoryBitMask = orcCategory
orc.physicsBody?.contactTestBitMask = knightCategory
orc.physicsBody?.collisionBitMask = knightCategory
addChild(orc)
walkOrc()
moveOrcForward()
}
func buildKnight() {
var knightIdleFrames: [SKTexture] = []
let knightIdleAtlas = SKTextureAtlas(named: "KnightIdle")
let numImages = knightIdleAtlas.textureNames.count
for i in 0...numImages - 1 {
let orcTextureName = "_IDLE_00\(i)"
knightIdleFrames.append(knightIdleAtlas.textureNamed(orcTextureName))
}
self.knightIdleFrames = knightIdleFrames
let firstFrameTexture = knightIdleFrames[0]
knight = SKSpriteNode(texture: firstFrameTexture)
knight.name = "knight"
knight.position = CGPoint(x: frame.maxX-orcWidth/2, y: frame.midY)
self.knight.zPosition = 1
knight.scale(to: CGSize(width: -orcWidth, height: orcWidth))
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size, center: knight.position)
knight.physicsBody?.affectedByGravity = false
knight.physicsBody?.isDynamic = false
knight.physicsBody?.categoryBitMask = knightCategory
knight.physicsBody?.contactTestBitMask = orcCategory
knight.physicsBody?.collisionBitMask = orcCategory
addChild(knight)
idleKnight()
}
func idleKnight() {
knight.run(SKAction.repeatForever(SKAction.animate(with: knightIdleFrames, timePerFrame: 0.1)))
}
func walkOrc() {
orc.run(SKAction.repeatForever(SKAction.animate(with: orcWalkingFrames,timePerFrame: 0.025)))
}
func moveOrcForward() {
orc.run(SKAction.repeatForever(SKAction.moveBy(x: 55, y: 0, duration: 0.25)))
}
func buildRandomOrcs () {
let wait = SKAction.wait(forDuration: TimeInterval(makeRandomNumberBetween(min: 0, max: 0)))
let spawn = SKAction.run {
self.buildOrc(yposition: self.makeRandomCGFloatNumber())
}
let spawning = SKAction.sequence([spawn,wait])
self.run(SKAction.repeat(spawning, count: 10))
}
func makeRandomCGFloatNumber() -> CGFloat {
let randomNumber = arc4random_uniform(UInt32((frame.maxY-orcWidth/2) - (frame.minY+orcWidth/2))) + UInt32(frame.minY+orcWidth/2)
return CGFloat(randomNumber)
}
func makeRandomNumberBetween (min: Int, max: Int) -> Int{
let randomNumber = arc4random_uniform(UInt32(max - min)) + UInt32(min)
return Int(randomNumber)
}
func didBegin(_ contact: SKPhysicsContact) {
let collision:UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == orcCategory | knightCategory {
self.scene?.view?.isPaused = true
print("COLLIDED")
}
}
}
The problem is that the scene pauses almost 2-3 seconds after the collision.
I changed the position of knight and delay time changed.
For example, if I set position to frame.minX+orcWidth/2 there is no delay.
What is wrong with my code?
Your problem isn't things are being delayed, your problem is your bounding box is not where you think it is
use view.showPhysics = true to determine where your boxes are
once you realize they are in the wrong spots, go to this line
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size, center: knight.position)
and fix it
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size)
Do so for the rest of your bodies
My guess is that manipulations of SKView properties must happen on main thread, i.e.
DispatchQueue.main.async { [unowned self] in
self.scene?.view?.isPaused = true
print("COLLIDED")
}

What is the proper way to end a CAEmitterLayer in Swift?

I've mostly seen examples of continuous emitters in Swift, and I've found one example in Obj-C by setting the birthRates of the emitter cells to 0.0, but it doesn't seem to work, so I must be doing something wrong. In my example, I can see the message that the birth rate was set to 0 sixteen times, but the particles continue to flow endlessly.
#IBAction func particleBtnAction(_ sender: Any) {
let emitter = CAEmitterLayer()
emitter.emitterPosition = CGPoint(x: self.view.frame.size.width / 2, y: -10)
emitter.emitterShape = kCAEmitterLayerLine
emitter.emitterSize = CGSize(width: self.view.frame.size.width, height: 2.0)
emitter.emitterCells = generateEmitterCells()
self.view.layer.addSublayer(emitter)
// perform selector after 1.5 seconds when particles start
perform(#selector(endParticles), with: emitter, afterDelay: 1.5)
}
private func generateEmitterCells() -> [CAEmitterCell] {
var cells:[CAEmitterCell] = [CAEmitterCell]()
for index in 0..<16 {
let cell = CAEmitterCell()
cell.birthRate = 4.0
cell.lifetime = 1.0
cell.lifetimeRange = 0
cell.velocity = 0.7
cell.velocityRange = 0
cell.emissionLongitude = CGFloat(Double.pi)
cell.emissionRange = 0.5
cell.spin = 3.5
cell.spinRange = 0
cell.scaleRange = 0.25
cell.scale = 0.1
cells.append(cell)
}
return cells
}
#objc func endParticles(emitterLayer:CAEmitterLayer) {
for emitterCell in emitterLayer.emitterCells! {
emitterCell.birthRate = 0.0
print("birth rate set to 0")
}
}
Setting the CAEmitterLayer's lifetime to zero stops any new emitterCells being emitted:
#objc func endParticles(emitterLayer:CAEmitterLayer) {
emitterLayer.lifetime = 0.0
}
You can use key paths to assign a name to each cell and loop through them, changing each cell's property when you want to change them:
private func generateEmitterCells() -> [CAEmitterCell] {
var cells:[CAEmitterCell] = [CAEmitterCell]()
for index in 0..<16 {
let cell = CAEmitterCell()
cell.birthRate = 4.0
cell.lifetime = 1.0
cell.lifetimeRange = 0
cell.velocity = 0.7
cell.velocityRange = 0
cell.emissionLongitude = CGFloat(Double.pi)
cell.emissionRange = 0.5
cell.spin = 3.5
cell.spinRange = 0
cell.scaleRange = 0.25
cell.scale = 0.1
cell.name = "cell\(index)" // cell name
cells.append(cell)
}
return cells
}
#objc func endParticles(emitterLayer:CAEmitterLayer) {
for i in 0..<(emitterLayer.emitterCells?.count ?? 0){
emitterLayer.setValue(0.0, forKeyPath: "emitterCells.cell\(i).birthRate")
print("birth rate set to 0")
}
}
You might try setting the isHidden property when you want to endParticles
emitter.isHidden = true
But note that all the cells instantly vanish, no matter when they were emitted, or their lifetime.
Another possibility would be to set all the scale related properties to 0, and then the lifetime and birthrate would not matter, as newly emitted cells would not be visible.
cell.scaleSpeed = 0
cell.scaleRange = 0
cell.scale = 0
What worked for me is this:
let emmitter = CAEmitterLayer()
let cell = makeCell()
emmitter.emitterCells = [cell]
view.layer.addSublayer(emmitter)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
emmitter.removeFromSuperlayer()
}

How to make sprites follow a random pattern within a circle?

I am makeing a game in which I want that the enemies move following a random pattern within a circle. I already made that the enemies spawn randomly in all the sides of the screen, but the problem is that I dont know how to make the enemies move following a random pattern within a circle just like the image.
class GameScene: SKScene, SKPhysicsContactDelegate {
var circuloPrincipal = SKSpriteNode(imageNamed: "circulo")
var enemigoTimer = NSTimer()
}
override func didMoveToView(view: SKView) {
circuloPrincipal.size = CGSize(width: 225, height: 225)
circuloPrincipal.position = CGPoint(x: frame.width / 2, y: frame.height / 2)
circuloPrincipal.color = colorAzul
circuloPrincipal.colorBlendFactor = 1.0
circuloPrincipal.name = "circuloPrincipal"
circuloPrincipal.zPosition = 1.0
self.addChild(circuloPrincipal)
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
enemigoTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("enemigos"), userInfo: nil, repeats: true)
}
func enemigos() {
let enemigo = SKSpriteNode(imageNamed: "enemigo")
enemigo.size = CGSize(width: 25, height: 25)
enemigo.zPosition = 2.0
enemigo.name = "enemigo"
let posisionRandom = arc4random() % 4
switch posisionRandom {
case 0:
enemigo.position.x = 0
let posisionY = arc4random_uniform(UInt32(frame.size.height))
enemigo.position.y = CGFloat(posisionY)
self.addChild(enemigo)
break
case 1:
enemigo.position.y = 0
let posisionX = arc4random_uniform(UInt32(frame.size.width))
enemigo.position.x = CGFloat(posisionX)
self.addChild(enemigo)
break
case 2:
enemigo.position.y = frame.size.height
let posisionX = arc4random_uniform(UInt32(frame.size.width))
enemigo.position.x = CGFloat(posisionX)
self.addChild(enemigo)
break
case 3:
enemigo.position.x = frame.size.width
let posisionY = arc4random_uniform(UInt32(frame.size.height))
enemigo.position.y = CGFloat(posisionY)
self.addChild(enemigo)
break
default:
break
}
enemigo.runAction(SKAction.moveTo(circuloPrincipal.position, duration: 1.4))
}
Try to add this code:
let randomY = CGFloat(Int.random(-Int(circuloPrincipal.frame.height/2)...Int(circuloPrincipal.frame.height/2)))
let randomX = CGFloat(Int.random(-Int(circuloPrincipal.frame.width/2)...Int(circuloPrincipal.frame.width/2)))
let slopeToCirculoPrincipal = (enemigo.position.y - circuloPrincipal.position.y + randomY ) / (enemigo.position.x - circuloPrincipal.position.x + randomX)
let constant = enemigo.position.y - slopeToCirculoPrincipal * enemigo.position.x
let finalX : CGFloat = enemigo.position.x < circuloPrincipal.position.x ? 1500.0 : -1500.0 // Set it to somewhere outside screen size
let finalY = constant + slopeToCirculoPrincipal * finalX
let distance = (enemigo.position.y - finalY) * (enemigo.position.y - finalY) + (enemigo.position.x - finalX) * (enemigo.position.x - finalX)
let enemigoSpeed : CGFloat = 100.0
let timeToCoverDistance = sqrt(distance) / enemigoSpeed
let moveAction = SKAction.moveTo(CGPointMake(finalX, finalY), duration: NSTimeInterval(timeToCoverDistance))
let removeAction = SKAction.runBlock { () -> Void in
enemigo.removeFromParent()
}
enemigo.runAction(SKAction.sequence([moveAction,removeAction]))
Instead of:
enemigo.runAction(SKAction.moveTo(circuloPrincipal.position, duration: 1.4))
Also you need to put this extension somewhere in your project:
extension Int
{
static func random(range: Range<Int> ) -> Int
{
var offset = 0
if range.startIndex < 0 // allow negative ranges
{
offset = abs(range.startIndex)
}
let mini = UInt32(range.startIndex + offset)
let maxi = UInt32(range.endIndex + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}

spritekit collision detection inconsistency (failing to get position of both nodes)

Within didBeginContact function I'm successfully capturing the collision between a horizontal grid line and a circle.
At the collision I can successfully get the position of the circle(head).
if let headBody = contact.bodyB.node as? SnakeBodyUnit {
print("head position ", headBody.position)
}
This successfully prints out:
head position (87.536979675293, 267.116882324219)
Now if I try to examine the horizontal line:
if let tripRow = contact.bodyA.node as? HeadTripper {
print("row position ", tripRow.position)
}
I get the following error:
po tripRow
error: :1:1: error: use of unresolved identifier 'tripRow'
tripRow
Here is the condition where this detection fires:
if (contact.bodyA.categoryBitMask == ColliderType.TripRow && contact.bodyB.categoryBitMask == ColliderType.Head) {
//contact successfully fires
}
The HeadTripper class and the SnakeBodyUnit class are both SKNode classes.
Here is the SnakeBodyUnit class:
import SpriteKit
class SnakeBodyUnit : SKNode {
var bodyDir: Direction?
var id = -1
var staticIDref = -1
var partX:CGFloat = -1
var partY:CGFloat = -1
var bodyT = -1
var staticTurn: TurnCrumb?
var turnRequested = 0
var requestedTurn: Direction?
var unitHolder: SKSpriteNode?
init(size: CGSize, bodyType: Int) {
super.init()
bodyT = bodyType
let reducedSize = size.width
unitHolder = SKSpriteNode()
unitHolder!.size = CGSize(width: (size.width), height: (size.height))
if (bodyT == 0) {
//head
unitHolder!.color = UIColor.clearColor()
//unitHolder!.position = CGPoint(x:xPos, y:yPos);
let shape = SKShapeNode(circleOfRadius: reducedSize/2)
shape.fillColor = UIColor.whiteColor()
shape.strokeColor = UIColor.whiteColor()
shape.position = CGPoint(x: reducedSize/2, y: reducedSize/2)
unitHolder!.addChild(shape)
self.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(reducedSize, reducedSize), center: CGPointMake(reducedSize/2, reducedSize/2))
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Head
self.physicsBody?.dynamic = false
self.physicsBody?.contactTestBitMask = ColliderType.TripRow | ColliderType.TripColumn | ColliderType.Food | ColliderType.Body | ColliderType.WallLeft | ColliderType.WallRight | ColliderType.WallBottom | ColliderType.WallTop | ColliderType.Canvas
} else if (bodyT == 1) {
//body part
self.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(reducedSize, reducedSize), center: CGPointMake(reducedSize/2, reducedSize/2))
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Body
self.physicsBody!.collisionBitMask = 0
self.physicsBody?.dynamic = true
self.physicsBody?.contactTestBitMask = ColliderType.WallLeft | ColliderType.WallRight | ColliderType.WallBottom | ColliderType.WallTop | ColliderType.BodyStatic
} else if (bodyT == 2) {
//static corner
self.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(reducedSize/2, reducedSize/2), center: CGPointMake(reducedSize/2, reducedSize/2))
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.BodyStatic
self.physicsBody!.collisionBitMask = 0
self.physicsBody?.dynamic = true
}
unitHolder!.anchorPoint = CGPoint(x: 0, y: 0)
self.addChild(unitHolder!)
}
func updateColor(color: UIColor) {
unitHolder!.color = color
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//fatalError("init coder not implemented")
}
}
Here is the HeadTripper class:
import SpriteKit
class HeadTripper : SKNode {
var id = -1
var rowTripLine: SKSpriteNode?
var colTripLine: SKSpriteNode?
init(size: CGSize, rowOrCol: Int) {
//init(size: CGSize, xPos: Double, yPos: Double, rowOrCol: Int) {
super.init()
//row 0
//col 1
if (rowOrCol == 0) {
//row
rowTripLine = SKSpriteNode()
rowTripLine!.color = UIColor.redColor()
//rowTripLine!.position = CGPoint(x: 0, y: yPos);
rowTripLine!.size = CGSize(width: Int(size.width), height: 1)
//borderLeft!.physicsBody = SKPhysicsBody(rectangleOfSize: size, center: CGPointMake(reducedSize/2, reducedSize/2))
rowTripLine!.physicsBody = SKPhysicsBody(rectangleOfSize: rowTripLine!.size, center: CGPointMake(rowTripLine!.size.width/2, rowTripLine!.size.height/2))
rowTripLine!.physicsBody?.affectedByGravity = false
rowTripLine!.physicsBody?.categoryBitMask = ColliderType.TripRow
rowTripLine!.physicsBody!.collisionBitMask = 0
rowTripLine!.physicsBody?.dynamic = true
rowTripLine!.anchorPoint = CGPoint(x: 0, y: 0)
self.addChild(rowTripLine!)
} else {
//col
colTripLine = SKSpriteNode()
colTripLine!.color = UIColor.redColor()
//colTripLine!.position = CGPoint(x: xPos, y: 0);
colTripLine!.size = CGSize(width: 1, height: Int(size.height))
//borderLeft!.physicsBody = SKPhysicsBody(rectangleOfSize: size, center: CGPointMake(reducedSize/2, reducedSize/2))
colTripLine!.physicsBody = SKPhysicsBody(rectangleOfSize: colTripLine!.size, center: CGPointMake(colTripLine!.size.width/2, colTripLine!.size.height/2))
colTripLine!.physicsBody?.affectedByGravity = false
colTripLine!.physicsBody?.categoryBitMask = ColliderType.TripColumn
colTripLine!.physicsBody!.collisionBitMask = 0
colTripLine!.physicsBody?.dynamic = true
colTripLine!.anchorPoint = CGPoint(x: 0, y: 0)
self.addChild(colTripLine!)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init coder not implemented")
}
}
I generate the HeadTripper lines like this in the main scene:
for var i = 0; i < rowCount; i++ {
let rowTrip = HeadTripper(size: size, rowOrCol: 0)
rowTrip.id = i
rowTrip.position.x = 0
rowTrip.position.y = CGFloat(yVal)
self.addChild(rowTrip)
rowLines.append(CGFloat(rowTrip.position.y))
CGPathMoveToPoint(linePath, nil, 0, CGFloat(yVal))
CGPathAddLineToPoint(linePath, nil, CGFloat(theGrid.screenWidth), CGFloat(yVal))
yVal += (theGrid.snakeHigh + yValPad)
}
Here is how the collider types are setup:
struct ColliderType {
static let Head: UInt32 = 0
static let Food: UInt32 = 0b1
static let Body: UInt32 = 0b10
static let BodyStatic: UInt32 = 0b100
static let WallLeft: UInt32 = 0b1000
static let WallBottom: UInt32 = 0b10000
static let WallRight: UInt32 = 0b100000
static let WallTop: UInt32 = 0b1000000
static let None: UInt32 = 0b10000000
static let Canvas: UInt32 = 0b100000000
static let TripColumn: UInt32 = 0b1000000000
static let TripRow: UInt32 = 0b10000000000
}
The tripRow let setup happens on line 310. On line 309 I can see the contact.bodyA:
0x000000012758db20
{
NSObject = {
isa = 0x000000012758db20
}
_representedObject = 0x00000001275765a0
_field = 0x0000000000000000
_dynamicType = 2
_world = 0x0000000127761b90
_joints = 0x0000000127576270 "0 values"
_inUse = true
_shapeType = 2
_radius = 0
_edgeRadius = 0.0010000000474974513
_mask = 0x0000000000000000
_isPinned = false
_allowsRotation = true
_postStepBlock = 0x000000019e666c70
}
I can also see the contact.bodyA.node:
0x00000001275765a0
{
UIKit.UIResponder = {...}
}
Line 311 never gets called because its in the if condition that isn't passing.
At line 313 I attempt to look at tripRow and get this:
(lldb) po tripRow
error: <EXPR>:1:1: error: use of unresolved identifier 'tripRow'
tripRow
It sounds like issue is related to the children within HeadTripper. I have named the children "row" and "col" respectively.
print("body A name ", contact.bodyA.node?.name)
let tripRow = contact.bodyA.node as? SKSpriteNode
This gives a name of "row". Also tripRow now prints out as:
(lldb) po tripRow
0x0000000155e737c0
{
SpriteKit.SKNode = {...}
}
I think this is very close. The problem still exists that the position is not accurate. The y value should be a number like 54 and it is zero:
(lldb) po tripRow?.position
(x = 0, y = 0)
This was resolved by having just 1 SKSpriteNode referenced in the HeadTripper SKNode class instead of 2.

SKAction not working as expected

Hi I have a sequence of action that run forever to generate obstacles in a side scrolling game. But instead of generating random pictures and heights from my textures it keeps the same ones it generated the first time it ran the SKActions...
Can someone solve this? I am trying not to use
NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: Selector("setUpMountains"), userInfo: nil, repeats: true)
As it lowers my FPS for some reason
func setUpMountains() {
let gapHeight = user.size.height * 3
var movementAmount = arc4random() % UInt32(self.frame.size.height / 3.0)
var randomNumber = arc4random() % 3
if randomNumber == 0 {
mountainTexture = SKTexture(imageNamed:"RedMountain.png")
} else if randomNumber == 1 {
mountainTexture = SKTexture(imageNamed:"OrangeMountain.png")
} else {
mountainTexture = SKTexture(imageNamed:"BeigeMountain.png")
}
var randomMountain = SKSpriteNode(texture: mountainTexture)
randomMountain.position = CGPointMake( CGRectGetMidX(self.frame) + self.frame.width, CGRectGetMidY(self.frame) - gapHeight - CGFloat(movementAmount))
randomMountain.zPosition = 8
randomMountain.physicsBody = SKPhysicsBody(texture: mountainTexture, size: mountainTexture.size())
randomMountain.physicsBody?.dynamic = false
// randomMountain.physicsBody?.categoryBitMask = objectCategory
movingObjects.addChild(randomMountain)
//spawning mountains
var distanceToMove = self.frame.size.width + mountainTexture.size().width
var moveMountain = SKAction.moveByX(-distanceToMove, y: 0, duration: NSTimeInterval (distanceToMove * 0.01))
var replaceMountain = SKAction.moveByX(distanceToMove, y: 0, duration: 0)
//var removeMountain = SKAction.removeFromParent()
var moveAndRemoveMountain = SKAction.repeatActionForever(SKAction.sequence([moveMountain, replaceMountain]))
randomMountain.runAction(moveAndRemoveMountain)
}
Instead of loading the textures each time, cache the textures once in an array, for e.g. mountains in the following code.
Use an SKAction sequence with a wait duration instead of an NSTimer to repeatedly execute functions.
You don't need the repeatActionForever action in the setup mountains since the generated obstacles are removed once they go out of the screen using removeParent.
var mountains : [SKTexture] = []
override func didMoveToView(view: SKView) {
mountains = [SKTexture(imageNamed:"RedMountain.png"),SKTexture(imageNamed:"OrangeMountain.png"),SKTexture(imageNamed:"BeigeMountain.png")]
let generateMountains = SKAction.runBlock { () -> Void in
self.setUpMountains()
}
let generateObstaclesPeriodically = SKAction.repeatActionForever (SKAction.sequence([generateMountains,SKAction.waitForDuration(4.0)]))
self.runAction(generateObstaclesPeriodically)
}
func setUpMountains() {
let gapHeight = user.size.height * 3
var movementAmount = arc4random() % UInt32(self.frame.size.height / 3.0)
var randomNumber = arc4random() % 3
let mountainTexture = mountains[randomNumber]
var randomMountain = SKSpriteNode(texture: mountainTexture)
randomMountain.position = CGPointMake( CGRectGetMidX(self.frame) + self.frame.width, CGRectGetMidY(self.frame) - gapHeight - CGFloat(movementAmount))
randomMountain.zPosition = 8
randomMountain.physicsBody = SKPhysicsBody(texture: mountainTexture, size: mountainTexture.size())
randomMountain.physicsBody?.dynamic = false
movingObjects.addChild(randomMountain)
//spawning mountains
var distanceToMove = self.frame.size.width + mountainTexture.size().width
var moveMountain = SKAction.moveByX(-distanceToMove, y: 0, duration: NSTimeInterval (distanceToMove * 0.01))
var removeMountain = SKAction.removeFromParent()
var moveAndRemoveMountain = SKAction.sequence([moveMountain, removeMountain])
randomMountain.runAction(moveAndRemoveMountain)
}
To remove an SKAction you can give a key to the SKAction while adding it and use the key to remove it from the node.
self.runAction(generateObstaclesPeriodically, withKey: "generateObstaclesPeriodically")
self.removeActionForKey("generateObstaclesPeriodically")
#rakeshbs thanks a lot that seems to work but i changed it to this instead
mountains = [SKTexture(imageNamed:"RedMountain.png"),SKTexture(imageNamed:"OrangeMountain.png"),SKTexture(imageNamed:"BeigeMountain.png")]
let generateMountains = SKAction.runBlock { () -> Void in
self.setUpMountains()
}
let generateObstaclesPeriodically = SKAction.repeatActionForever(SKAction.sequence([generateMountains,SKAction.waitForDuration(4.0)]))
self.runAction(generateObstaclesPeriodically)