P.Dane P.Dane - 1 year ago 160
Swift Question

Spawning multiple coin node(s) using an array Swift SpriteKit

I use a function (shown below) to spawn a coin node in specific locations at random using an array.

Using this function, I am trying to incorporate more than one coin node (that are slightly different from one another) into this function so that multiple nodes can use this array to spawn and function just like the first coin node.

The problem that I have is that when I incorporate another node into this function or make a new but similar function for the 2nd node I get a Thread 1 SIGABERT error after the game crashes.

I currently have two separate functions for each node that are very similar, but with slight differences to accommodate each node.

func generateCoinZero() {

if(self.actionForKey("spawningCoinZero") != nil){return}
let coinTimerZero = SKAction.waitForDuration(2, withRange: 7)

let spawnCoinZero = SKAction.runBlock {
let coinZeroTexture = SKTexture(imageNamed: "coinZero")

self.coinZero = SKSpriteNode(texture: coinZeroTexture)
self.coinZero.physicsBody = SKPhysicsBody(circleOfRadius: self.coinZero.size.height / 12)
self.coinZero.physicsBody?.dynamic = false
self.coinZero.physicsBody?.allowsRotation = false
self.coinZero.zPosition = 1

self.coinZero.physicsBody?.categoryBitMask = ColliderType.coinZeroCategory
self.coinZero.physicsBody?.contactTestBitMask = ColliderType.playerCategory
self.coinZero.physicsBody?.collisionBitMask = 0

self.player.physicsBody?.categoryBitMask = ColliderType.playerCategory
self.player.physicsBody?.contactTestBitMask = 0
self.player.physicsBody?.collisionBitMask = ColliderType.boundary

var coinPositionZero = Array<CGPoint>()
coinPositionZero.append((CGPoint(x:250, y:139)))
coinPositionZero.append((CGPoint(x:790, y:298)))
coinPositionZero.append((CGPoint(x:225, y:208)))
coinPositionZero.append((CGPoint(x:220, y:237)))

let spawnLocationZero = coinPositionZero[Int(arc4random_uniform(UInt32(coinPositionZero.count)))]
let actionZero = SKAction.repeatActionForever(SKAction.moveToX(+self.xScale, duration: 2.0))

self.coinZero.position = spawnLocationZero

let sequenceZero = SKAction.sequence([coinTimerZero, spawnCoinZero])
self.runAction(SKAction.repeatActionForever(sequenceZero), withKey: "spawningCoinZero")

func generateCoinTwo() {

if(self.actionForKey("spawnCoinTwo") != nil){return}
let coinTimerTwo = SKAction.waitForDuration(2, withRange: 7)

let spawnCoinTwo = SKAction.runBlock {

let coinTwoTexture = SKTexture(imageNamed: "coinTwo")
self.coinTwo = SKSpriteNode(texture: coinTwoTexture)
self.coinTwo.physicsBody = SKPhysicsBody(circleOfRadius: self.coinTwo.size.height / 12)
self.coinTwo.physicsBody?.dynamic = false
self.coinTwo.physicsBody?.allowsRotation = false
self.coinTwo.zPosition = 1

var coinPositionTwo = Array<CGPoint>()
coinPositionTwo.append((CGPoint(x:250, y:139)))
coinPositionTwo.append((CGPoint(x:790, y:298)))
coinPositionTwo.append((CGPoint(x:225, y:208)))
coinPositionTwo.append((CGPoint(x:220, y:237)))

let spawnLocationTwo = coinPositionTwo[Int(arc4random_uniform(UInt32(coinPositionTwo.count)))]
let actionTwo = SKAction.repeatActionForever(SKAction.moveToX(+self.xScale, duration: 2.0))

self.coinTwo.position = spawnLocationTwo

let sequenceTwo = SKAction.sequence([coinTimerTwo, spawnCoinTwo])
self.runAction(SKAction.repeatActionForever(sequenceTwo), withKey: "spawnCoinTwo")

enter image description here

Answer Source

OK, there are quite a lot of issues here, the main ones being the extreme duplication of code and having your generateCoin...-functions doing way too much. So here goes:

You state in the comments that the scene should have two coins spawning at different times at one of four possible positions. If the scene has two coins, then the scene has two coins. Let's just create these as properties and be done with it:

// Your two coin properties 
let coin1 = coinNode()
let coin2 = coinNode()

// the function from which they are created
func coinNode() -> SKSpriteNode {
    let coinNode = SKSpriteNode(imageNamed: "coinZero")
    coinNode.physicsBody = SKPhysicsBody(circleOfRadius: coinNode.size.height / 2)
    coinNode.physicsBody?.dynamic = false
    coinNode.physicsBody?.allowsRotation = false
    coinNode.zPosition = 1

    coinNode.physicsBody?.categoryBitMask = ColliderType.coinZeroCategory
    coinNode.physicsBody?.contactTestBitMask = ColliderType.playerCategory
    coinNode.physicsBody?.collisionBitMask = 0 // A ColliderType.none would be lovely...

    return coinNode

Now, these coins are not yet added to the scene nor do they have a proper position, this sounds like a fitting scope for another function:

func addCoin() {
    let positions = [ CGPoint(x:250, y:139), CGPoint(x:790, y:298), CGPoint(x:225, y:208), CGPoint(x:220, y:237)]
    let position = positions[Int(arc4random_uniform(UInt32(positions.count)))]

    if coin1.parent == nil {
        coin1.position = position
    } else if coin2.parent == nil {
        coin2.position = position

Finally you want to have this function being called so do the following in your scene's init or setup:

let delay = SKAction.waitForDuration(1) // or however long you want it to be between each spawn
let addCoinCall = SKAction.runBlock({ self.addCoin() })
let spawnSequence = SKAction.sequence([delay, addCoinCall])