270 lines
10 KiB
C#
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}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|