首先,您需要将角色放置在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模型!!!)
现在我们准备设置动画:
您应该将DAE文件组织在art.scnassets
文件夹中
让我们进行配置:
我通常会在一个名为 characters 的 struct
中组织它。但是任何其他实现都可以。
添加这个:
struct 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
}()
init() {
bodyWarrior = SCNNode(named: "art.scnassets/warrior/warrior.dae")
bodyWarrior.childNodes[1].geometry?.firstMaterial = objectMaterialWarrior
print("Characters Init Completed.")
}
}
然后你可以在viewDidLoad中初始化结构体i.Ex.
var characters = Characters()
注意使用正确的childNodes!
在这种情况下,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 )!
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 {
var node = SCNNode()
private var animNode : SCNNode!
var isIdle : Bool = true
private var position = SCNMatrix4Mult(SCNMatrix4MakeRotation(0,0,0,0), SCNMatrix4MakeTranslation(0,0,0))
private var scale = SCNMatrix4MakeScale(0.03, 0.03, 0.03)
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"
init(index: Int, scaleFactor: Float = 0.03) {
scale = SCNMatrix4MakeScale(scaleFactor, scaleFactor, scaleFactor)
node.index = index
node.name = "warrior"
node.addChildNode(GameViewController.characters.bodyWarrior.clone())
node.childNodes[0].transform = SCNMatrix4Mult(position, scale)
animNode = node.childNodes[0].childNodes[0]
gameScene.rootNode.addChildNode(node)
print("Warrior initialized with index: \(String(describing: node.index))")
}
func remove() {
print("Warrior deinitializing")
self.animNode.removeAllAnimations()
self.node.removeAllActions()
self.node.removeFromParentNode()
}
deinit { remove() }
func setPosition(position: SCNVector3) { self.node.position = position }
enum IdleType: Int {
case NeutralIdle
case DwarfIdle
case LookAroundIdle
}
func idle(type: IdleType) {
isIdle = true
var animationName : String = ""
var key : String = ""
switch type {
case .NeutralIdle: animationName = aniMAT_NeutralIdle ; key = aniKEY_NeutralIdle
case .DwarfIdle: animationName = aniMAT_DwarfIdle ; key = aniKEY_DwarfIdle
case .LookAroundIdle: animationName = aniMAT_LookAroundIdle ; key = aniKEY_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
}
}
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
node.addAnimationPlayer(anim, forKey: key)
node.animationPlayer(forKey: key)?.play()
}
}
在初始化字符结构之后,您可以初始化类对象。
其余的部分您会自己解决的,如果您有问题或需要完整的示例应用程序,请回来找我 :)
xmllint anim.dae > fixed.dae
;如果你使用--format
参数,它似乎什么也不做,并抛出一个无意义的错误...(2)当你使用上下文菜单中的“ConvertToXcodeCollada”时,它会创建一个额外的“.dae-e”文件,它只是原始文件,可以被丢弃。 - Fattie