I did a small test project using the "Hello World" Sprite-kit template where there is an atlas animation composed by these frames:
-
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:
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:
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:
Do you know a better way, or a more elegant method to obtain this result?