379 lines
13 KiB
C#
379 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Newtonsoft.Json;
|
|
using UnityEngine;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor.Animations;
|
|
#endif
|
|
|
|
namespace AibisDream.Framework
|
|
{
|
|
public static class AnimatorKit
|
|
{
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
/// 解析 AnimatorController 下的所有混合树
|
|
/// </summary>
|
|
/// <param name="controller">AnimatorController 资产</param>
|
|
/// <param name="includeNested">是否包含嵌套的混合树(子混合树),默认为 true</param>
|
|
/// <returns>混合树信息列表</returns>
|
|
public static List<BlendTreeInfo> ParseBlendTree(AnimatorController controller, bool includeNested = true)
|
|
{
|
|
if (controller == null)
|
|
{
|
|
Debug.LogWarning("AnimatorController 为空");
|
|
return new List<BlendTreeInfo>();
|
|
}
|
|
|
|
var blendTreeInfos = new List<BlendTreeInfo>();
|
|
|
|
// 遍历所有层
|
|
foreach (var layer in controller.layers)
|
|
{
|
|
if (layer.stateMachine != null)
|
|
{
|
|
CollectBlendTreesFromStateMachine(layer.stateMachine, blendTreeInfos, includeNested);
|
|
}
|
|
}
|
|
|
|
return blendTreeInfos;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从状态机中收集混合树(递归处理子状态机)
|
|
/// </summary>
|
|
private static void CollectBlendTreesFromStateMachine(AnimatorStateMachine stateMachine, List<BlendTreeInfo> blendTreeInfos, bool includeNested)
|
|
{
|
|
if (stateMachine == null) return;
|
|
|
|
// 遍历所有状态
|
|
foreach (var state in stateMachine.states)
|
|
{
|
|
if (state.state?.motion is BlendTree blendTree)
|
|
{
|
|
CollectBlendTreeInfo(blendTree, blendTreeInfos, includeNested);
|
|
}
|
|
}
|
|
|
|
// 递归处理子状态机
|
|
foreach (var subStateMachine in stateMachine.stateMachines)
|
|
{
|
|
if (subStateMachine.stateMachine != null)
|
|
{
|
|
CollectBlendTreesFromStateMachine(subStateMachine.stateMachine, blendTreeInfos, includeNested);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 收集混合树信息(可选递归处理子混合树)
|
|
/// </summary>
|
|
/// <param name="blendTree">混合树</param>
|
|
/// <param name="blendTreeInfos">混合树信息列表</param>
|
|
/// <param name="includeNested">是否包含嵌套的混合树(子混合树)</param>
|
|
private static void CollectBlendTreeInfo(BlendTree blendTree, List<BlendTreeInfo> blendTreeInfos, bool includeNested)
|
|
{
|
|
if (blendTree == null) return;
|
|
|
|
var info = new BlendTreeInfo
|
|
{
|
|
name = blendTree.name,
|
|
minThreshold = blendTree.minThreshold,
|
|
maxThreshold = blendTree.maxThreshold,
|
|
blendParameter = blendTree.blendParameter,
|
|
blendParameterY = blendTree.blendParameterY
|
|
};
|
|
|
|
// 判断混合树类型
|
|
int blendType = (int)blendTree.blendType;
|
|
if (blendType == 0) // Simple1D
|
|
{
|
|
info.type = "1D";
|
|
info.clips = new List<BlendTreeClipInfo>();
|
|
|
|
if (blendTree.children != null)
|
|
{
|
|
foreach (var child in blendTree.children)
|
|
{
|
|
if (child.motion != null)
|
|
{
|
|
var clipInfo = new BlendTreeClipInfo
|
|
{
|
|
clipName = child.motion.name,
|
|
threshold = child.threshold
|
|
};
|
|
info.clips.Add(clipInfo);
|
|
|
|
// 如果子 Motion 也是 BlendTree,根据 includeNested 参数决定是否递归处理
|
|
if (includeNested && child.motion is BlendTree childBlendTree)
|
|
{
|
|
CollectBlendTreeInfo(childBlendTree, blendTreeInfos, includeNested);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
info.clipCount = info.clips?.Count ?? 0;
|
|
}
|
|
else if (blendType >= 1 && blendType <= 3) // 2D 类型
|
|
{
|
|
info.type = "2D";
|
|
info.clips = new List<BlendTreeClipInfo>();
|
|
|
|
if (blendTree.children != null)
|
|
{
|
|
foreach (var child in blendTree.children)
|
|
{
|
|
if (child.motion != null)
|
|
{
|
|
var clipInfo = new BlendTreeClipInfo
|
|
{
|
|
clipName = child.motion.name,
|
|
position = child.position
|
|
};
|
|
info.clips.Add(clipInfo);
|
|
|
|
// 如果子 Motion 也是 BlendTree,根据 includeNested 参数决定是否递归处理
|
|
if (includeNested && child.motion is BlendTree childBlendTree)
|
|
{
|
|
CollectBlendTreeInfo(childBlendTree, blendTreeInfos, includeNested);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // Direct 类型或其他
|
|
{
|
|
info.type = "Direct";
|
|
info.clipCount = blendTree.children?.Length ?? 0;
|
|
}
|
|
|
|
blendTreeInfos.Add(info);
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// 从 JSON 字符串解析混合树配置(包含默认状态名和混合树列表)
|
|
/// </summary>
|
|
/// <param name="jsonString">JSON 字符串</param>
|
|
/// <returns>混合树配置</returns>
|
|
public static BlendTreeConfig ParseBlendTreeConfigFromJson(string jsonString)
|
|
{
|
|
if (string.IsNullOrEmpty(jsonString))
|
|
{
|
|
Debug.LogWarning("JSON 字符串为空");
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
// 配置反序列化设置
|
|
var settings = new JsonSerializerSettings
|
|
{
|
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
|
};
|
|
|
|
// 优先按新结构(包含默认状态名)反序列化
|
|
var config = JsonConvert.DeserializeObject<BlendTreeConfig>(jsonString, settings);
|
|
if (config != null && config.blendTrees != null && config.blendTrees.Count > 0)
|
|
{
|
|
return config;
|
|
}
|
|
|
|
// 兼容旧结构:仅为 List<BlendTreeInfo>
|
|
var blendTreeInfos = JsonConvert.DeserializeObject<List<BlendTreeInfo>>(jsonString, settings);
|
|
if (blendTreeInfos != null)
|
|
{
|
|
return new BlendTreeConfig
|
|
{
|
|
defaultStateName = string.Empty,
|
|
blendTrees = blendTreeInfos
|
|
};
|
|
}
|
|
|
|
Debug.LogWarning("JSON 反序列化结果为 null");
|
|
return null;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"从 JSON 解析混合树信息失败: {e.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从 JSON 文件路径解析混合树信息列表
|
|
/// </summary>
|
|
/// <param name="jsonFilePath">JSON 文件路径(可以是 Assets 相对路径或绝对路径)</param>
|
|
/// <returns>混合树信息列表</returns>
|
|
public static List<BlendTreeInfo> ParseBlendTreeFromJsonFile(string jsonFilePath)
|
|
{
|
|
var config = ParseBlendTreeConfigFromJsonFile(jsonFilePath);
|
|
return config?.blendTrees ?? new List<BlendTreeInfo>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从 JSON 文件路径解析混合树配置(包含默认状态名和混合树列表)
|
|
/// </summary>
|
|
/// <param name="jsonFilePath">JSON 文件路径(可以是 Assets 相对路径或绝对路径)</param>
|
|
/// <returns>混合树配置</returns>
|
|
public static BlendTreeConfig ParseBlendTreeConfigFromJsonFile(string jsonFilePath)
|
|
{
|
|
if (string.IsNullOrEmpty(jsonFilePath))
|
|
{
|
|
Debug.LogWarning("JSON 文件路径为空");
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
// 处理路径
|
|
string fullPath = jsonFilePath;
|
|
if (!Path.IsPathRooted(jsonFilePath))
|
|
{
|
|
// 如果是相对路径,尝试转换为绝对路径
|
|
if (jsonFilePath.StartsWith("Assets/"))
|
|
{
|
|
fullPath = Path.Combine(Application.dataPath, jsonFilePath.Substring(7));
|
|
}
|
|
else
|
|
{
|
|
fullPath = Path.Combine(Application.dataPath, jsonFilePath);
|
|
}
|
|
}
|
|
|
|
// 检查文件是否存在
|
|
if (!File.Exists(fullPath))
|
|
{
|
|
Debug.LogWarning($"JSON 文件不存在: {fullPath}");
|
|
return null;
|
|
}
|
|
|
|
// 读取文件内容
|
|
string jsonContent = File.ReadAllText(fullPath, System.Text.Encoding.UTF8);
|
|
|
|
// 调用字符串解析方法
|
|
return ParseBlendTreeConfigFromJson(jsonContent);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"从 JSON 文件解析混合树信息失败: {e.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从 AnimatorController 构建 shortNameHash → 状态短名 映射,供 ActorAnima 快照 Capture 反查。
|
|
/// Editor 下遍历完整状态机;运行时 fallback 为 animationClips 名称(与状态名一致时有效)。
|
|
/// </summary>
|
|
public static Dictionary<int, string> BuildStateHashToNameMap(RuntimeAnimatorController controller)
|
|
{
|
|
var map = new Dictionary<int, string>();
|
|
if (controller == null)
|
|
{
|
|
return map;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
if (controller is AnimatorController animatorController)
|
|
{
|
|
foreach (var layer in animatorController.layers)
|
|
{
|
|
if (layer.stateMachine != null)
|
|
{
|
|
CollectStateNamesFromStateMachine(layer.stateMachine, map);
|
|
}
|
|
}
|
|
|
|
return map;
|
|
}
|
|
#endif
|
|
|
|
foreach (var clip in controller.animationClips)
|
|
{
|
|
if (clip == null) continue;
|
|
var hash = Animator.StringToHash(clip.name);
|
|
map.TryAdd(hash, clip.name);
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
private static void CollectStateNamesFromStateMachine(
|
|
AnimatorStateMachine stateMachine,
|
|
Dictionary<int, string> map)
|
|
{
|
|
if (stateMachine == null) return;
|
|
|
|
foreach (var childState in stateMachine.states)
|
|
{
|
|
if (childState.state == null) continue;
|
|
var hash = Animator.StringToHash(childState.state.name);
|
|
map.TryAdd(hash, childState.state.name);
|
|
}
|
|
|
|
foreach (var subStateMachine in stateMachine.stateMachines)
|
|
{
|
|
if (subStateMachine.stateMachine != null)
|
|
{
|
|
CollectStateNamesFromStateMachine(subStateMachine.stateMachine, map);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// 混合树配置(包含默认状态名和混合树列表)
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class BlendTreeConfig
|
|
{
|
|
/// <summary>
|
|
/// AnimatorController 的默认 State 名称
|
|
/// </summary>
|
|
public string defaultStateName;
|
|
|
|
/// <summary>
|
|
/// 混合树信息列表
|
|
/// </summary>
|
|
public List<BlendTreeInfo> blendTrees;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 混合树信息数据结构
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class BlendTreeInfo
|
|
{
|
|
public string name;
|
|
public string type; // "1D" 或 "2D"
|
|
public int clipCount; // 1D 类型的动画片段数量
|
|
public List<BlendTreeClipInfo> clips; // 2D 类型的动画片段列表
|
|
public float minThreshold;
|
|
public float maxThreshold;
|
|
public string blendParameter;
|
|
public string blendParameterY; // 2D 类型使用
|
|
|
|
public BlendTreeClipInfo GetClipInfo(string clipName)
|
|
{
|
|
return clips.FirstOrDefault(clip => clip.clipName == clipName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 混合树动画片段信息(用于 1D 和 2D 类型)
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class BlendTreeClipInfo
|
|
{
|
|
public string clipName;
|
|
public Vector2 position; // 2D 类型使用
|
|
public float threshold; // 1D 类型使用
|
|
}
|
|
}
|