9

我使用“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
    }
}

输出是:

在此处输入图像描述

如您所见,这physicsBody是静态的,不要尊重动画:这是正常的,因为在动画期间纹理会更改尺寸/大小,而我们不会更改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])

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

在此处输入图像描述


因此,正确的做法应该是在动画期间截取帧并动态更改physicsBody。我们也可以使用update()from 的方法SKScene,但我正在考虑扩展。

我的想法是将animation动作与 a结合起来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])
    }
}

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

 //
        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
    }

这终于奏效了,输出是:

在此处输入图像描述

您知道获得此结果的更好方法或更优雅的方法吗?

4

4 回答 4

3

就像我在评论中提到的那样,由于您正在做盒装物理,因此向您的骑士添加一个子 SKSpriteNode 它将处理物理的接触部分,并根据骑士的框架进行缩放:

(注意:这仅用于演示目的,我相信您可以想出一种更优雅的方式来处理多个精灵)

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)
    }
}

处理基于纹理的实体。我会编写一个自定义动画来处理它:

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")
    }
}
于 2016-12-22T23:13:46.897 回答
3

的解决方案isDynamic = false

*以下更新至 2017 年

经过几天对我的回答进行的测试,Knight0fDragon 的回答和其他一些想法来自其他 SO 答案(Confused 和 Whirlwind 建议..)我已经看到有一个新问题physicsBody无法将它们的属性充分传播到其他机构并且正确。换句话说,将所有属性从一个主体复制到另一个主体是不够的。那是因为 Apple 限制了对physicsBody原始类的某些方法和属性的访问。可能会发生这样的情况,当您启动physicsBody.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
        }
    }
}

输出

在此处输入图像描述

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


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
    }
}

重要细节

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

于 2017-01-10T15:45:19.050 回答
2

另一个想法可能是关于 didEvaluateActions()搜索更通用方法的建议,以使“变量”physicsBody遵循真实的当前骑士纹理 物理设置作为矩形体,如下例所示:

更新:(感谢 Knight0fDragon 和 0x141E 干预)

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
    }
}
于 2016-12-23T10:44:52.570 回答
0

我正准备深入研究一些纹理物理体,不久前遇到了这个问题。

读完这篇文章后,我正在考虑这样的方法:

制作动画时,为什么不拥有一个基节点(中心节点),然后是每个动画帧的子节点。然后隐藏和取消隐藏子节点的模式,其中每个子节点(我将要做的)正在使用纹理的 alpha(全部加载)作为它们的 PB。但只是隐藏/取消隐藏(我的回答)。

我的意思是,与为动画加载和卸载图像相比,拥有 12 个节点本身(作为动画帧的基本节点)会受到多大的惩罚。

我心目中的任何惩罚都非常值得拥有各种不同的物理体。

于 2021-03-19T01:19:26.427 回答