Skip to content

Instantly share code, notes, and snippets.

@adammyhre
Last active June 29, 2024 06:53
Show Gist options
  • Save adammyhre/cd97c93e82d1c75931c22900be24e462 to your computer and use it in GitHub Desktop.
Save adammyhre/cd97c93e82d1c75931c22900be24e462 to your computer and use it in GitHub Desktop.
Code to clone an existing Animator Controller Asset with or without Motions
using UnityEditor;
using UnityEditor.Animations;
public static class AnimatorControllerCloneTool {
[MenuItem("Assets/Clone Animator Controller", true)]
static bool CanCloneAnimatorController() {
return Selection.activeObject is AnimatorController;
}
[MenuItem("Assets/Clone Animator Controller")]
static void CloneAnimatorController() {
if (Selection.activeObject is AnimatorController sourceController) {
string path = EditorUtility.SaveFilePanelInProject(
"Save Cloned Animator Controller",
$"{sourceController.name}_Cloned",
"controller",
"Specify where to save the cloned animator controller.");
if (!string.IsNullOrEmpty(path)) {
AnimatorController destinationController = new AnimatorController();
AssetDatabase.CreateAsset(destinationController, path);
sourceController.CloneWithoutMotions(destinationController);
EditorUtility.DisplayDialog("Success", "Animator Controller cloned successfully.", "OK");
}
}
else {
EditorUtility.DisplayDialog("Error", "Please select an AnimatorController to clone.", "OK");
}
}
}
using System;
using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;
using System.Linq;
public static class AnimatorControllerExtensions {
public static AnimatorController Clone(this AnimatorController source, AnimatorController target) {
return target.CopyLayers(source)
.ClearParameters()
.CopyParameters(source)
.CopyStatesAndTransitions(source);
}
public static AnimatorController CloneWithoutMotions(this AnimatorController source, AnimatorController target) {
return target.CopyLayers(source)
.ClearParameters()
.CopyParameters(source)
.CopyStatesAndTransitions(source, false);
}
static AnimatorController CopyLayers(this AnimatorController target, AnimatorController source) {
target.layers = Array.Empty<AnimatorControllerLayer>();
foreach (var layer in source.layers) {
var newLayer = new AnimatorControllerLayer {
name = MakeUniqueLayerName(target, layer.name),
avatarMask = layer.avatarMask,
blendingMode = layer.blendingMode,
syncedLayerIndex = layer.syncedLayerIndex,
iKPass = layer.iKPass,
defaultWeight = layer.defaultWeight,
syncedLayerAffectsTiming = layer.syncedLayerAffectsTiming,
stateMachine = new AnimatorStateMachine {
name = layer.name,
hideFlags = HideFlags.HideInHierarchy
}
};
if (AssetDatabase.GetAssetPath(target) != "") {
AssetDatabase.AddObjectToAsset(newLayer.stateMachine, AssetDatabase.GetAssetPath(target));
}
target.AddLayer(newLayer);
}
return target;
}
static AnimatorController ClearParameters(this AnimatorController target) {
target.parameters = Array.Empty<AnimatorControllerParameter>();
return target;
}
static AnimatorController CopyParameters(this AnimatorController target, AnimatorController source) {
foreach (var param in source.parameters) {
target.AddParameter(param.name, param.type);
}
return target;
}
static AnimatorController CopyStatesAndTransitions(this AnimatorController target, AnimatorController source, bool withMotions = true) {
for (int i = 0; i < source.layers.Length; i++) {
var sourceLayer = source.layers[i];
var destLayer = target.layers[i];
CopyStates(sourceLayer.stateMachine, destLayer.stateMachine, withMotions);
CopyTransitions(sourceLayer.stateMachine, destLayer.stateMachine);
SetDefaultState(sourceLayer.stateMachine, destLayer.stateMachine);
}
return target;
}
static void CopyStates(AnimatorStateMachine source, AnimatorStateMachine target, bool withMotions = true) {
target.exitPosition = source.exitPosition;
target.entryPosition = source.entryPosition;
target.anyStatePosition = source.anyStatePosition;
foreach (var state in source.states) {
var newState = target.AddState(state.state.name, state.position);
newState.speed = state.state.speed;
newState.motion = withMotions ? CopyMotion(state.state.motion) : default;
}
foreach (var subStateMachine in source.stateMachines) {
var newSubStateMachine = target.AddStateMachine(subStateMachine.stateMachine.name, subStateMachine.position);
CopyStates(subStateMachine.stateMachine, newSubStateMachine, withMotions);
}
}
static void CopyTransitions(AnimatorStateMachine source, AnimatorStateMachine target) {
foreach (var transition in source.anyStateTransitions) {
var newTransition = target.AddAnyStateTransition(transition.destinationState);
CopyTransitionProperties(newTransition, transition);
}
foreach (var transition in source.entryTransitions) {
var newTransition = target.AddEntryTransition(transition.destinationState);
CopyTransitionProperties(newTransition, transition);
}
foreach (var state in source.states) {
foreach (var transition in state.state.transitions) {
var destState = FindStateByName(target, state.state.name);
var newTransition = destState.AddTransition(FindStateByName(target, transition.destinationState.name));
CopyTransitionProperties(newTransition, transition);
}
}
}
static void SetDefaultState(AnimatorStateMachine source, AnimatorStateMachine target) {
target.defaultState = FindStateByName(target, source.defaultState.name);
}
static AnimatorState FindStateByName(AnimatorStateMachine stateMachine, string stateName) {
return stateMachine.states.FirstOrDefault(s => s.state.name == stateName).state;
}
static Motion CopyMotion(Motion motion) {
switch (motion) {
case AnimationClip clip:
return clip;
case BlendTree oldBlendTree: {
var newBlendTree = new BlendTree {
blendParameter = oldBlendTree.blendParameter,
blendType = oldBlendTree.blendType,
children = oldBlendTree.children
.Select(c => new ChildMotion { motion = c.motion, threshold = c.threshold }).ToArray()
};
return newBlendTree;
}
default:
return null;
}
}
static void CopyTransitionProperties(AnimatorTransitionBase target, AnimatorTransitionBase source) {
if (target is not AnimatorStateTransition stateTransitionDest || source is not AnimatorStateTransition stateTransitionSource) return;
stateTransitionDest.conditions = stateTransitionSource.conditions;
stateTransitionDest.canTransitionToSelf = stateTransitionSource.canTransitionToSelf;
stateTransitionDest.hasExitTime = stateTransitionSource.hasExitTime;
stateTransitionDest.exitTime = stateTransitionSource.exitTime;
stateTransitionDest.duration = stateTransitionSource.duration;
stateTransitionDest.interruptionSource = stateTransitionSource.interruptionSource;
}
static string MakeUniqueLayerName(AnimatorController controller, string baseName) {
var name = baseName;
var counter = 1;
while (controller.layers.Any(layer => layer.name == name)) {
name = $"{baseName} {counter++}";
}
return name;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment