替代animation(forKey:)(现已弃用)的方法是什么?

12

我正在使用iOS 11(针对ARKit),尽管许多人指向苹果提供的一个带有狐狸的SceneKit示例应用程序,但我在该示例项目中使用的扩展中遇到了问题(文件),无法添加动画:

extension CAAnimation {
    class func animationWithSceneNamed(_ name: String) -> CAAnimation? {
        var animation: CAAnimation?
        if let scene = SCNScene(named: name) {
            scene.rootNode.enumerateChildNodes({ (child, stop) in
                if child.animationKeys.count > 0 {
                    animation = child.animation(forKey: child.animationKeys.first!)
                    stop.initialize(to: true)
                }
            })
        }
        return animation
    }
}

这个扩展看起来非常方便,但我不确定现在它被弃用后如何迁移?现在它是否已经默认内置到SceneKit中了?

文档并没有提供太多关于为什么它被弃用或者接下来该怎么做的信息。

谢谢

4个回答

8
TL;DR: 可以在苹果的示例游戏中找到使用新API的示例(搜索SCNAnimationPlayer)。

尽管iOS11已经弃用了与CAAnimation一起工作的animation(forKey:)及其姊妹方法,但您仍然可以继续使用它们 - 一切都会正常工作。

但是,如果您想使用新的API并且不关心向后兼容性(在iOS11的情况下您不需要),请继续阅读。

新引入的SCNAnimationPlayer相比其前身提供了更方便的API。现在更容易实时处理动画。

这个来自WWDC2017的视频将是学习它的好起点。

简单地说:SCNAnimationPlayer允许您随时更改动画速度。与添加和删除CAAnimation相比,它提供了更直观的动画播放界面,如play()stop()方法。

您还可以将两个动画混合在一起,例如用于使它们之间的过渡更加平滑。

您可以在苹果的Fox 2游戏示例中找到如何使用所有这些功能的示例。


这是您发布的扩展,已适应使用SCNAnimationPlayer(您可以在Fox 2示例项目的Character类中找到它):

extension SCNAnimationPlayer {
    class func loadAnimation(fromSceneNamed sceneName: String) -> SCNAnimationPlayer {
        let scene = SCNScene( named: sceneName )!
        // find top level animation
        var animationPlayer: SCNAnimationPlayer! = nil
        scene.rootNode.enumerateChildNodes { (child, stop) in
            if !child.animationKeys.isEmpty {
                animationPlayer = child.animationPlayer(forKey: child.animationKeys[0])
                stop.pointee = true
            }
        }
        return animationPlayer
    }
}

你可以按照以下方式使用它:
  1. Load the animation and add it to the corresponding node

    let jumpAnimation = SCNAnimationPlayer.loadAnimation(fromSceneNamed: "jump.scn")
    jumpAnimation.stop() // stop it for now so that we can use it later when it's appropriate 
    model.addAnimationPlayer(jumpAnimation, forKey: "jump")
    
  2. Use it!

    model.animationPlayer(forKey: "jump")?.play()
    

1
非常感谢你的帮助。我已经按照你提供的链接进行操作,使用 SCNAnimationPlayer 替换了我的 CAAnimation 代码,但是无论如何都无法播放它。网格跳到动画的第一帧,所以我知道它正在加载。我可以记录动画并查看正确数量的帧数,但 play() 却没有任何作用。使用 CAAnimation 运行相同的动画正常。有什么方法可以测试吗? - Toby Evetts

2

Lësha Turkowski 的答案没有使用强制解包。

extension SCNAnimationPlayer {

    class func loadAnimationPlayer(from sceneName: String) -> SCNAnimationPlayer? {
        var animationPlayer: SCNAnimationPlayer?

        if let scene = SCNScene(named: sceneName) {
            scene.rootNode.enumerateChildNodes { (child, stop) in
                if !child.animationKeys.isEmpty {
                    animationPlayer = child.animationPlayer(forKey: child.animationKeys[0])
                    stop.pointee = true
                }
            }
        }

        return animationPlayer
    }
}

1
这是一个关于SwiftUI和SceneKit的例子。
import SwiftUI
import SceneKit

struct ScenekitView : UIViewRepresentable {
@Binding var isPlayingAnimation: Bool

let scene = SCNScene(named: "art.scnassets/TestScene.scn")!

func makeUIView(context: Context) -> SCNView {
    // create and add a camera to the scene
    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    scene.rootNode.addChildNode(cameraNode)

    let scnView = SCNView()
    return scnView
}

func updateUIView(_ scnView: SCNView, context: Context) {
    scnView.scene = scene

    // allows the user to manipulate the camera
    scnView.allowsCameraControl = true

    controlAnimation(isAnimating: isPlayingAnimation, nodeName: "TestNode", animationName: "TestAnimationName")
}

func controlAnimation(isAnimating: Bool, nodeName: String, animationName: String) {
    guard let node = scene.rootNode.childNode(withName: nodeName, recursively: true) else {  return }
    guard let animationPlayer: SCNAnimationPlayer = node.animationPlayer(forKey: animationName) else {  return }
    if isAnimating {
        print("Play Animation")
        animationPlayer.play()
    } else {
        print("Stop Animation")
        animationPlayer.stop()
    }
}
}

struct DogAnimation_Previews: PreviewProvider {
    static var previews: some View {
        ScenekitView(isPlayingAnimation: .constant(true))
    }
}

0

一个2023年的案例。

我像这样加载典型的动画:

func simpleLoadAnim(filename: String) -> SCNAnimationPlayer {
    let s = SCNScene(named: filename)!
    let n = s.rootNode.childNodes.filter({!$0.animationKeys.isEmpty}).first!
    return n.animationPlayer(forKey: n.animationKeys.first!)!
}

那么,

laugh = simpleLoadAnim(filename: "animeLaugh") // animeLaugh.dae
giggle = simpleLoadAnim(filename: "animeGiggle")

然后,第一步,您必须将它们添加到字符中:

sally.addAnimationPlayer(laugh, forKey: "laugh")
sally.addAnimationPlayer(giggle, forKey: "giggle")

通常情况下,您一次只会有一个。因此设置权重,第二步

laugh.blendFactor = 1
giggle.blendFactor = 0

播放或停止一个 SCNAnimationPlayer 只需要三个步骤

laugh.play()
giggle.stop()

几乎可以肯定,您将有(数百行)自己的代码来在动画之间进行混合(可能只需要很短的时间,0.1秒,或者可能需要一秒左右)。为此,您将使用SCNAction.customAction

如果您愿意,您可以使用键访问角色(Sally)上的动画。但真正的“重点”是您可以随时启动、停止等等SCNAnimationPlayer

您还将有大量的代码来设置SCNAnimationPlayer的喜好(速度、循环、镜像等等等等等等等等)

您需要这个非常关键的答案才能使Collada文件(必须是单独的角色/动画文件)正常工作https://dev59.com/78Xsa4cB1Zd3GeqPsbUh#75093081

一旦各种SCNAnimationPlayer动画正常工作,就很容易使用、运行、混合等等动画。

基本序列是

  1. 每个动画必须在自己的 .dae 文件中
  2. 将每个动画文件加载到 SCNAnimationPlayer
  3. 将所有动画“添加”到相关角色中
  4. 编写混合程序
  5. 然后简单地 play()stop() 实际的 SCNAnimationPlayer 项目(不要费心使用角色上的键,这有点无意义)

您对本文有任何问题吗? - ZAY

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接