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

194 lines
7.1 KiB
C#

#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
public static class ZibuAnimatorSync
{
const string AnimationsFolder = "Assets/Animations"; // change if your clips elsewhere
[MenuItem("Tools/Zibu/Update Selected Animator From Clips")]
public static void UpdateAnimatorFromClips()
{
var ctrl = Selection.activeObject as AnimatorController;
if (!ctrl)
{
EditorUtility.DisplayDialog("Select Animator",
"Select an AnimatorController in the Project view, then run this.",
"OK");
return;
}
// Load all clips under folder
var clips = AssetDatabase.FindAssets("t:AnimationClip", new[] { AnimationsFolder })
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<AnimationClip>)
.Where(c => c != null && c.name != "__preview__")
.ToList();
if (clips.Count == 0)
{
EditorUtility.DisplayDialog("No Clips",
$"No AnimationClips found under {AnimationsFolder}.", "OK");
return;
}
// Build mapping: stateName (enum-like) -> clip
var map = BuildStateMap(clips);
// Ensure base layer exists
var baseLayer = ctrl.layers[0];
var sm = baseLayer.stateMachine;
// Create/update states for each mapped entry
int created = 0, updated = 0;
foreach (var kv in map)
{
var stateName = kv.Key; // e.g., "FallingLoop"
var clip = kv.Value;
var st = sm.states.FirstOrDefault(s => s.state.name == stateName).state;
if (st == null)
{
st = sm.AddState(stateName);
created++;
}
if (st.motion != clip)
{
st.motion = clip;
updated++;
}
}
// Optional: Put Jump-related states in a sub-state machine
EnsureJumpSubMachine(ctrl, sm, map);
EditorUtility.SetDirty(ctrl);
AssetDatabase.SaveAssets();
Debug.Log($"[ZibuAnimatorSync] Done. States created: {created}, updated: {updated}");
}
// Normalize clip name to state key matching enum names
static string Normalize(string clipName)
{
// Remove 'Zibu_' prefix and common separators, then convert cases
var s = clipName;
if (s.StartsWith("Zibu_", StringComparison.OrdinalIgnoreCase)) s = s.Substring(5);
// Unify some common long names
s = s.Replace("Run_Lean_L", "RunLeanL")
.Replace("Run_Lean_R", "RunLeanR")
.Replace("Run_Backward", "RunBackward")
.Replace("Idle_Base_Add", "IdleBaseAdd")
.Replace("Jump_Start_IPC", "JumpStartIPC")
.Replace("Jump_IPC", "JumpIPC")
.Replace("JumpEnd_Add", "JumpEndAdd")
.Replace("JumpEndOld", "JumpEndOld")
.Replace("DoubleJump_inPlace_Short", "DoubleJumpInPlaceShort")
.Replace("DoubleJump_inPlace", "DoubleJumpInPlace")
.Replace("Shu_Charge", "ShuCharge")
.Replace("Reaction_Shu_Charge_L", "ReactionShuChargeL")
.Replace("Reaction_Shu_Charge_R", "ReactionShuChargeR")
.Replace("Side_Kick_L", "SideKickL")
.Replace("Side_Kick_R", "SideKickR")
.Replace("Reaction_Side_Kick_L", "ReactionSideKickL")
.Replace("Reaction_Side_Kick_R", "ReactionSideKickR")
.Replace("Falling_Loop", "FallingLoop")
.Replace("BlueberryBlast", "BlueberryBlast")
;
// Generic cleanup: underscores → PascalCase-ish
s = s.Replace("__", "_");
if (s.Contains("_"))
{
var parts = s.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
s = string.Concat(parts.Select(p => char.ToUpperInvariant(p[0]) + p.Substring(1)));
}
// Final trims
s = s.Replace(" ", "");
return s;
}
static Dictionary<string, AnimationClip> BuildStateMap(List<AnimationClip> clips)
{
var map = new Dictionary<string, AnimationClip>(StringComparer.OrdinalIgnoreCase);
foreach (var c in clips)
{
var key = Normalize(c.name);
// prefer specific (non-generic) names if duplicates arise
if (!map.ContainsKey(key))
map[key] = c;
}
return map;
}
static void EnsureJumpSubMachine(AnimatorController ctrl, AnimatorStateMachine rootSM, Dictionary<string, AnimationClip> map)
{
// Create a sub-SM named "Jump" and move specific states if they exist
var jumpSM = rootSM.stateMachines.FirstOrDefault(s => s.stateMachine.name == "Jump").stateMachine;
if (jumpSM == null)
{
jumpSM = rootSM.AddStateMachine("Jump");
// Place it nicely
jumpSM.entryPosition = new Vector3(200, 0);
jumpSM.anyStatePosition = new Vector3(200, 50);
}
string[] jumpStates = { "JumpStart", "Falling", "FallingLoop", "DoubleJump", "Land", "JumpEndAdd", "JumpEndOld" };
foreach (var js in jumpStates)
{
if (!map.TryGetValue(js, out var clip)) continue;
// Find existing state either in root or in jump SM
var existing = rootSM.states.FirstOrDefault(s => s.state.name == js).state;
if (existing != null)
{
// Move into jump SM (delete and recreate to avoid duplicate)
rootSM.RemoveState(existing);
var st = jumpSM.AddState(js);
st.motion = clip;
continue;
}
var inJump = jumpSM.states.FirstOrDefault(s => s.state.name == js).state;
if (inJump == null)
{
var st = jumpSM.AddState(js);
st.motion = clip;
}
else
{
if (inJump.motion != clip) inJump.motion = clip;
}
}
// Simple default transitions inside Jump SM
var stJumpStart = jumpSM.states.FirstOrDefault(s => s.state.name == "JumpStart").state;
var stFalling = jumpSM.states.FirstOrDefault(s => s.state.name == "FallingLoop").state
?? jumpSM.states.FirstOrDefault(s => s.state.name == "Falling").state;
var stLand = jumpSM.states.FirstOrDefault(s => s.state.name == "Land").state
?? jumpSM.states.FirstOrDefault(s => s.state.name == "JumpEndAdd").state;
if (jumpSM.defaultState == null && stJumpStart != null)
jumpSM.defaultState = stJumpStart;
if (stJumpStart != null && stFalling != null)
{
var t = stJumpStart.AddTransition(stFalling);
t.hasExitTime = true; t.exitTime = 0.9f; t.duration = 0.05f;
}
if (stFalling != null && stLand != null)
{
var t = stFalling.AddTransition(stLand);
t.hasExitTime = true; t.exitTime = 0.9f; t.duration = 0.05f;
}
}
}
#endif