155 lines
5.5 KiB
C#
155 lines
5.5 KiB
C#
#if UNITY_EDITOR
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEditor.Animations;
|
|
using UnityEngine;
|
|
|
|
public static class AnimatorSingleLayerizer
|
|
{
|
|
[MenuItem("Tools/Animator/Make Single-Layer Copy (Selected Controller)")]
|
|
public static void MakeSingleLayerCopy()
|
|
{
|
|
var srcCtrl = Selection.activeObject as AnimatorController;
|
|
if (!srcCtrl)
|
|
{
|
|
EditorUtility.DisplayDialog("Select AnimatorController",
|
|
"Select an AnimatorController in the Project view first.", "OK");
|
|
return;
|
|
}
|
|
|
|
var srcPath = AssetDatabase.GetAssetPath(srcCtrl);
|
|
var dir = Path.GetDirectoryName(srcPath).Replace('\\','/');
|
|
var nameNoExt = Path.GetFileNameWithoutExtension(srcPath);
|
|
var dstPath = AssetDatabase.GenerateUniqueAssetPath($"{dir}/{nameNoExt}_SingleLayer.controller");
|
|
|
|
if (!AssetDatabase.CopyAsset(srcPath, dstPath))
|
|
{
|
|
EditorUtility.DisplayDialog("Copy failed", "Could not duplicate controller.", "OK");
|
|
return;
|
|
}
|
|
|
|
AssetDatabase.SaveAssets();
|
|
AssetDatabase.Refresh();
|
|
|
|
var dstCtrl = AssetDatabase.LoadAssetAtPath<AnimatorController>(dstPath);
|
|
if (!dstCtrl)
|
|
{
|
|
EditorUtility.DisplayDialog("Load failed", "Could not load duplicated controller.", "OK");
|
|
return;
|
|
}
|
|
|
|
// Base layer to keep
|
|
var baseLayer = dstCtrl.layers[0];
|
|
var baseSM = baseLayer.stateMachine;
|
|
|
|
// Migrate all extra layers into Base as sub state machines
|
|
var layers = dstCtrl.layers;
|
|
var migrated = 0;
|
|
for (int i = 1; i < layers.Length; i++)
|
|
{
|
|
var srcLayer = layers[i];
|
|
var sub = baseSM.AddStateMachine($"L_{srcLayer.name}");
|
|
CopyStateMachineRecursively(srcLayer.stateMachine, sub);
|
|
migrated++;
|
|
}
|
|
|
|
// Remove extra layers (only Base remains)
|
|
dstCtrl.layers = new[] { baseLayer };
|
|
|
|
EditorUtility.SetDirty(dstCtrl);
|
|
AssetDatabase.SaveAssets();
|
|
Debug.Log($"[SingleLayerizer] Created: {dstPath}\nMigrated layers: {migrated}\nBase layer kept: {baseLayer.name}");
|
|
EditorGUIUtility.PingObject(dstCtrl);
|
|
}
|
|
|
|
// --------- Copy helpers ---------
|
|
|
|
static void CopyStateMachineRecursively(AnimatorStateMachine src, AnimatorStateMachine dst)
|
|
{
|
|
// Maps to reconnect transitions
|
|
var stateMap = new Dictionary<AnimatorState, AnimatorState>();
|
|
var smMap = new Dictionary<AnimatorStateMachine, AnimatorStateMachine> { { src, dst } };
|
|
|
|
// Copy child states
|
|
foreach (var cs in src.states)
|
|
{
|
|
var s = cs.state;
|
|
var newS = dst.AddState(s.name);
|
|
newS.motion = s.motion;
|
|
newS.speed = s.speed;
|
|
newS.speedParameter = s.speedParameter;
|
|
newS.speedParameterActive = s.speedParameterActive;
|
|
newS.writeDefaultValues = s.writeDefaultValues;
|
|
newS.mirror = s.mirror;
|
|
newS.mirrorParameter = s.mirrorParameter;
|
|
newS.mirrorParameterActive = s.mirrorParameterActive;
|
|
#if UNITY_2021_2_OR_NEWER
|
|
newS.timeParameter = s.timeParameter;
|
|
newS.timeParameterActive = s.timeParameterActive;
|
|
#endif
|
|
stateMap[s] = newS;
|
|
}
|
|
|
|
// Copy child state machines (recursive)
|
|
foreach (var csm in src.stateMachines)
|
|
{
|
|
var newSM = dst.AddStateMachine(csm.stateMachine.name);
|
|
smMap[csm.stateMachine] = newSM;
|
|
CopyStateMachineRecursively(csm.stateMachine, newSM);
|
|
}
|
|
|
|
// Default state
|
|
if (src.defaultState && stateMap.TryGetValue(src.defaultState, out var mappedDefault))
|
|
dst.defaultState = mappedDefault;
|
|
|
|
// AnyState transitions (only to states; ignore to sub-SMs)
|
|
foreach (var t in src.anyStateTransitions)
|
|
{
|
|
if (t.destinationState && stateMap.TryGetValue(t.destinationState, out var to))
|
|
{
|
|
var nt = dst.AddAnyStateTransition(to);
|
|
CopyTransitionSettings(t, nt);
|
|
}
|
|
}
|
|
|
|
// State → State transitions
|
|
foreach (var cs in src.states)
|
|
{
|
|
var from = cs.state;
|
|
if (!stateMap.TryGetValue(from, out var mappedFrom)) continue;
|
|
|
|
foreach (var t in from.transitions)
|
|
{
|
|
if (t.destinationState && stateMap.TryGetValue(t.destinationState, out var to))
|
|
{
|
|
var nt = mappedFrom.AddTransition(to);
|
|
CopyTransitionSettings(t, nt);
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: Entry transitions and StateMachine → StateMachine transitions are skipped
|
|
// because AnimatorTransition doesn't expose the same API surface.
|
|
// DefaultState should cover standard entry behavior.
|
|
}
|
|
|
|
static void CopyTransitionSettings(AnimatorStateTransition src, AnimatorStateTransition dst)
|
|
{
|
|
dst.hasExitTime = src.hasExitTime;
|
|
dst.exitTime = src.exitTime;
|
|
dst.hasFixedDuration = src.hasFixedDuration;
|
|
dst.duration = src.duration;
|
|
dst.offset = src.offset;
|
|
dst.interruptionSource = src.interruptionSource;
|
|
dst.orderedInterruption = src.orderedInterruption;
|
|
dst.canTransitionToSelf = src.canTransitionToSelf;
|
|
|
|
// Conditions
|
|
foreach (var c in src.conditions)
|
|
dst.AddCondition(c.mode, c.threshold, c.parameter);
|
|
}
|
|
}
|
|
#endif
|