Search code examples
iosswiftxcodesprite-kitskphysicsbody

How to set physicsBody on spawning spritekitnodes


I have problem with physics body. I have about 20 nodes spawning randomly to view. Every time the node spawns I set the object's physics, like this:

func showObject(){
    let texture =  SKTexture(imageNamed: "A.png")
    object = SKSpriteNode(texture: texture)
        object.name = "A"
    object.position = CGPoint(x: 0, y: self.frame.width)
    object.setScale(0.7)
    object.zPosition = 2
    object.run(moveAndRemove)

    object.physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
    object.physicsBody?.categoryBitMask = PhysicsCategory.Object
    object.physicsBody?.collisionBitMask = PhysicsCategory.Object2
    object.physicsBody?.contactTestBitMask = PhysicsCategory.Object2
    object.physicsBody?.affectedByGravity = false
    object.physicsBody?.isDynamic = true

    addChild(object)
}

I think this is not optimal, because physicsBody is set everytime when node is spawned. Because of this I have sometimes little flickering - lower fps when app running. When is physicsBody turned off, everything is ok. So I have a simple question. How to set physics body for all 20 nodes after games started and than just spawn them without creating physicsBody again. I try to set physics body with texture straight to SKView but after this, app crash with nil error.

Thanks for any tip.

There is my spawn:

let SpawnObject = SKAction.run({
    () in

    let randomFunc = [self.showObject, self.showObject1.......]
        let randomResult = Int(arc4random_uniform(UInt32(randomFunc.count)))
        randomFunc[randomResult]()
})

let delay1 = SKAction.wait(forDuration: 0.9)
let SpawnDelay1 = SKAction.sequence([SpawnObject,delay1])
let SpawnDelayForever1 = SKAction.repeatForever(SpawnDelay1)
self.run(SpawnDelayForever1)

let distance = CGFloat(self.frame.height + 200)
let moveObject = SKAction.moveBy(x: -distance, y: 0, duration: TimeInterval(0.004 * distance))
let removeObject = SKAction.removeFromParent()
moveAndRemove = SKAction.sequence([moveObject,removeObject])

Solution

  • There is many different ways you could go about this, what I like to do if I am spawning multiple of the same object is preload the objects into an array and the pull them from the array as I need them, when you are done with the object you can remove them from the scene but keep them in the array. You can then use that object again without having to recreate it and without having to recreate the physics body causing the lag

    private var moveAndRemove = SKAction()
    private var objects = [SKSpriteNode]()
    private let objectCount = 20
    
    override func didMove(to view: SKView) {
    
        for x in 0..<objectCount {
            let texture =  SKTexture(imageNamed: "A")
            let object = SKSpriteNode(texture: texture)
            object.name = "A"
            object.isHidden = true
            object.setScale(0.7)
            object.zPosition = 2
    
            object.physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
            object.physicsBody?.categoryBitMask = 0
            object.physicsBody?.collisionBitMask = 0
            object.physicsBody?.contactTestBitMask = 0
            object.physicsBody?.affectedByGravity = false
            object.physicsBody?.isDynamic = true
    
            objects.append(object)
        }
    
        let distance = CGFloat(self.frame.height + 200)
        let moveObject = SKAction.moveBy(x:0 - distance, y: 0, duration: Double(0.004 * distance))
        let removeObject = SKAction.removeFromParent()
        let hideObject = SKAction.hide()
        moveAndRemove = SKAction.sequence([moveObject, hideObject, removeObject])
    
        let SpawnObject = SKAction.run( {
    
            let randomResult = Int(arc4random_uniform(UInt32(self.objects.count)))
            self.showObject(objectIndex: randomResult)
        })
    
        let delay1 = SKAction.wait(forDuration: 0.9)
        let SpawnDelay1 = SKAction.sequence([SpawnObject, delay1])
        let SpawnDelayForever1 = SKAction.repeatForever(SpawnDelay1)
        self.run(SpawnDelayForever1)
    }
    
    func showObject(objectIndex: Int) {
    
        let object = objects[objectIndex]
    
        guard object.isHidden == true else { return }
    
        object.isHidden = false
        object.position = CGPoint(x: 0, y: self.frame.width)
        object.zPosition = 10000
        addChild(object)
        object.run(moveAndRemove)
    }
    

    I've edited the answer to reflect your spawn code. Please note that I am only checking if the object in the array has already been used by checking the hidden status of the object. There are many other ways to check if the object has been used, but you'll have to figure out what works best for you.