Last active
September 23, 2024 03:15
-
-
Save adammyhre/cd97c93e82d1c75931c22900be24e462 to your computer and use it in GitHub Desktop.
Code to clone an existing Animator Controller Asset with or without Motions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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