Search code examples
swiftsprite-kitskspritenode

Performance issues creating set of nodes


I'm making a game and trying to add a set of obstacles in one call.

I'm making two nodes and inside theses nodes creating an obstacle 14 to 18 times, and if theses obstacles are outside of the scene to not even add them.

All my textures are inside an sprite.atlas and I already called preload at didMove. The problem is that whenever I called the function above the scene drops to 58fps and this makes the game looks like is lag. I really do not know what to do to make this code works better.

Maybe seeing the code makes more sense on what I'm trying to do.

  func createMeteor() {
    rightMeteors = SKSpriteNode(color: UIColor.clear, size: CGSize(width: 400, height: 50))
    leftMeteors = SKSpriteNode(color: UIColor.clear, size: CGSize(width: 400, height: 50))
    rightMeteors.name = "MeteorPairs"
    leftMeteors.name = "MeteorPairs"

    let meteorsDistance: CGFloat = 90
    let maxX = scene!.size.width/2 + 150
    let minX = -scene!.size.width/2 + meteorsDistance + 250
    let xPosition = round(CGFloat.random(in: minX ... maxX))

    rightMeteors.position = CGPoint(x: xPosition, y: scene!.size.height/2 + 50)
    rightMeteors.zPosition = 2
    leftMeteors.position = CGPoint(x: xPosition - 400 - meteorsDistance, y: scene!.size.height/2 + 50)
    leftMeteors.zPosition = 2

    addChild(rightMeteors)
    addChild(leftMeteors)

    let randomNumberMeteors = Int.random(in: 14...18)

    for _ in 0...randomNumberMeteors {
        let numberGenerator = GKShuffledDistribution(lowestValue: 1, highestValue: 12)
        let randomMeteorNumber = numberGenerator.nextInt()
        let meteorTexture = meteorAtlas.textureNamed("Meteor_\(randomMeteorNumber)")

        meteor = SKSpriteNode(texture: meteorTexture)
        meteor.setScale(0.6)

        let xMin = meteor.size.width/2 - 200
        let xMax = 200 - meteor.size.width/2
        let xRange = xMax - xMin
        let xRandom = xMax - CGFloat(arc4random_uniform(UInt32(xRange)))
        meteor.position = CGPoint(x: xRandom, y: 0)

        var body = SKPhysicsBody()
        switch randomMeteorNumber {
        case 1:
            body = SKPhysicsBody(texture: meteorTexture, size: meteor.size)
        case 2:
            body = SKPhysicsBody(texture: meteorTexture, size: meteor.size)
        case 3:
            body = SKPhysicsBody(circleOfRadius: 17)
        case 4:
            body = SKPhysicsBody(circleOfRadius: 18)
        case 5:
            body = SKPhysicsBody(circleOfRadius: 8)
        case 6:
            body = SKPhysicsBody(circleOfRadius: 8)
        case 7:
            body = SKPhysicsBody(circleOfRadius: 5)
        case 8:
            body = SKPhysicsBody(circleOfRadius: 5)
        case 9:
            body = SKPhysicsBody(circleOfRadius: 3)
        case 10:
            body = SKPhysicsBody(circleOfRadius: 2)
        default:
            break
        }

        body.affectedByGravity = false
        body.categoryBitMask = obstacleCategory
        body.contactTestBitMask = shipCategory
        body.collisionBitMask = 0

        meteor.physicsBody = body
        if rightMeteors.convert(meteor.position, to: scene!).x < scene!.size.width/2  {
            rightMeteors.addChild(meteor)
        }

        let random = CGFloat.random(in: -0.1 ... 0.1)
        let xMove = SKAction.moveBy(x: random, y: 0, duration: 0.03)
        meteor.run(SKAction.repeatForever(xMove))
    }

    for _ in 0...randomNumberMeteors {
        let numberGenerator = GKShuffledDistribution(lowestValue: 1, highestValue: 12)
        let randomMeteorNumber = numberGenerator.nextInt()
        let meteorTexture = meteorAtlas.textureNamed("Meteor_\(randomMeteorNumber)")

        meteor = SKSpriteNode(texture: meteorTexture)
        meteor.setScale(0.6)

        let xMin = meteor.size.width/2 - 200
        let xMax = 200 - meteor.size.width/2
        let xRange = xMax - xMin
        let xRandom = xMax - CGFloat(arc4random_uniform(UInt32(xRange)))
        meteor.position = CGPoint(x: xRandom, y: 0)

        var body = SKPhysicsBody()
        switch randomMeteorNumber {
        case 1:
            body = SKPhysicsBody(texture: meteorTexture, size: meteor.size)
        case 2:
            body = SKPhysicsBody(texture: meteorTexture, size: meteor.size)
        case 3:
            body = SKPhysicsBody(circleOfRadius: 17)
        case 4:
            body = SKPhysicsBody(circleOfRadius: 18)
        case 5:
            body = SKPhysicsBody(circleOfRadius: 8)
        case 6:
            body = SKPhysicsBody(circleOfRadius: 8)
        case 7:
            body = SKPhysicsBody(circleOfRadius: 5)
        case 8:
            body = SKPhysicsBody(circleOfRadius: 5)
        case 9:
            body = SKPhysicsBody(circleOfRadius: 3)
        case 10:
            body = SKPhysicsBody(circleOfRadius: 2)
        default:
            break
        }

        body.affectedByGravity = false
        body.categoryBitMask = obstacleCategory
        body.contactTestBitMask = shipCategory
        body.collisionBitMask = 0

        meteor.physicsBody = body
        if leftMeteors.convert(meteor.position, to: scene!).x > -scene!.size.width/2  {
            leftMeteors.addChild(meteor)
        }

        let random = CGFloat.random(in: -0.1 ... 0.1)
        let xMove = SKAction.moveBy(x: random, y: 0, duration: 0.03)
        meteor.run(SKAction.repeatForever(xMove))
    }

    let moveDown = SKAction.moveBy(x: 0, y: -scene!.size.height - 100, duration: 2.5)

    leftMeteors.run(SKAction.sequence([moveDown, SKAction.removeFromParent()]))
    rightMeteors.run(SKAction.sequence([moveDown, SKAction.removeFromParent()]))
}

SOLVED: I know it is not a clean code but I'm working on that aspect idk why I feel so comfortable doing it like that. Thanks for your guide I manage to set the fps to normal whenever the function is called:

var meteorsArray = [SKSpriteNode]()

func createMeteorsTemplate() {
    for i in 1 ... 12 {
        let texture = meteorAtlas.textureNamed("Meteor_\(i)")
        let newMeteor = SKSpriteNode(texture: texture)
        newMeteor.setScale(0.6)

        switch i {
        case 1:
            newMeteor.physicsBody = SKPhysicsBody(texture: texture, size: newMeteor.size)
        case 2:
            newMeteor.physicsBody = SKPhysicsBody(texture: texture, size: newMeteor.size)
        case 3:
            newMeteor.physicsBody = SKPhysicsBody(texture: texture, size: newMeteor.size)
        case 4:
            newMeteor.physicsBody = SKPhysicsBody(circleOfRadius: 18)
        case 5:
            newMeteor.physicsBody = SKPhysicsBody(circleOfRadius: 23)
        case 6:
            newMeteor.physicsBody = SKPhysicsBody(circleOfRadius: 8)
        case 7:
            newMeteor.physicsBody = SKPhysicsBody(circleOfRadius: 8)
        case 8:
            newMeteor.physicsBody = SKPhysicsBody(circleOfRadius: 5)
        case 9:
            newMeteor.physicsBody = SKPhysicsBody(circleOfRadius: 5)
        case 10:
            newMeteor.physicsBody = SKPhysicsBody(circleOfRadius: 5)
        case 11:
            newMeteor.physicsBody = SKPhysicsBody(circleOfRadius: 3)
        case 12:
            newMeteor.physicsBody = SKPhysicsBody(circleOfRadius: 10)
        default:
            break
        }

        newMeteor.physicsBody?.affectedByGravity = false
        newMeteor.physicsBody?.categoryBitMask = obstacleCategory
        newMeteor.physicsBody?.contactTestBitMask = shipCategory
        newMeteor.physicsBody?.collisionBitMask = 0

        meteorsArray.append(newMeteor)
    }

}
func createMeteor() {
    let rightMeteors = SKNode()
    let leftMeteors = SKNode()
    rightMeteors.name = "MeteorPairs"
    leftMeteors.name = "MeteorPairs"

    let meteorsDistance: CGFloat = 90
    let maxX = scene!.size.width/2 + 150
    let minX = -scene!.size.width/2 + meteorsDistance + 250
    let xPosition = round(CGFloat.random(in: minX ... maxX))

    rightMeteors.position = CGPoint(x: xPosition, y: scene!.size.height/2 + 50)
    leftMeteors.position = CGPoint(x: xPosition - 400 - meteorsDistance, y: scene!.size.height/2 + 50)

    rightMeteors.zPosition = 2
    leftMeteors.zPosition = 2

    addChild(rightMeteors)
    addChild(leftMeteors)

    for meteorSide in 0 ... 1 {
        let randomNumberOfMeteors = GKShuffledDistribution(lowestValue: 14, highestValue: 18).nextInt()
        for _ in 0 ... randomNumberOfMeteors {
            let randomMeteorNumber = GKShuffledDistribution(lowestValue: 1, highestValue: 12).nextInt()
            let meteorTemplate = meteorsArray[randomMeteorNumber-1]
            let meteor = meteorTemplate.copy() as! SKSpriteNode
            let xMin = meteor.size.width/2 - 200
            let xMax = 200 - meteor.size.width/2
            let xRange = xMax - xMin
            let xRandom = xMax - CGFloat(arc4random_uniform(UInt32(xRange)))
            meteor.position = CGPoint(x: xRandom, y: 0)

            switch meteorSide {
            case 0:
                if rightMeteors.convert(meteor.position, to: scene!).x < scene!.size.width/2  {
                    rightMeteors.addChild(meteor)
                }
            case 1:
                if leftMeteors.convert(meteor.position, to: scene!).x > -scene!.size.width/2  {
                    leftMeteors.addChild(meteor)
                }
            default:
                break
            }
        }
    }

    let moveDown = SKAction.moveBy(x: 0, y: -scene!.size.height - 100, duration: 2.5)
    leftMeteors.run(SKAction.sequence([moveDown, SKAction.removeFromParent()]))
    rightMeteors.run(SKAction.sequence([moveDown, SKAction.removeFromParent()]))
}

Solution

  • Give this a try, let me know what fails you. Basically, I eliminated the process of building new nodes and physics bodies, and cleaned up your code so that we are not double looping

    let meteorsDistance: CGFloat = 90
    let maxX = scene!.size.width/2 + 150
    let minX = -scene!.size.width/2 + meteorsDistance + 250
    let meteors = [SKSpriteNode](repeating: SKSpriteNode(), count: 13)
    func createMeteorTemplate(){ //Call on init
        for meteorNumber in 1...12 {
            let meteorTexture = meteorAtlas.textureNamed("Meteor_\(meteorNumber)")
            let meteor = SKSpriteNode(texture: meteorTexture)
            meteor.setScale(0.6)
            var body : SKPhysicsBody!
    
            switch meteorNumber {
            case 1:
                body = SKPhysicsBody(texture: meteorTexture, size: meteor.size)
            case 2:
                body = SKPhysicsBody(texture: meteorTexture, size: meteor.size)
            case 3:
                body = SKPhysicsBody(circleOfRadius: 17)
            case 4:
                body = SKPhysicsBody(circleOfRadius: 18)
            case 5:
                body = SKPhysicsBody(circleOfRadius: 8)
            case 6:
                body = SKPhysicsBody(circleOfRadius: 8)
            case 7:
                body = SKPhysicsBody(circleOfRadius: 5)
            case 8:
                body = SKPhysicsBody(circleOfRadius: 5)
            case 9:
                body = SKPhysicsBody(circleOfRadius: 3)
            case 10:
                body = SKPhysicsBody(circleOfRadius: 2)
            default:
                break
            }
            body.affectedByGravity = false
            body.categoryBitMask = obstacleCategory
            body.contactTestBitMask = shipCategory
            body.collisionBitMask = 0
            meteor.physicsBody = body
            meteors[i] = meteor 
       }
    }
    
    func createMeteor() {
        rightMeteors = SKNode()
        leftMeteors = SKNode()
        rightMeteors.name = "MeteorPairs"
        leftMeteors.name = "MeteorPairs"
    
        let xPosition = round(CGFloat.random(in: minX ... maxX))
    
        rightMeteors.position = CGPoint(x: xPosition, y: scene!.size.height/2 + 50)
        rightMeteors.zPosition = 2
        leftMeteors.position = CGPoint(x: xPosition - 400 - meteorsDistance, y: scene!.size.height/2 + 50)
        leftMeteors.zPosition = 2
    
        addChild(rightMeteors)
        addChild(leftMeteors)
    
        let randomNumberMeteors = Int.random(in: 14...18)
        let numberGenerator = GKShuffledDistribution(lowestValue: 1, highestValue: 12)
    
    
        func createNewMeteor(numberGenerator : GKShuffleDistribution) -> SKSpriteNode
        {
            let meteorTemplate = meteors[randomMeteorNumber]
            let meteor = meteorsTemplate.copy()
            meteor.physicsBody = meteorTemplate.physicsBody.copy()
            let xMin = meteor.size.width/2 - 200
            let xMax = 200 - meteor.size.width/2
            let xRange = xMax - xMin
            let xRandom = xMax - CGFloat(arc4random_uniform(UInt32(xRange)))
            meteor.position = CGPoint(x: xRandom, y: 0)
            let random = CGFloat.random(in: -0.1 ... 0.1)
            let xMove = SKAction.moveBy(x: random, y: 0, duration: 0.03)
            meteor.run(SKAction.repeatForever(xMove))
    
            return meteor
        }
    
        for _ in 0...(randomNumberMeteors * 2 + 1) {
            let meteor = createNewMeteor(numberGenerator)
            if meteor.position.x < scene!.size.width/2  {
                rightMeteors.addChild(meteor)
            else
                leftMeteors.addChild(meteor)
            }
        }
    
    
    
        let moveDown = SKAction.moveBy(x: 0, y: -scene!.size.height - 100, duration: 2.5)
    
        leftMeteors.run(SKAction.sequence([moveDown, SKAction.removeFromParent()]))
        rightMeteors.run(SKAction.sequence([moveDown, SKAction.removeFromParent()]))
    }