动画地图集和动态物理在Swift 3中

Ale*_*ano 8 sprite-kit texture-atlas skphysicsbody swift

我使用"Hello World"Sprite-kit模板做了一个小测试项目,其中有一个由这些框架组成的地图集动画: 在此输入图像描述

-

我想展示这个骑士和它的动画.

我想设置一个DYNAMIC物理体.

所以我用一个工具来分隔单帧,我做了一个atlasc文件夹,所以代码应该是:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

在此输入图像描述

正如你所看到的,physicsBody是STATIC,不尊重动画:这是正常的,因为在动画期间纹理改变尺寸/尺寸,我们不会改变physicsBody动作期间保持不变的尺寸.

根据消息来源,没有方法,在期间SKAction.animate,允许更改physicsBody.

虽然我们使用:

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

为我们动画的每一帧创建实体,这些实体在场景中保持一致,创造了一个丑陋奇怪的情况,如下图:

在此输入图像描述


因此,正确的方法应该是在动画期间拦截帧并physicsBody在运行中进行更改.我们也可以使用update()方法SKScene,但我正在考虑扩展.

我的思想是将结合animation有动作SKAction.group,使该检查第一动作的执行匹配的电流,截距帧另一自定义动作knight.texturetextures阵列和改变physicsBody启动外部的方法,在这种情况下setPhysicsBody.

然后,我写了这个:

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])
    }
}
Run Code Online (Sandbox Code Playgroud)

添加此扩展,我们更改代码的动画部分:

 //
        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
    }
Run Code Online (Sandbox Code Playgroud)

这最终起作用,输出是:

在此输入图像描述

你知道更好的方法,或者更优雅的方法来获得这个结果吗?

Ale*_*ano 3

的解决方案isDynamic = false

*以下更新至 2017 年

经过几天的测试,我的答案、Knight0fDragon 答案和其他一些想法来自其他 SO 答案(困惑和旋风建议..)我发现存在一个新问题physicsBody无法将它们的属性充分传播到其他机构,并且正确。换句话说,将一个主体的所有属性复制到另一个主体是不够的。这是因为 Apple 限制了对原始类的某些方法和属性的访问physicsBodyphysicsBody.applyImpulse当您充分传播 时,可能会发生以下情况velocity:尚未正确考虑重力。这真的很可怕……而且显然这是错误的。

所以主要目标是:不要改变physicBody重新创建它。换句话说,不要重新创建它

我认为,您可以创建一个幽灵精灵来代替主精灵来完成这项工作,而不是创建子精灵,并且主精灵利用幽灵的变化,但只有主精灵有物理主体。

这似乎有效!

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出

在此输入图像描述

显然,您可以将其设置为 0.0 来隐藏alpha,并根据需要重新定位幽灵,使其消失。


2017年更新

经过几个小时的测试,我尝试改进代码,最后我成功地删除了幽灵精灵,但是,为了正常工作,有一个条件非常重要:您不应该在 true 中使用SKAction.animatewith 。resize这是因为这种方法调整了精灵的大小并且不尊重比例(我真的不明白为什么,希望苹果未来能做出一些改进..)。这是我目前获得的最好的:

  • 没有孩子
  • 没有其他幽灵精灵
  • 无延期
  • 没有重新创建动画方法

代码:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

重要细节

isDynamic = true是不可能的,因为,在频繁改变大小的过程中,苹果也经常重置骑士的物理主体,但没有将最新physicsBody属性的继承应用到新的重置中physicsBody,这真是一个耻辱,你可以在更新打印中测试它(knight.physicsBody?.velocity始终为零,但应该由于重力而改变......)。这可能就是苹果建议不要在物理过程中缩放精灵的原因。在我看来,这是Sprite-kit 的限制。