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

bhmjp9jg  于 2023-01-14  发布在  iOS
关注(0)|答案(1)|浏览(166)

访问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)

(To获取几何体名称,只需查看.dae文本example
你会完美地看到这个角色,在T型姿势
但是,似乎无法在角色上运行动画。
代码看起来像...

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

不管我怎么努力它就是不动。
在添加动画时,角色会跳到不同的静态位置,并且不执行任何操作。(如果您安排“结束”动画removeAllAnimations(),则它只是返回到T姿势。)
显然,dae文件是完美的,因为显示动画完美简单地在Mac查找器,和完美在实际屏幕上的.dae文件在Xcode!
简而言之,从上面的mixamo图片来看,有人能够让动画在SceneKit场景中运行吗
(PS不是ARKit..场景工具包。)

rmbxnbpk

rmbxnbpk1#

首先,你只需要你的角色在T位置。下载该文件作为Collada(DAE)与皮肤。不要包括任何动画到这个文件。没有进一步的修改需要这个文件。
然后,对于任何动画效果,你会实现像走,跑,跳舞,或什么-这样做:
测试/应用您想要的动画在Mixamo的字符,调整设置,然后下载它。这里是非常重要的下载为Collada(DAE),并选择无皮肤!!!
这将为您要实现的每个动画给予一个DAE文件。此DAE不包含网格数据和装备。它只包含其所属模型的变形(这就是您选择下载不带蒙皮的DAE文件的原因)。
然后,您需要对包含动画的所有DAE文件执行两个附加操作。
首先,您需要精确打印每个包含动画的DAE的XML结构。您可以使用Notepad++中的XML工具在i.Ex.中完成此操作,或者在Mac上打开一个终端并使用以下命令:

xmllint —-format my_anim_orig.dae > my_anim.dae

然后在Mac上安装此工具。(https://drive.google.com/file/d/0B1_uvI21ZYGUaGdJckdwaTRZUEk/edit?usp=sharing)x1c 0d1x
使用此转换器转换所有DAE动画:(但不要使用此工具转换您的T型模型!!!)

否,我们已准备好设置动画:
您应在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变量characters = Characters()中初始化结构i.Ex.
注意使用正确的子节点!

在这种情况下,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()
        
    }
    
}

在你初始化字符结构之后,你可以初始化类对象。
剩下的你会弄清楚,回来找我,如果你有问题或需要一个完整的示例应用程序:)

相关问题