139 lines
5.1 KiB
C#
139 lines
5.1 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using Sirenix.OdinInspector;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor.Animations;
|
|
#endif
|
|
|
|
namespace Cielonos.MainGame.Characters
|
|
{
|
|
// ============================================================================
|
|
// AnimatorStateMapper (nested in AnimationSubcontrollerBase via partial)
|
|
// ============================================================================
|
|
|
|
public partial class AnimationSubcontrollerBase
|
|
{
|
|
/// <summary>
|
|
/// 将 Animator 状态机中的 State 名称映射到对应 AnimationClip 的查询器。
|
|
/// 通过 Bake 按钮在编辑器中预先建立映射表,运行时以 O(1) 速度查询。
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class AnimatorStateMapper
|
|
{
|
|
[Title("Bake Settings")]
|
|
[SerializeField, LabelText("Target Animator")]
|
|
private Animator _targetAnimatorForBake;
|
|
|
|
[TableList(ShowIndexLabels = true), Searchable]
|
|
[SerializeField]
|
|
private List<StateClipPair> _mappings = new List<StateClipPair>();
|
|
|
|
private Dictionary<string, AnimationClip> _clipDict;
|
|
private bool _isInitialized = false;
|
|
|
|
[System.Serializable]
|
|
public struct StateClipPair
|
|
{
|
|
[ReadOnly] public string stateName;
|
|
public AnimationClip clip;
|
|
}
|
|
|
|
public AnimatorStateMapper()
|
|
{
|
|
_mappings = new List<StateClipPair>();
|
|
}
|
|
|
|
// ── Runtime API ───────────────────────────────────────────────────
|
|
|
|
/// <summary>必须在宿主的 Awake / Initialize 中调用,构建运行时索引。</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)
|
|
_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 ─────────────────────────────────────────────────
|
|
|
|
#if UNITY_EDITOR
|
|
public void Bake(Animator animator)
|
|
{
|
|
_targetAnimatorForBake = animator;
|
|
|
|
var controller = _targetAnimatorForBake.runtimeAnimatorController as AnimatorController;
|
|
|
|
if (controller == null &&
|
|
_targetAnimatorForBake.runtimeAnimatorController is AnimatorOverrideController oc)
|
|
controller = oc.runtimeAnimatorController as AnimatorController;
|
|
|
|
if (controller == null)
|
|
{
|
|
Debug.LogError("Animator 上未找到有效的 AnimatorController 资源!");
|
|
return;
|
|
}
|
|
|
|
_mappings.Clear();
|
|
int count = 0;
|
|
foreach (var layer in controller.layers)
|
|
count += RecursiveProcessStateMachine(layer.stateMachine);
|
|
|
|
Debug.Log($"<color=green>Bake 完成!共提取了 {count} 个 State-Clip 映射。</color>");
|
|
}
|
|
|
|
private int RecursiveProcessStateMachine(AnimatorStateMachine sm)
|
|
{
|
|
int count = 0;
|
|
foreach (var cs in sm.states)
|
|
{
|
|
if (cs.state.motion is AnimationClip clip)
|
|
{
|
|
_mappings.Add(new StateClipPair { stateName = cs.state.name, clip = clip });
|
|
count++;
|
|
}
|
|
}
|
|
foreach (var childSm in sm.stateMachines)
|
|
count += RecursiveProcessStateMachine(childSm.stateMachine);
|
|
return count;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// ── Editor Buttons ─────────────────────────────────────────────────────────
|
|
|
|
#if UNITY_EDITOR
|
|
public partial class AnimationSubcontrollerBase
|
|
{
|
|
[Button]
|
|
private void MapperBake()
|
|
{
|
|
mapper ??= new AnimatorStateMapper();
|
|
mapper.Bake(animator);
|
|
}
|
|
}
|
|
#endif
|
|
} |