如何在SceneKit中使用普通的Mixamo角色动画?

6

访问mixamo.com,选择角色,点击动画,选择一个,简单地下载为.dae格式

enter image description here

将文件保存在您的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)

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

你将完美地看到 T 形姿势的角色

然而似乎无法对角色运行动画。

代码将类似于...

theAnime = amySource.entryWithIdentifier("unnamed_animation__0", withClass: CAAnimation.self)!
theModel.addAnimation(theAnime, forKey:"aKey")

无论我尝试什么,它都不会播放动画。
当您添加动画时,角色会跳到不同的静态位置并且什么也不做。(如果您安排“结束”动画removeAllAnimations(),则它会简单地返回 T 姿势。)
很明显,由于在 Mac finder 中完美地显示了动画,在 Xcode 的 .dae 文件的实际屏幕上也完美地显示了动画,因此 dae 文件是完美的
总之,从上面的 mixamo 图像中,有没有人能够真正在 SceneKit 场景中运行动画
(PS 不是 ARKit.. 而是 scene kit。)
1个回答

6

首先,您需要将角色放置在T形位置。将该文件下载为带有皮肤的Collada(DAE)格式。不要在此文件中包含任何动画。然后无需进行其他修改。

接下来,对于您想要实现的任何动画效果,例如行走、奔跑、跳舞等,请按照以下步骤操作:

在Mixamo上测试/应用所需的动画效果,并根据需要调整设置,然后下载它。在这里非常重要的是以Collada(DAE)格式下载并选择“不带皮肤”!保留默认的帧速率和关键帧缩减。

这将为您提供每个要实现的动画的单个DAE文件。此DAE文件不包含网格数据和骨架。它仅包含模型的变形效果(这就是为什么您选择不带皮肤下载它的原因)。

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

首先,您需要美化每个包含动画的DAE的XML结构。您可以使用Notepad++中的XML工具来完成此操作,或者在Mac上打开终端并使用此命令:

xmllint —-format my_anim_orig.dae > my_anim.dae

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

使用此转换器将所有DAE动画转换为Collada格式: (但不要使用此工具转换您的T-Pose模型!!!) Collada转换器

现在我们准备设置动画:

您应该将DAE文件组织在art.scnassets文件夹中 DAE文件

让我们进行配置:

我通常会在一个名为 characters 的 struct 中组织它。但是任何其他实现都可以。

添加这个:

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

然后你可以在viewDidLoad中初始化结构体i.Ex. var characters = Characters()

注意使用正确的childNodes! child nodes

在这种情况下,childNodes[1]是可见网格,childNodes[0]将是动画节点。

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

extension SCNNode {
    convenience init(named name: String) {
        self.init()
        guard let scene = SCNScene(named: name) else {return}
        for childNode in scene.rootNode.childNodes {addChildNode(childNode)}
    }
}

同时添加下面的扩展。稍后您将需要它来播放动画。

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

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

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

其余的部分您会自己解决的,如果您有问题或需要完整的示例应用程序,请回来找我 :)


1
令人难以置信!!!!!正在努力地工作中。。。 - Fattie
1
我不敢相信...它在@ZAY上工作了!!!!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! !!!!!!!!!!!! - Fattie
亲爱的Fattie,你一如既往地受欢迎 ;) - ZAY
1
哇!我没想到Mixamo有一个“原地”按钮,如果你正在进行根动作,那太棒了。我发现了一些小问题!对于其他在这里搜索的人:(1)实际上,在当前的Mac上(在我的Mac上),你只需要使用xmllint anim.dae > fixed.dae;如果你使用--format参数,它似乎什么也不做,并抛出一个无意义的错误...(2)当你使用上下文菜单中的“ConvertToXcodeCollada”时,它会创建一个额外的“.dae-e”文件,它只是原始文件,可以被丢弃。 - Fattie
谢谢,现在我明白了...回头见 - ZAY
显示剩余3条评论

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