Importing Collada Characters and Animations from Mixamo into Xcode


Mixamo is an Adobe online service that provides automatic character rigging and an extensive library of animations. However, these characters cannot be directly used in Xcode without some cleanup. Most of the heavy lifting is handled by xcode-collada, a CLI tool I wrote to automatically fix Collada files for Xcode.

Workflow

This assumes you already have an unrigged character model. The steps to get it animated and into Xcode are:

  1. Export your character in OBJ format
  2. Import into Mixamo and follow the rigging instructions
  3. Export the rigged character in a t-pose using Collada (DAE) format
  4. Export each animation without the skin in DAE format
  5. Clean up the DAE files using xcode-collada
  6. Add all DAE files to your Xcode project

Below are solutions to some of the common problems you may encounter along the way.

Mixamo Export Failures

Mixamo would occasionally time out without producing a file. The fix was to reduce the complexity of the model before uploading. If you are using Maya, the Reduce function works well for this — just avoid 100% reduction, as it can produce undesirable geometry.

Xcode Unable to Load Collada Files

Xcode is strict about how DAE files are structured. Files exported from Mixamo commonly have fragmented animation tags, spaces in IDs, and inconsistent formatting — all of which cause Xcode to reject the file or silently ignore the animation data. xcode-collada resolves these automatically.

Loading the Character

After cleanup, you should have a t-pose character file (without any animations) and one or more animation files (without the skin).

Load the t-pose scene and find the animation node. Collada files use an XML tree hierarchy, and SceneKit preserves this structure — so you can traverse the imported nodes to find the skeleton root:

guard let scene = SCNScene(named: "tpose.dae") else {
    fatalError("Failed to load main scene")
}

guard let animationsNode = scene.rootNode
    .childNode(withName: "mixamorig_Hips", recursively: true)?
    .parent else {
    fatalError("Unable to find character node")
}

Adding Animations

For each animation, load it and attach it to the character’s animation node:

guard let animation = SCNAnimationPlayer
    .loadAnimation(fromSceneNamed: "idle.dae") else {
    fatalError("Failed to load animation")
}
animationsNode.addAnimationPlayer(animation, forKey: "idle")

To switch between animations, stop all current playback and play the desired one by key:

for key in animationsNode.animationKeys {
    guard let player = animationsNode.animationPlayer(forKey: key) else { return }
    player.stop()
}

guard let player = animationsNode.animationPlayer(forKey: name) else { return }
player.play()