using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEditor.Animations; public class AnimatorParameterDetailAnalyzer : EditorWindow { private AnimatorController selectedController; private Dictionary parameterUsageData = new Dictionary(); private Vector2 scrollPosition; private string analysisResult = ""; private bool showRawData = false; private string targetLayerName = ""; [MenuItem("Tools/Animator Parameter Detail Analyzer")] public static void ShowWindow() { GetWindow("Animator Parameter Detail Analyzer"); } private void OnGUI() { GUILayout.Label("Animator Parameter Detail Analyzer", EditorStyles.boldLabel); EditorGUI.BeginChangeCheck(); selectedController = (AnimatorController)EditorGUILayout.ObjectField("Animator Controller", selectedController, typeof(AnimatorController), false); if (EditorGUI.EndChangeCheck()) { AnalyzeController(); } targetLayerName = EditorGUILayout.TextField("Target Layer Name (Empty for all)", targetLayerName); if (GUILayout.Button("Analyze Selected Controller")) { AnalyzeController(); } showRawData = EditorGUILayout.Toggle("Show Raw Data", showRawData); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); EditorGUILayout.TextArea(analysisResult, GUILayout.ExpandHeight(true)); EditorGUILayout.EndScrollView(); } private void AnalyzeController() { parameterUsageData.Clear(); analysisResult = ""; if (selectedController == null) { Debug.LogWarning("No Animator Controller selected."); return; } StringBuilder sb = new StringBuilder(); sb.AppendLine($"=== Animator Controller Analysis: {selectedController.name} ==="); if (string.IsNullOrEmpty(targetLayerName)) { sb.AppendLine("Analyzing ALL layers"); } else { sb.AppendLine($"Analyzing layer: {targetLayerName}"); } sb.AppendLine(); foreach (var param in selectedController.parameters) { parameterUsageData[param.name] = new ParameterUsageData(param); } foreach (var layer in selectedController.layers) { if (string.IsNullOrEmpty(targetLayerName) || layer.name == targetLayerName) { AnalyzeStateMachine(layer.stateMachine, layer.name); } } foreach (var paramData in parameterUsageData.Values.OrderBy(d => d.parameter.name)) { AppendParameterReport(sb, paramData); } if (showRawData) { sb.AppendLine(); sb.AppendLine("=== Raw Transition Data ==="); foreach (var layer in selectedController.layers) { if (string.IsNullOrEmpty(targetLayerName) || layer.name == targetLayerName) { AppendRawTransitionData(sb, layer.stateMachine, layer.name); } } } analysisResult = sb.ToString(); } private void AppendParameterReport(StringBuilder sb, ParameterUsageData paramData) { var param = paramData.parameter; sb.AppendLine($"Parameter: {param.name} ({param.type})"); sb.Append(" Possible Values: "); switch (param.type) { case AnimatorControllerParameterType.Int: sb.AppendLine(string.Join(", ", paramData.possibleIntValues.OrderBy(v => v).Select(v => v.ToString()))); break; case AnimatorControllerParameterType.Float: sb.AppendLine(string.Join(", ", paramData.possibleFloatValues.OrderBy(v => v).Select(v => v.ToString("F2")))); break; case AnimatorControllerParameterType.Bool: sb.AppendLine(string.Join(", ", paramData.possibleBoolValues.Select(v => v.ToString()))); break; case AnimatorControllerParameterType.Trigger: sb.AppendLine("Trigger (used in conditions)"); break; } if (paramData.transitionUsages.Count > 0) { sb.AppendLine(" Used in Transitions:"); foreach (var usage in paramData.transitionUsages) { sb.AppendLine($" {usage.fromState} -> {usage.toState} (Layer: {usage.layerName})"); sb.AppendLine($" Condition: {param.name} {GetConditionSymbol(usage.condition.mode)} {usage.condition.threshold}"); } } else { sb.AppendLine(" Not used in any transitions (may be used in scripts)"); } sb.AppendLine(); } private string GetConditionSymbol(AnimatorConditionMode mode) { switch (mode) { case AnimatorConditionMode.If: return "=="; case AnimatorConditionMode.IfNot: return "!="; case AnimatorConditionMode.Greater: return ">"; case AnimatorConditionMode.Less: return "<"; case AnimatorConditionMode.Equals: return "=="; case AnimatorConditionMode.NotEqual: return "!="; default: return "?"; } } private void AnalyzeStateMachine(AnimatorStateMachine stateMachine, string layerName) { foreach (var childState in stateMachine.states) { AnalyzeState(childState.state, layerName); } foreach (var childStateMachine in stateMachine.stateMachines) { AnalyzeStateMachine(childStateMachine.stateMachine, layerName); } } private void AnalyzeState(AnimatorState state, string layerName) { foreach (var transition in state.transitions) { AnalyzeTransition(transition, state.name, layerName); } } private void AnalyzeTransition(AnimatorStateTransition transition, string fromStateName, string layerName) { foreach (var condition in transition.conditions) { if (!parameterUsageData.ContainsKey(condition.parameter)) continue; var paramData = parameterUsageData[condition.parameter]; var usage = new TransitionUsage { layerName = layerName, fromState = fromStateName, toState = transition.destinationState?.name ?? "Exit", condition = condition }; paramData.transitionUsages.Add(usage); switch (paramData.parameter.type) { case AnimatorControllerParameterType.Int: paramData.possibleIntValues.Add((int)condition.threshold); break; case AnimatorControllerParameterType.Float: paramData.possibleFloatValues.Add(condition.threshold); break; case AnimatorControllerParameterType.Bool: paramData.possibleBoolValues.Add(condition.threshold > 0.5f); break; } } } private void AppendRawTransitionData(StringBuilder sb, AnimatorStateMachine stateMachine, string layerName) { foreach (var childState in stateMachine.states) { foreach (var transition in childState.state.transitions) { sb.AppendLine($"{layerName}: {childState.state.name} -> {transition.destinationState?.name ?? "Exit"}"); foreach (var condition in transition.conditions) { sb.AppendLine($" {condition.parameter} {condition.mode} {condition.threshold}"); } } } foreach (var childStateMachine in stateMachine.stateMachines) { AppendRawTransitionData(sb, childStateMachine.stateMachine, layerName); } } private class ParameterUsageData { public AnimatorControllerParameter parameter; public HashSet possibleIntValues = new HashSet(); public HashSet possibleFloatValues = new HashSet(); public HashSet possibleBoolValues = new HashSet(); public List transitionUsages = new List(); public ParameterUsageData(AnimatorControllerParameter param) { parameter = param; switch (param.type) { case AnimatorControllerParameterType.Int: possibleIntValues.Add(param.defaultInt); break; case AnimatorControllerParameterType.Float: possibleFloatValues.Add(param.defaultFloat); break; case AnimatorControllerParameterType.Bool: possibleBoolValues.Add(param.defaultBool); break; } } } private class TransitionUsage { public string layerName; public string fromState; public string toState; public AnimatorCondition condition; } }