194 lines
7.1 KiB
C#
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
|