MiniGames/Assets/Editor/AnimatorSingleLayerizer.cs
2025-08-14 20:29:09 +05:00

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