如何让普通的 Mixamo 角色动画在 SceneKit 中工作?

Fat*_*tie 6 ios scenekit swift

前往 mixamo.com,选择一个角色,点击动画,选择一个,只需下载为 .dae即可。

在此输入图像描述

将文件放在 Mac 桌面上;点击文件信息)。它将完美地动画角色的动作。

Xcode,拖入文件夹。点击 .dae 文件,点击底部的“播放”图标。它将完美地动画角色的动作。

现在,将角色添加到现有的 SceneKit 场景中。例如:

let p = Bundle.main.url(forResource: "File Name", withExtension: "dae")!
modelSource = SCNSceneSource(url: p, options: nil)!
let geom = modelSource.entryWithIdentifier("geometry316",
                 withClass: SCNGeometry.self)! as SCNGeometry
theModel = SCNNode(geometry: geom)
.. your node .. .addChildNode(theModel)
Run Code Online (Sandbox Code Playgroud)

(要获取几何名称,只需查看 .dae 文本示例

您将完美地看到 T 形的角色

然而,似乎不可能在角色上运行动画。

代码看起来像......

theAnime = amySource.entryWithIdentifier("unnamed_animation__0", withClass: CAAnimation.self)!
theModel.addAnimation(theAnime, forKey:"aKey")
Run Code Online (Sandbox Code Playgroud)

无论我尝试什么,它都不会动画。

在添加动画的那一刻,角色会跳转到不同的静态位置,并且不执行任何操作。(如果您安排“结束”动画removeAllAnimations(),它只会返回到 T 姿势。)

显然,dae 文件是完美的,因为它在 Mac Finder 中完美地显示了动画,并且在 Xcode 中的 .dae 文件的实际屏幕上完美地显示了动画!

简而言之,从上面的 mixamo 图像来看,有没有人能够让动画实际上在 SceneKit 场景中运行

(PS 不是 ARKit ..场景套件。)

ZAY*_*ZAY 6

首先,您的角色只需处于 T 字位置。将该文件下载为带有皮肤的 Collada (DAE)。请勿在此文件中包含任何动画。则无需对此文件进行进一步修改。

\n

然后,对于您将实现的任何动画效果,例如行走、跑步、跳舞或其他任何效果 - 这样做:

\n

在 Mixamo 中的角色上测试/应用您想要的动画,根据需要调整设置,然后下载。在这里,下载为 Collada (DAE) 并选择没有皮肤非常重要!将帧速率和关键帧缩减保留为默认值。

\n

这将为您想要实现的每个动画提供一个 DAE 文件。该 DAE 不包含网格数据,也不包含装备。它仅包含其所属模型的变形(这就是您选择下载不带皮肤的原因)。

\n

然后,您需要对所有包含动画的 DAE 文件执行两个附加操作。

\n

首先,您需要漂亮地打印包含动画的每个 DAE 的 XML 结构。你可以这样做,即。使用 Notepad++ 中的 XML 工具,或者在 Mac 上打开终端并使用以下命令:

\n
xmllint \xe2\x80\x94-format my_anim_orig.dae > my_anim.dae\n
Run Code Online (Sandbox Code Playgroud)\n

然后在您的 Mac 上安装此工具。\n( https://drive.google.com/file/d/0B1_uvI21ZYGUaGdJckdwaTRZUEk/edit?usp=sharing )\n转换器工具

\n

使用此转换器转换您的所有 DAE 动画:\n(但请勿使用此工具转换您的 T-Pose 模型!!!)\n科拉达转换器

\n

不,我们已准备好设置动画:

\n

你应该在以下范围内组织 DAEart.scnassets您应该在文件夹

\n

在此输入图像描述

\n

让我们配置一下:

\n

我通常会在一个struct称为字符的范围内。但任何其他实现都可以

\n

添加这个:

\n
struct Characters {\n    \n    // MARK: Characters\n    var bodyWarrior                         : SCNNode!\n    \n    private let objectMaterialWarrior      : SCNMaterial = {\n        let material = SCNMaterial()\n        material.name                       = "warrior"\n        material.diffuse.contents           = UIImage.init(named: "art.scnassets/warrior/textures/warrior_diffuse.png")\n        material.normal.contents            = UIImage.init(named: "art.scnassets/warrior/textures/warrior_normal.png")\n        material.metalness.contents         = UIImage.init(named: "art.scnassets/warrior/textures/warrior_metalness.png")\n        material.roughness.contents         = UIImage.init(named: "art.scnassets/warrior/textures/warrior_roughness.png")\n        material.ambientOcclusion.contents  = UIImage.init(named: "art.scnassets/warrior/textures/warrior_AO.png")\n        material.lightingModel              = .physicallyBased\n        material.isDoubleSided              = false\n        return material\n    }()\n    \n    // MARK: MAIN Init Function\n    init() {\n        \n        // Init Warrior\n        bodyWarrior = SCNNode(named: "art.scnassets/warrior/warrior.dae")\n        bodyWarrior.childNodes[1].geometry?.firstMaterial = objectMaterialWarrior // character body material\n        \n        print("Characters Init Completed.")\n        \n    }\n    \n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后你可以初始化结构体 i.Ex。在 viewDidLoad\nvar 字符 = 字符()

\n

注意使用正确的childNode!\n子节点

\n

在这种情况下,childNodes[1]是可见的网格并且childNodes[0]然后是动画节点。

\n

你也可以在你的代码中实现这个 SceneKit 扩展,它对于导入模型非常有用。(注意,它会将模型节点组织为新节点的子节点!)

\n
extension SCNNode {\n    convenience init(named name: String) {\n        self.init()\n        guard let scene = SCNScene(named: name) else {return}\n        for childNode in scene.rootNode.childNodes {addChildNode(childNode)}\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

还在下面添加该扩展名。稍后您将在动画播放器中需要它。

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

像这样处理角色设置和动画:\n(这是我的类的简化版本)

\n
class Warrior {\n    \n    // Main Nodes\n    var node                 = SCNNode()\n    private var animNode     : SCNNode!\n    \n    // Control Variables\n    var isIdle               : Bool = true\n    \n    // For Initial Warrior Position and Scale\n    private var position            = SCNMatrix4Mult(SCNMatrix4MakeRotation(0,0,0,0), SCNMatrix4MakeTranslation(0,0,0))\n    private var scale               = SCNMatrix4MakeScale(0.03, 0.03, 0.03) // default size ca 6m height\n    \n    // MARK: ANIMATIONS\n    private let aniKEY_NeutralIdle       : String = "NeutralIdle-1"       ; private let aniMAT_NeutralIdle       : String = "art.scnassets/warrior/NeutralIdle.dae"\n    private let aniKEY_DwarfIdle         : String = "DwarfIdle-1"         ; private let aniMAT_DwarfIdle         : String = "art.scnassets/warrior/DwarfIdle.dae"\n    private let aniKEY_LookAroundIdle    : String = "LookAroundIdle-1"    ; private let aniMAT_LookAroundIdle    : String = "art.scnassets/warrior/LookAround.dae"\n    private let aniKEY_Stomp             : String = "Stomp-1"             ; private let aniMAT_Stomp             : String = "art.scnassets/warrior/Stomp.dae"\n    private let aniKEY_ThrowObject       : String = "ThrowObject-1"       ; private let aniMAT_ThrowObject       : String = "art.scnassets/warrior/ThrowObject.dae"\n    private let aniKEY_FlyingBackDeath   : String = "FlyingBackDeath-1"   ; private let aniMAT_FlyingBackDeath   : String = "art.scnassets/warrior/FlyingBackDeath.dae"\n    \n    // MARK: MAIN CLASS INIT\n    init(index: Int, scaleFactor: Float = 0.03) {\n        \n        scale = SCNMatrix4MakeScale(scaleFactor, scaleFactor, scaleFactor)\n        \n        // Config Node\n        node.index = index\n        node.name = "warrior"\n        node.addChildNode(GameViewController.characters.bodyWarrior.clone()) // childNodes[0] of node. this holds all subnodes for the character including animation skeletton\n        node.childNodes[0].transform = SCNMatrix4Mult(position, scale)\n        \n        // Set permanent animation Node\n        animNode = node.childNodes[0].childNodes[0]\n        \n        // Add to Scene\n        gameScene.rootNode.addChildNode(node) // add the warrior to scene\n        \n        print("Warrior initialized with index: \\(String(describing: node.index))")\n        \n    }\n    \n    \n    // Cleanup & Deinit\n    func remove() {\n        print("Warrior deinitializing")\n        self.animNode.removeAllAnimations()\n        self.node.removeAllActions()\n        self.node.removeFromParentNode()\n    }\n    deinit { remove() }\n    \n    // Set Warrior Position\n    func setPosition(position: SCNVector3) { self.node.position = position }\n    \n    // Normal Idle\n    enum IdleType: Int {\n        case NeutralIdle\n        case DwarfIdle // observe Fingers\n        case LookAroundIdle\n    }\n    \n    // Normal Idles\n    func idle(type: IdleType) {\n        \n        isIdle = true // also sets all walking and running variabled to false\n        \n        var animationName : String = ""\n        var key           : String = ""\n        \n        switch type {\n        case .NeutralIdle:       animationName = aniMAT_NeutralIdle        ; key = aniKEY_NeutralIdle      // ; print("NeutralIdle   ")\n        case .DwarfIdle:         animationName = aniMAT_DwarfIdle          ; key = aniKEY_DwarfIdle        // ; print("DwarfIdle     ")\n        case .LookAroundIdle:    animationName = aniMAT_LookAroundIdle     ; key = aniKEY_LookAroundIdle   // ; print("LookAroundIdle")\n        }\n        \n        makeAnimation(animationName, key, self.animNode, backwards: false, once: false, speed: 1.0, blendIn: 0.5, blendOut: 0.5)\n        \n    }\n    \n    func idleRandom() {\n        switch Int.random(in: 1...3) {\n        case 1: self.idle(type: .NeutralIdle)\n        case 2: self.idle(type: .DwarfIdle)\n        case 3: self.idle(type: .LookAroundIdle)\n        default: break\n        }\n    }\n    \n    // MARK: Private Functions\n    // Common Animation Function\n    private func makeAnimation(_ fileName           : String,\n                               _ key                : String,\n                               _ node               : SCNNode,\n                               backwards            : Bool = false,\n                               once                 : Bool = true,\n                               speed                : CGFloat = 1.0,\n                               blendIn              : TimeInterval = 0.2,\n                               blendOut             : TimeInterval = 0.2,\n                               removedWhenComplete  : Bool = true,\n                               fillForward          : Bool = false\n                              )\n    \n    {\n        \n        let anim   = SCNAnimationPlayer.loadAnimation(fromSceneNamed: fileName)\n        \n        if once { anim.animation.repeatCount = 0 }\n        anim.animation.autoreverses = false\n        anim.animation.blendInDuration  = blendIn\n        anim.animation.blendOutDuration = blendOut\n        anim.speed = speed; if backwards {anim.speed = -anim.speed}\n        anim.stop()\n        print("duration: \\(anim.animation.duration)")\n        \n        anim.animation.isRemovedOnCompletion = removedWhenComplete\n        anim.animation.fillsForward          = fillForward\n        anim.animation.fillsBackward         = false\n        \n        // Attach Animation\n        node.addAnimationPlayer(anim, forKey: key)\n        node.animationPlayer(forKey: key)?.play()\n        \n    }\n    \n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后,您可以在初始化字符结构后初始化类对象。

\n

其余的你会弄清楚,如果你有疑问或需要一个完整的示例应用程序,请回来找我:)

\n