Files
Cielonos/Assets/Scripts/MainGame/Characters/Base/Subcontrollers/Animation/AnimatorClipMap.cs
SoulliesOfficial 33b1795c1f 更新
2026-01-03 18:19:39 -05:00

160 lines
5.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}