#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) .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 BuildStateMap(List clips) { var map = new Dictionary(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 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