Animation atlas and dynamic physicsBody in Swift 3
Asked Answered
M

4

9

I did a small test project using the "Hello World" Sprite-kit template where there is an atlas animation composed by these frames: enter image description here

-

I want to show this knight and it's animation.

I want to set a DYNAMIC physics body.

So I've used a tool to separated single frames and I did an atlasc folder so the code should be:

import SpriteKit
class GameScene: SKScene {
    var knight: SKSpriteNode!
    var textures : [SKTexture] = [SKTexture]()
    override func didMove(to view: SKView) {
        self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
        let plist = "knight.plist"
        let genericAtlas = SKTextureAtlas(named:plist)
        let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
        for i in 0 ..< genericAtlas.textureNames.count
        {
            let textureName = (String(format:"%@%02d",filename,i))
            textures.append(genericAtlas.textureNamed(textureName))
        }
        if textures.count>0 {
            knight = SKSpriteNode(texture:textures.first)
            knight.zPosition = 2
            addChild(knight)
            knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
        }
        //
        self.setPhysics()
        let animation = SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false)
        knight.run(animation, withKey:"knight")
    }
    func setPhysics() {
        knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.size)
        knight.physicsBody?.isDynamic = false
    }
}

The output is:

enter image description here

As you can see, the physicsBody is STATIC, don't respect the animation: this is normal because during the animation the texture change dimension / size and we don't change the physicsBody that remain the same during the action.

Following the sources there aren't methods that , during SKAction.animate, allow to change the physicsBody.

Although we use :

/**
Creates an compound body that is the union of the bodies used to create it.
*/
public /*not inherited*/ init(bodies: [SKPhysicsBody])

to create bodies for each frame of our animation, these bodies remain all together in the scene creating an ugly bizarre situation like this pic:

enter image description here


So, the correct way to do it should be to intercept frames during animation and change physicsBody on the fly. We can use also the update() method from SKScene, but I was thinking about an extension.

My idea is to combine the animation action with a SKAction.group, making another custom action that check the execution of the first action, intercept frames that match the current knight.texture with the textures array and change the physicsBody launching an external method, in this case setPhysicsBody.

Then, I've write this one:

extension SKAction {
    class func animateWithDynamicPhysicsBody(animate:SKAction, key:String, textures:[SKTexture], duration: TimeInterval, launchMethod: @escaping ()->()) ->SKAction {
        let interceptor = SKAction.customAction(withDuration: duration) { node, _ in
            if node is SKSpriteNode {
                let n = node as! SKSpriteNode
                guard n.action(forKey: key) != nil else { return }
                if textures.contains(n.texture!) {
                    let frameNum = textures.index(of: n.texture!)
                    print("frame number: \(frameNum)")
                    // Launch a method to change physicBody or do other things with frameNum
                    launchMethod()
                }
            }
        }
        return SKAction.group([animate,interceptor])
    }
}

Adding this extension, we change the animation part of the code with:

 //
        self.setPhysics()
        let animation = SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false)
        let interceptor = SKAction.animateWithDynamicPhysicsBody(animate: animation, key: "knight", textures: textures, duration: 60.0, launchMethod: self.setPhysics)
        knight.run(interceptor,withKey:"knight")
    }
    func setPhysics() {
        knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.size)
        knight.physicsBody?.isDynamic = false
    }

This finally works, the output is:

enter image description here

Do you know a better way, or a more elegant method to obtain this result?

Mounts answered 22/12, 2016 at 21:51 Comment(2)
Since you are just doing boxed bodies, attach a child node, attach the physics body to the child node and scale the child node to the size of your character. If I have time I will write a quick exampleTurley
@Turley Thanks for you suggestion, oh well I am waiting for it..Mounts
M
3

Solution for isDynamic = false.

*Update to 2017 below

After days of test maded with my answer, Knight0fDragon answer's and some other ideas came from other SO answers (Confused and Whirlwind suggestions..) I've seen that there is a new problem : physicsBody can't propagate their properties to other bodies adequately and correctly. In other words copy all properties from a body to another body it's not enough. That's because Apple restrict the access to some methods and properties of the physicsBody original class. It may happen that when you launch a physicsBody.applyImpulse propagating adequately the velocity, the gravity isn't yet respected correctly. That's really orrible to see..and obviusly that's wrong.

So the main goal is: do not change the physicBody recreating it.In other words DON'T RECREATE IT!

I thought that, instead of creating sprite children, you could create a ghost sprites that do the work instead of the main sprite, and the main sprite takes advantage of the ghost changes but ONLY the main sprite have a physicsBody.

This seems to work!

import SpriteKit
class GameScene: SKScene {
    var knight: SKSpriteNode!
    private var ghostKnight:SKSpriteNode!
    var textures : [SKTexture] = [SKTexture]()
    var lastKnightTexture : SKTexture!
    override func didMove(to view: SKView) {
        self.physicsWorld.gravity = CGVector.zero
        let plist = "knight.plist"
        let genericAtlas = SKTextureAtlas(named:plist)
        let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
        for i in 0 ..< genericAtlas.textureNames.count
        {
            let textureName = (String(format:"%@%02d",filename,i))
            textures.append(genericAtlas.textureNamed(textureName))
        }
        if textures.count>0 {
            // Prepare the ghost
            ghostKnight = SKSpriteNode(texture:textures.first)
            addChild(ghostKnight)
            ghostKnight.alpha = 0.2
            ghostKnight.position = CGPoint(x:self.frame.midX,y:100)
            lastKnightTexture = ghostKnight.texture
            // Prepare my sprite
            knight =  SKSpriteNode(texture:textures.first,size:CGSize(width:1,height:1))
            knight.zPosition = 2
            addChild(knight)
            knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
        }
        let ghostAnimation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false))
        ghostKnight.run(ghostAnimation,withKey:"ghostAnimation")
        let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: false, restore: false))
        knight.run(animation,withKey:"knight")

    }
    override func didEvaluateActions() {
        if ghostKnight.action(forKey: "ghostAnimation") != nil {
            if ghostKnight.texture != lastKnightTexture {
                setPhysics()
                lastKnightTexture = ghostKnight.texture
            }
        }
    }
    func setPhysics() {
        if let _ = knight.physicsBody{
            knight.xScale = ghostKnight.frame.size.width
            knight.yScale = ghostKnight.frame.size.height
        } else {
            knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.frame.size)
            knight.physicsBody?.isDynamic = true
            knight.physicsBody?.allowsRotation = false
            knight.physicsBody?.affectedByGravity = true
        }
    }
}

Output:

enter image description here

Obviusly you can hide with alpha set to 0.0 and re-positioning the ghost as you wish to make it disappear.


Update 2017:

After hours of testing I've try to improve the code, finally I managed to remove the ghost sprite but, to work well, one condition is very important: you should not use SKAction.animate with resize in true. This because this method resize the sprites and don't respect the scale (I really don't understand why, hope to some future Apple improvements..). This is the best I've obtain for now:

  • NO CHILDS
  • NO OTHER GHOST SPRITES
  • NO EXTENSION
  • NO ANIMATE METHOD RECREATED

Code:

import SpriteKit
class GameScene: SKScene {
    var knight: SKSpriteNode!
    var textures : [SKTexture] = [SKTexture]()
    var lastKnightSize: CGSize!
    override func didMove(to view: SKView) {
        self.physicsWorld.gravity = CGVector.zero
        let plist = "knight.plist"
        let genericAtlas = SKTextureAtlas(named:plist)
        let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
        for i in 0 ..< genericAtlas.textureNames.count
        {
            let textureName = (String(format:"%@%02d",filename,i))
            textures.append(genericAtlas.textureNamed(textureName))
        }
        if textures.count>0 {
            // Prepare my sprite
            knight =  SKSpriteNode(texture:textures.first,size:CGSize(width:1,height:1))
            knight.zPosition = 2
            addChild(knight)
            knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
            lastKnightSize = knight.texture?.size()
            setPhysics()
        }
        let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: false, restore: false))
        knight.run(animation,withKey:"knight")
    }
    override func didEvaluateActions() {
        lastKnightSize = knight.texture?.size()
        knight.xScale = lastKnightSize.width
        knight.yScale = lastKnightSize.height
    }
    func setPhysics() {
        knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.frame.size)
        knight.physicsBody?.isDynamic = true
        knight.physicsBody?.allowsRotation = false
        knight.physicsBody?.affectedByGravity = true
    }
}

Important detail:

About isDynamic = true that's not possible simply because , during the frequently changes of size, Apple reset also frequently the knight physicsBody but don't apply the inherit of the latest physicsBody properties to the new resetted physicsBody, this is a real shame, you can test it in update printing the knight.physicsBody?.velocity (is always zero but should change due to gravity...). This is probably the reason why Apple recommended to don't scale sprites during physics. To my point of view is a Sprite-kit limitation.

Mounts answered 10/1, 2017 at 15:45 Comment(0)
T
3

Like I mentioned in the comments, since you are doing boxed physics, add a child SKSpriteNode to your knight that will handle the contacts part of the physics, and just scale based on the knight's frame:

(Note: this is for demo purposes only, I am sure you can come up with a more elegant way to handle this across multiple sprites)

import SpriteKit
class GameScene: SKScene {
    var knight: SKSpriteNode!
    var textures : [SKTexture] = [SKTexture]()
    private var child = SKSpriteNode(color:.clear,size:CGSize(width:1,height:1))
    override func didMove(to view: SKView) {
        self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
        let plist = "knight.plist"
        let genericAtlas = SKTextureAtlas(named:plist)
        let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
        for i in 0 ..< genericAtlas.textureNames.count
        {
            let textureName = (String(format:"%@%02d",filename,i))
            textures.append(genericAtlas.textureNamed(textureName))
        }
        if textures.count>0 {
            knight = SKSpriteNode(texture:textures.first)
            knight.zPosition = 2
            addChild(knight)
            knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
        }
        //
        self.setPhysics()
        let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false))
       knight.run(animation,withKey:"knight")
    }
    override func didEvaluateActions() {
        child.xScale = knight.frame.size.width
        child.yScale = knight.frame.size.height

    }
    func setPhysics() {
        child.physicsBody = SKPhysicsBody.init(rectangleOf: child.size)
        child.physicsBody?.isDynamic = false
        knight.addChild(child)
    }
}

To handle texture based bodies. I would write a custom animation to handle it:

extension SKAction
{
    static func animate(withPhysicsTextures textures:[(texture:SKTexture,body:SKPhysicsBody)], timePerFrame:TimeInterval ,resize:Bool, restore:Bool) ->SKAction {

        var originalTexture : SKTexture!;
        let duration = timePerFrame * Double(textures.count);

        return SKAction.customAction(withDuration: duration)
        {
            node,elapsedTime in
            guard let sprNode = node as? SKSpriteNode
            else
            {
                    assert(false,"animatePhysicsWithTextures only works on members of SKSpriteNode");
                    return;
            }
            let index = Int((elapsedTime / CGFloat(duration)) * CGFloat(textures.count))
            //If we havent assigned this yet, lets assign it now
            if originalTexture == nil
            {
                originalTexture = sprNode.texture;
            }


            if(index < textures.count)
            {
                sprNode.texture = textures[index].texture
                sprNode.physicsBody = textures[index].body
            }
            else if(restore)
            {
                sprNode.texture = originalTexture;
            }

            if(resize)
            {
                sprNode.size = sprNode.texture!.size();
            }

        }
    }
}


import SpriteKit
class GameScene: SKScene {
    var knight: SKSpriteNode!
    var textures = [texture:SKTexture,body:SKPhysicsBody]()
    override func didMove(to view: SKView) {
        self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
        let plist = "knight.plist"
        let genericAtlas = SKTextureAtlas(named:plist)
        let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
        for i in 0 ..< genericAtlas.textureNames.count
        {
            let textureName = (String(format:"%@%02d",filename,i))
            let texture = genericAtlas.textureNamed(textureName)
            let body = SKPhysicsBody(texture:texture)
            body.isDynamic = false
            textures.append((texture:texture,body:body))
        }
        if textures.count>0 {
            knight = SKSpriteNode(texture:textures.first.texture)
            knight.zPosition = 2
            addChild(knight)
            knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
        }
        //
        let animation = SKAction.animate(withPhysicsTextures: textures, timePerFrame: 0.15, resize: true, restore: false)
        knight.run(animation, withKey:"knight")
    }
}
Turley answered 22/12, 2016 at 23:13 Comment(2)
This is another interesting idea, obviusly it does not take care about frames: the various textures which the meantime run to the action because the physicsBody is a simple rectangle, not a texture, as you said. This answer don't cover the "physicsBody texture" creation but avoid to create a secondary action but create another sprite. And use didEvaluateActions(), another interesting part.Mounts
Added custom animation to handle texture based bodiesTurley
M
3

Solution for isDynamic = false.

*Update to 2017 below

After days of test maded with my answer, Knight0fDragon answer's and some other ideas came from other SO answers (Confused and Whirlwind suggestions..) I've seen that there is a new problem : physicsBody can't propagate their properties to other bodies adequately and correctly. In other words copy all properties from a body to another body it's not enough. That's because Apple restrict the access to some methods and properties of the physicsBody original class. It may happen that when you launch a physicsBody.applyImpulse propagating adequately the velocity, the gravity isn't yet respected correctly. That's really orrible to see..and obviusly that's wrong.

So the main goal is: do not change the physicBody recreating it.In other words DON'T RECREATE IT!

I thought that, instead of creating sprite children, you could create a ghost sprites that do the work instead of the main sprite, and the main sprite takes advantage of the ghost changes but ONLY the main sprite have a physicsBody.

This seems to work!

import SpriteKit
class GameScene: SKScene {
    var knight: SKSpriteNode!
    private var ghostKnight:SKSpriteNode!
    var textures : [SKTexture] = [SKTexture]()
    var lastKnightTexture : SKTexture!
    override func didMove(to view: SKView) {
        self.physicsWorld.gravity = CGVector.zero
        let plist = "knight.plist"
        let genericAtlas = SKTextureAtlas(named:plist)
        let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
        for i in 0 ..< genericAtlas.textureNames.count
        {
            let textureName = (String(format:"%@%02d",filename,i))
            textures.append(genericAtlas.textureNamed(textureName))
        }
        if textures.count>0 {
            // Prepare the ghost
            ghostKnight = SKSpriteNode(texture:textures.first)
            addChild(ghostKnight)
            ghostKnight.alpha = 0.2
            ghostKnight.position = CGPoint(x:self.frame.midX,y:100)
            lastKnightTexture = ghostKnight.texture
            // Prepare my sprite
            knight =  SKSpriteNode(texture:textures.first,size:CGSize(width:1,height:1))
            knight.zPosition = 2
            addChild(knight)
            knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
        }
        let ghostAnimation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false))
        ghostKnight.run(ghostAnimation,withKey:"ghostAnimation")
        let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: false, restore: false))
        knight.run(animation,withKey:"knight")

    }
    override func didEvaluateActions() {
        if ghostKnight.action(forKey: "ghostAnimation") != nil {
            if ghostKnight.texture != lastKnightTexture {
                setPhysics()
                lastKnightTexture = ghostKnight.texture
            }
        }
    }
    func setPhysics() {
        if let _ = knight.physicsBody{
            knight.xScale = ghostKnight.frame.size.width
            knight.yScale = ghostKnight.frame.size.height
        } else {
            knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.frame.size)
            knight.physicsBody?.isDynamic = true
            knight.physicsBody?.allowsRotation = false
            knight.physicsBody?.affectedByGravity = true
        }
    }
}

Output:

enter image description here

Obviusly you can hide with alpha set to 0.0 and re-positioning the ghost as you wish to make it disappear.


Update 2017:

After hours of testing I've try to improve the code, finally I managed to remove the ghost sprite but, to work well, one condition is very important: you should not use SKAction.animate with resize in true. This because this method resize the sprites and don't respect the scale (I really don't understand why, hope to some future Apple improvements..). This is the best I've obtain for now:

  • NO CHILDS
  • NO OTHER GHOST SPRITES
  • NO EXTENSION
  • NO ANIMATE METHOD RECREATED

Code:

import SpriteKit
class GameScene: SKScene {
    var knight: SKSpriteNode!
    var textures : [SKTexture] = [SKTexture]()
    var lastKnightSize: CGSize!
    override func didMove(to view: SKView) {
        self.physicsWorld.gravity = CGVector.zero
        let plist = "knight.plist"
        let genericAtlas = SKTextureAtlas(named:plist)
        let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
        for i in 0 ..< genericAtlas.textureNames.count
        {
            let textureName = (String(format:"%@%02d",filename,i))
            textures.append(genericAtlas.textureNamed(textureName))
        }
        if textures.count>0 {
            // Prepare my sprite
            knight =  SKSpriteNode(texture:textures.first,size:CGSize(width:1,height:1))
            knight.zPosition = 2
            addChild(knight)
            knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
            lastKnightSize = knight.texture?.size()
            setPhysics()
        }
        let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: false, restore: false))
        knight.run(animation,withKey:"knight")
    }
    override func didEvaluateActions() {
        lastKnightSize = knight.texture?.size()
        knight.xScale = lastKnightSize.width
        knight.yScale = lastKnightSize.height
    }
    func setPhysics() {
        knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.frame.size)
        knight.physicsBody?.isDynamic = true
        knight.physicsBody?.allowsRotation = false
        knight.physicsBody?.affectedByGravity = true
    }
}

Important detail:

About isDynamic = true that's not possible simply because , during the frequently changes of size, Apple reset also frequently the knight physicsBody but don't apply the inherit of the latest physicsBody properties to the new resetted physicsBody, this is a real shame, you can test it in update printing the knight.physicsBody?.velocity (is always zero but should change due to gravity...). This is probably the reason why Apple recommended to don't scale sprites during physics. To my point of view is a Sprite-kit limitation.

Mounts answered 10/1, 2017 at 15:45 Comment(0)
M
2

Another idea could be the suggestion about didEvaluateActions() to search a more general method to have a "variable" physicsBody that follow the real current knight texture OR physics settings as a rectangle body like this case:

Update: (thanks to Knight0fDragon and 0x141E interventions)

import SpriteKit
class GameScene: SKScene {
    var knight: SKSpriteNode!
    var textures : [SKTexture] = [SKTexture]()
    var lastKnightTexture : SKTexture!
    override func didMove(to view: SKView) {
        self.physicsWorld.gravity = CGVector(dx:0, dy:-2)
        let plist = "knight.plist"
        let genericAtlas = SKTextureAtlas(named:plist)
        let filename : String! = NSURL(fileURLWithPath: plist).deletingPathExtension!.lastPathComponent
        for i in 0 ..< genericAtlas.textureNames.count
        {
            let textureName = (String(format:"%@%02d",filename,i))
            textures.append(genericAtlas.textureNamed(textureName))
        }
        if textures.count>0 {
            knight = SKSpriteNode(texture:textures.first)
            lastKnightTexture = knight.texture
            knight.zPosition = 2
            addChild(knight)
            knight.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
        }
        let animation = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.15, resize: true, restore: false))
        knight.run(animation,withKey:"knight")
    }
    override func didEvaluateActions() {
        if knight.action(forKey: "knight") != nil {
            if knight.texture != lastKnightTexture {
                setPhysics()
                lastKnightTexture = knight.texture
            }
        }
    }
    func setPhysics() {
        knight.physicsBody = SKPhysicsBody.init(rectangleOf: knight.size)
        knight.physicsBody?.isDynamic = false
    }
}
Mounts answered 23/12, 2016 at 10:44 Comment(17)
I wouldnt take this approach, id stick with your original to keep the code simple. what I have done though in one of my games was created an array of tuples that was (SKTexture,SKPhysicsBody) and do a customAnimation that behaves like animation with texturesTurley
No, I'm not agree to re-create the official framework method skaction.animate with a customAction for many reasons...but if you have some working example maybe that's possible. However I prefeer to use the available animate function..Mounts
But you already have an action doing this already in your original codeTurley
With this approach you are creating new skphysicsbodies right before they get used, this is fine maybe if you are only using 1 sprite, but if you have many, it may get hecticTurley
I preefer the approach in this answer and your answer without parallel actions o re-creation of the main animation. The code in my question is only the first working approach. The absurd thing is that not me and not you or anyone should be solve this sprite-kit gaps, holes but it should did Apple team by improving their framework.Mounts
I don't think allocating a new physics body ~60 times a second is a good idea. Perhaps you can pre-allocate the bodies, store them in a dictionary, and use a hashable version of knight.size as a key.Breastbeating
@Breastbeating Interesting idea. Suppose you have 80 frames : overload temporary the memory with 80 rectangles could be ok, with 80 textures I start to think it's not a good idea..Mounts
Anyway, both Knight0fDragon and 0x141E made a excellent point: allocating a new physics body ~60 times a second is NOT a good idea. So, I've try to improve my code with this new update(this should be restrict physicsBody creation calls only when necessary), but as usual, if you think to a better way please express your idea (to comment or answer). Thank you so much guys, I appreciate each intervention.Mounts
Actually a nice idea... But, Isn't what @Turley said, a best way to accomplish this ? I haven't looked closely, but it seems that he scales a sprite, which results in physics body scaling, means no re-allocations (if I am wrong, please feel free to correct me). Anyway, if that is not how Knight0fDragon imagined this, I would try with scaling thing (I remember it worked on some iOS versions).Bouillabaisse
@Bouillabaisse yes it works but only if you don't use textures to make physicsBodies, so we can easily scale the rectangle body as the good knight0fdragon did. I try to make a method for who want to use also textures like this one in answerMounts
@AlessandroOrnano, when it comes to timing, you may want to consider sacrificing the RAM. The way you have it set up now, you are accepting lag spikes when animations are happening, instead of a constant lag, which the user may not even notice because it is constant.Turley
@Turley Can you please explain a bit more (if you find time) about lag & RAM thing. I haven't had a chance to try all examples yet, but I am interested in details.Bouillabaisse
RAM and CPU cycles have an opposite correlation. If you want something to use less CPU cycles, usually you end up using more RAM, and if you want to use less RAM, usually you end up using more CPU cycles. In the case of Alessandro's example, usually we try to think in worst case scenario. That is the animation frame always changes. This is the laggiest moment our game can get, lets say it takes 1/30FPS to complete. Now the optimization put into place is a simple check if a new body is needed. This takes 1/60FPS to complete. Now we play a game where every 5 frames we hit worst case,...Turley
We now see an update of 1/60,1/60,1/60,1/60,1/30. We hit a lag spike, and this will be very noticeable. If you do the worst case all the time, you hit 1/30 5 times, and since the timing is consistent, the user experience is that is how the game is designed, not that it lags, and it becomes a more acceptable experience. @BouillabaisseTurley
Now yes, there are other optimizations that come into play, this is just a very simple generalization about handling update cycles.Turley
@Turley Ah, okay. Yeah, it is better to have even a constant lower frame rate than fluctuations. LearnCocos2d explained [something like that]((https://mcmap.net/q/1124866/-why-is-only-60-fps-really-smooth-in-cocos2d)) earlier as well so it might be a good read for those who are interested in all this. Anyways, I was curious about the fact whether you experiencing some lag currently, or not, and in which conditions.Bouillabaisse
@Bouillabaisse no i am not experiencing any, i try to think in terms of larger scale, easier to plan ahead than to go back and fixTurley
M
0

I am getting ready to dive into some texture physics bodies and ran into this problem a while back.

After reading this I am thinking of an approach like so:

When animating, why not have a base node (center node), and then children that are are each the frames of animation. Then patterns of hiding and unhiding the child nodes, where each child node (which I will be doing) is using the alpha of the texture (all loaded) for their PBs. But just HIDE / UNHIDE (my answer).

I mean how much of a penalty would we incure having 12 nodes per se, of a base node that are the animation frames, versus loading and unloading images for animation.

Any penalty in my mind would be well worth being able to have the variety of various physics bodies.

Mythopoeia answered 19/3, 2021 at 1:19 Comment(4)
You'd have to turn the Physics contactTestBitMask and collisionBitMask all OFF, while hidden, cause they linger even while hidden.Mythopoeia
This solution could be useful if you have few animations in your game: what about ten characters each one have ten animations where the average animation is composed by 30 sprites?Mounts
Well like I said (with my example using alpha channels for PBs) "Any penalty" would be well worth having 99% physics bodies, "in effect." Plus lets say you have a world with say 1000 Base Nodes, well with occlusion you're at most gonna have say, 20 enties "on screen" and that would be 240 nodes? SpriteKit can handle that no problem...Mythopoeia
What about 2D Tiled maps and its physical nodes? I've said as example only characters, what about the rest of the game..fps inevitably drops downMounts

© 2022 - 2024 — McMap. All rights reserved.