Files
aibis-dream/Assets/Editor/Window/AnimatorBlendTreeParserWindow.cs
T
2026-01-06 18:51:59 +08:00

270 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using AibisDream.Framework;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace AibisDream.SystemEditor
{
public class AnimatorBlendTreeParserWindow : EditorWindow
{
private AnimatorController _selectedController;
private List<BlendTreeInfo> _blendTreeInfos;
private Vector2 _scrollPosition;
private string _resultText = "";
private bool _includeNested = false; // 是否包含嵌套混合树,默认不提取
private string _defaultStateName = ""; // 控制器默认 State 名称
[MenuItem("Tools/Animator BlendTree Parser")]
public static void ShowWindow()
{
GetWindow<AnimatorBlendTreeParserWindow>("BlendTree Parser");
}
private void OnGUI()
{
EditorGUILayout.Space(10);
// AnimatorController 选择区域
EditorGUILayout.LabelField("AnimatorController", EditorStyles.boldLabel);
_selectedController = (AnimatorController)EditorGUILayout.ObjectField(
"Controller", _selectedController, typeof(AnimatorController), false);
EditorGUILayout.Space(10);
// 解析选项
EditorGUILayout.LabelField("解析选项", EditorStyles.boldLabel);
_includeNested = EditorGUILayout.Toggle("包含嵌套混合树", _includeNested);
EditorGUILayout.HelpBox(
_includeNested
? "将提取所有层级的混合树(包括嵌套在子混合树中的混合树)"
: "仅提取第一层混合树(不包含嵌套在子混合树中的混合树)",
MessageType.Info);
EditorGUILayout.Space(10);
// 解析按钮
EditorGUI.BeginDisabledGroup(_selectedController == null);
if (GUILayout.Button("解析混合树", GUILayout.Height(30)))
{
ParseBlendTree();
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space(10);
// 结果显示区域
if (_blendTreeInfos != null && _blendTreeInfos.Count > 0)
{
EditorGUILayout.LabelField("解析结果", EditorStyles.boldLabel);
// 显示统计信息
EditorGUILayout.HelpBox(
$"共找到 {_blendTreeInfos.Count} 个混合树",
MessageType.Info);
// 可滚动的文本区域
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, GUILayout.ExpandHeight(true));
EditorGUILayout.TextArea(_resultText, GUILayout.ExpandHeight(true));
EditorGUILayout.EndScrollView();
EditorGUILayout.Space(10);
// 保存 JSON 按钮
if (GUILayout.Button("保存为 JSON", GUILayout.Height(30)))
{
SaveToJson();
}
}
else if (_blendTreeInfos != null && _blendTreeInfos.Count == 0)
{
EditorGUILayout.HelpBox(
"未找到混合树",
MessageType.Warning);
}
}
private void ParseBlendTree()
{
if (_selectedController == null)
{
EditorUtility.DisplayDialog("错误", "请先选择 AnimatorController", "确定");
return;
}
try
{
// 解析默认 State 名称(默认使用第 0 层的默认状态)
if (_selectedController.layers != null && _selectedController.layers.Length > 0)
{
var baseLayer = _selectedController.layers[0];
_defaultStateName = baseLayer.stateMachine?.defaultState != null
? baseLayer.stateMachine.defaultState.name
: string.Empty;
}
else
{
_defaultStateName = string.Empty;
}
// 调用解析方法,传入 includeNested 选项
_blendTreeInfos = AnimatorKit.ParseBlendTree(_selectedController, _includeNested);
// 格式化结果显示文本
FormatResultText();
Debug.Log(
$"解析完成: 找到 {_blendTreeInfos.Count} 个混合树 (包含嵌套: {_includeNested}),默认 State: {_defaultStateName}");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("解析失败", e.Message, "确定");
Debug.LogError($"解析失败: {e}");
_blendTreeInfos = null;
_resultText = "";
}
}
private void FormatResultText()
{
if (_blendTreeInfos == null || _blendTreeInfos.Count == 0)
{
_resultText = "未找到混合树";
return;
}
var lines = new List<string>();
lines.Add($"=== 控制器 {_selectedController.name} 的混合树信息 ===");
lines.Add($"默认 State: {_defaultStateName}");
lines.Add($"共找到 {_blendTreeInfos.Count} 个混合树\n");
for (int i = 0; i < _blendTreeInfos.Count; i++)
{
var info = _blendTreeInfos[i];
lines.Add($"[{i + 1}] 混合树名称: {info.name}");
lines.Add($" 类型: {info.type}");
if (info.type == "1D")
{
lines.Add($" 动画片段数量: {info.clipCount}");
lines.Add($" 混合参数: {info.blendParameter}");
lines.Add($" 参数范围: [{info.minThreshold}, {info.maxThreshold}]");
lines.Add($" 动画片段列表 ({info.clips?.Count ?? 0} 个):");
if (info.clips != null)
{
for (int j = 0; j < info.clips.Count; j++)
{
var clip = info.clips[j];
lines.Add($" [{j + 1}] {clip.clipName} (Threshold: {clip.threshold})");
}
}
}
else if (info.type == "2D")
{
lines.Add($" 混合参数 X: {info.blendParameter}");
lines.Add($" 混合参数 Y: {info.blendParameterY}");
lines.Add($" 参数范围: [{info.minThreshold}, {info.maxThreshold}]");
lines.Add($" 动画片段列表 ({info.clips?.Count ?? 0} 个):");
if (info.clips != null)
{
for (int j = 0; j < info.clips.Count; j++)
{
var clip = info.clips[j];
lines.Add($" [{j + 1}] {clip.clipName} (位置: {clip.position})");
}
}
}
else
{
lines.Add($" 动画片段数量: {info.clipCount}");
}
if (i < _blendTreeInfos.Count - 1)
{
lines.Add("");
}
}
_resultText = string.Join("\n", lines);
}
private void SaveToJson()
{
if (_selectedController == null || _blendTreeInfos == null || _blendTreeInfos.Count == 0)
{
EditorUtility.DisplayDialog("错误", "没有可保存的数据", "确定");
return;
}
try
{
// 获取 AnimatorController 的资产路径
string controllerPath = AssetDatabase.GetAssetPath(_selectedController);
if (string.IsNullOrEmpty(controllerPath))
{
EditorUtility.DisplayDialog("错误", "无法获取 AnimatorController 的路径", "确定");
return;
}
// 生成 JSON 文件路径(与 .controller 文件同名同路径,扩展名为 .json)
string directory = Path.GetDirectoryName(controllerPath);
string fileName = Path.GetFileNameWithoutExtension(controllerPath);
string jsonPath = Path.Combine(directory, $"{fileName}.json");
// 转换为 Unity 资源路径格式
jsonPath = jsonPath.Replace("\\", "/");
if (!jsonPath.StartsWith("Assets/"))
{
// 如果不是 Assets 路径,尝试转换
string fullPath = Path.GetFullPath(jsonPath);
string assetsPath = Path.GetFullPath(Application.dataPath);
if (fullPath.StartsWith(assetsPath))
{
jsonPath = "Assets" + fullPath.Substring(assetsPath.Length).Replace("\\", "/");
}
else
{
EditorUtility.DisplayDialog("错误", "JSON 文件路径必须在 Assets 目录下", "确定");
return;
}
}
// 配置序列化设置,忽略循环引用
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Formatting = Formatting.Indented
};
// 组装配置对象(包含默认 State 名称和混合树列表)
var config = new BlendTreeConfig
{
defaultStateName = _defaultStateName,
blendTrees = _blendTreeInfos
};
// 序列化为 JSON
string jsonContent = JsonConvert.SerializeObject(config, settings);
// 写入文件(如果已存在则覆盖)
File.WriteAllText(jsonPath, jsonContent, System.Text.Encoding.UTF8);
// 刷新资源数据库
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("保存成功", $"JSON 文件已保存到:\n{jsonPath}", "确定");
Debug.Log($"JSON 文件已保存: {jsonPath}");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("保存失败", e.Message, "确定");
Debug.LogError($"保存 JSON 失败: {e}");
}
}
}
}