Search code examples
arraysswiftsprite-kitcollision-detectionskphysicsbody

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.runAction(actionZero)
    self.coinZero.position = spawnLocationZero
    self.addChild(self.coinZero)
    print(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
        self.addChild(self.coinTwo)

        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.runAction(actionTwo)
        self.coinTwo.position = spawnLocationTwo
        self.addChild(self.coinTwo)
        print(spawnLocationTwo)
    }

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

enter image description here


Solution

  • 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
            addChild(coin1)
        } else if coin2.parent == nil {
            coin2.position = position
            addChild(coin2)
        }
    }
    

    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])
    runAction(SKAction.repeatActionForever(spawnSequence))