160 lines
5.2 KiB
C#
160 lines
5.2 KiB
C#
using UnityEngine;
|
||
using System.Collections.Generic;
|
||
using Sirenix.OdinInspector; // 引入 Odin 命名空间
|
||
|
||
#if UNITY_EDITOR
|
||
using UnityEditor.Animations; // 仅编辑器下引用,用于解析Controller
|
||
#endif
|
||
namespace Cielonos.MainGame.Characters
|
||
{
|
||
public class AnimatorStateMapper
|
||
{
|
||
// --- 配置区域 ---
|
||
[Title("Bake Settings")]
|
||
[SerializeField, LabelText("Target Animator")]
|
||
private Animator _targetAnimatorForBake;
|
||
|
||
[TableList(ShowIndexLabels = true), Searchable] // Odin: 列表显示为表格,且支持搜索
|
||
[SerializeField]
|
||
private List<StateClipPair> _mappings = new List<StateClipPair>();
|
||
|
||
// --- 运行时缓存 ---
|
||
private Dictionary<string, AnimationClip> _clipDict;
|
||
private bool _isInitialized = false;
|
||
|
||
// --- 简单的数据结构 ---
|
||
[System.Serializable]
|
||
public struct StateClipPair
|
||
{
|
||
[ReadOnly] // 防止手动误改,建议通过Bake生成
|
||
public string stateName;
|
||
|
||
public AnimationClip clip;
|
||
}
|
||
|
||
public AnimatorStateMapper()
|
||
{
|
||
_mappings = new List<StateClipPair>();
|
||
}
|
||
|
||
// ========================================================================
|
||
// Runtime API (供宿主调用)
|
||
// ========================================================================
|
||
|
||
/// <summary>
|
||
/// 必须在宿主的 Awake 中调用此方法构建索引
|
||
/// </summary>
|
||
public void Initialize()
|
||
{
|
||
if (_isInitialized) return;
|
||
|
||
_clipDict = new Dictionary<string, AnimationClip>(_mappings.Count);
|
||
foreach (var pair in _mappings)
|
||
{
|
||
if (!string.IsNullOrEmpty(pair.stateName) && pair.clip != null)
|
||
{
|
||
// 防止重复Key报错,以后面的覆盖前面的(或者你可以选择忽略)
|
||
_clipDict[pair.stateName] = pair.clip;
|
||
}
|
||
}
|
||
|
||
_isInitialized = true;
|
||
}
|
||
|
||
public AnimationClip GetClip(string stateName)
|
||
{
|
||
if (!_isInitialized) Initialize();
|
||
|
||
if (_clipDict.TryGetValue(stateName, out var clip))
|
||
{
|
||
return clip;
|
||
}
|
||
|
||
Debug.LogWarning($"[AnimatorStateMapper] 未找到 State: '{stateName}' 对应的 Clip。请检查是否已 Bake 或 State 名字是否正确。");
|
||
return null;
|
||
}
|
||
|
||
public float GetClipLength(string stateName)
|
||
{
|
||
var clip = GetClip(stateName);
|
||
return clip != null ? clip.length : 0f;
|
||
}
|
||
|
||
// ========================================================================
|
||
// Editor Baking Logic (Odin Button)
|
||
// ========================================================================
|
||
|
||
#if UNITY_EDITOR
|
||
public void Bake(Animator animator)
|
||
{
|
||
_targetAnimatorForBake = animator;
|
||
|
||
var controller = _targetAnimatorForBake.runtimeAnimatorController as AnimatorController;
|
||
|
||
// 处理 Override Controller 的情况
|
||
if (controller == null && _targetAnimatorForBake.runtimeAnimatorController is AnimatorOverrideController overrideCtrl)
|
||
{
|
||
controller = overrideCtrl.runtimeAnimatorController as AnimatorController;
|
||
}
|
||
|
||
if (controller == null)
|
||
{
|
||
Debug.LogError("Animator 上未找到有效的 AnimatorController (或 AnimatorOverrideController) 资源!");
|
||
return;
|
||
}
|
||
|
||
_mappings.Clear();
|
||
int count = 0;
|
||
|
||
// 递归遍历所有的 Layer 和 SubStateMachine
|
||
foreach (var layer in controller.layers)
|
||
{
|
||
count += RecursiveProcessStateMachine(layer.stateMachine);
|
||
}
|
||
|
||
Debug.Log($"<color=green>Bake 完成!共提取了 {count} 个 State-Clip 映射。</color>");
|
||
}
|
||
|
||
private int RecursiveProcessStateMachine(AnimatorStateMachine stateMachine)
|
||
{
|
||
int count = 0;
|
||
|
||
// 1. 遍历当前层级的 State
|
||
foreach (var childState in stateMachine.states)
|
||
{
|
||
var state = childState.state;
|
||
var motion = state.motion;
|
||
|
||
// 仅处理直接引用 AnimationClip 的情况
|
||
if (motion is AnimationClip clip)
|
||
{
|
||
_mappings.Add(new StateClipPair { stateName = state.name, clip = clip });
|
||
count++;
|
||
}
|
||
// TODO: 如果需要支持 BlendTree,可以在这里扩展逻辑
|
||
// else if (motion is BlendTree tree) { ... }
|
||
}
|
||
|
||
// 2. 递归遍历子状态机 (Sub-State Machines)
|
||
foreach (var childMachine in stateMachine.stateMachines)
|
||
{
|
||
count += RecursiveProcessStateMachine(childMachine.stateMachine);
|
||
}
|
||
|
||
return count;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
public partial class AnimationSubcontrollerBase
|
||
{
|
||
[Button]
|
||
private void MapperBake()
|
||
{
|
||
mapper ??= new AnimatorStateMapper();
|
||
mapper.Bake(animator);
|
||
}
|
||
}
|
||
#endif
|
||
} |