Files
aibis-dream/Assets/Editor/UXML/AnimatorLinker/AnimatorClipLink.cs
T

303 lines
10 KiB
C#

using System;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using UnityEditor;
using UnityEditor.Animations;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
namespace AibisDream.SystemEditor
{
public class AnimatorClipLink : EditorWindow
{
[SerializeField] private VisualTreeAsset visualTreeAsset;
private ObjectField _animatorField;
private Label _animatorMessage;
private ListView _stateInfoView;
private TextField _clipsPathField;
private Label _clipsMessage;
private ListView _clipsNameList;
private HelpBox _matchMessageBox;
#region 部分缓存
private static AnimatorController _animatorCache;
private static string _clipsPathCache;
private ChildAnimatorState[] _statesCache;
private string[] _clipsCache;
private bool _isChecked;
private bool _allMatched;
private bool _isAllLoad;
#endregion
[MenuItem("Tools/AnimatorClipLink")]
public static void ShowExample()
{
AnimatorClipLink wnd = GetWindow<AnimatorClipLink>();
wnd.titleContent = new GUIContent("AnimatorClipLink");
}
public void CreateGUI()
{
// Each editor window contains a root VisualElement object
VisualElement root = rootVisualElement;
// Instantiate UXML
root.Add(visualTreeAsset.Instantiate());
// 注册一些事件
_animatorField = root.Q<ObjectField>("animator-field");
_animatorField.RegisterValueChangedCallback(OnAnimatorChanged);
_animatorMessage = root.Q<Label>("animator-message");
_stateInfoView = root.Q<ListView>("state-info-list");
_clipsPathField = root.Q<TextField>("clips-path-field");
_clipsPathField.RegisterValueChangedCallback(OnClipsPathChanged);
_clipsMessage = root.Q<Label>("clips-message");
_clipsNameList = root.Q<ListView>("clips-name-list");
_matchMessageBox = root.Q<HelpBox>("match-message-box");
root.Q<Button>("import-merge-button").clicked += ImportMerge;
// 初始化
_animatorMessage.text = "请添加AnimatorController";
_clipsMessage.text = "请输入Clips路径";
// 加载AnimatorState
if (_animatorCache != null)
{
_animatorField.value = _animatorCache;
LoadAnimatorController(_animatorCache);
}
else
{
UpdateStateInfos(Array.Empty<ChildAnimatorState>());
}
// 加载路径下的Clips
if (!string.IsNullOrWhiteSpace(_clipsPathCache))
{
_clipsPathField.value = _clipsPathCache;
LoadClipPath(_clipsPathCache);
}
else
{
UpdateClipInFolder(Array.Empty<string>());
}
}
private void UpdateStateInfos(ChildAnimatorState[] states)
{
_statesCache = states;
// 总条目
var stateList = states.ToList();
_stateInfoView.makeItem = MakeItem;
_stateInfoView.bindItem = BindItem;
_stateInfoView.itemsSource = stateList;
_stateInfoView.selectionType = UnityEngine.UIElements.SelectionType.None;
MatchStateAndClip();
return;
// 链接
void BindItem(VisualElement item, int index)
{
if (index >= stateList.Count) return;
var state = stateList[index];
if (item is ObjectField field)
{
field.value = state.state.motion;
field.label = state.state.name;
}
}
// 创建
VisualElement MakeItem()
{
var item = new ObjectField
{
objectType = typeof(AnimationClip)
};
item.SetEnabled(false);
return item;
}
}
private void OnAnimatorChanged(ChangeEvent<Object> evt)
{
if (evt.newValue is AnimatorController animatorController)
{
LoadAnimatorController(animatorController);
}
else
{
LoadAnimatorController(null);
}
}
private void LoadAnimatorController([CanBeNull] AnimatorController animatorController)
{
// 判断新的值是不是空的,是就清空State信息
if (animatorController == null)
{
_animatorCache = null;
_animatorMessage.text = "AnimatorController 为空";
// 清空State信息
UpdateStateInfos(Array.Empty<ChildAnimatorState>());
return;
}
_animatorCache = animatorController;
// 值更新的时候,更新State信息
var states = animatorController.layers[0].stateMachine.states;
_animatorMessage.text = $"BaseLayer共有{states.Length}个State";
UpdateStateInfos(states);
}
private void OnClipsPathChanged(ChangeEvent<string> evt)
{
LoadClipPath(evt.newValue);
}
private void LoadClipPath([CanBeNull] string clipsPath)
{
_clipsPathCache = clipsPath;
// 先判断路径是否存在
if (!AssetDatabase.IsValidFolder(clipsPath))
{
_clipsMessage.text = "路径不存在";
// 清空Clip信息
UpdateClipInFolder(Array.Empty<string>());
return;
}
// 从路径中获取Clip
var clipIds = AssetDatabase.FindAssets("t:AnimationClip", new[] { clipsPath });
// 获取名称
var clipNames = clipIds.Select(AssetDatabase.GUIDToAssetPath)
.Where(item => item.EndsWith(".anim"))
.ToArray();
_clipsMessage.text = $"共有{clipNames.Length}个Clip";
UpdateClipInFolder(clipNames);
}
private void UpdateClipInFolder(string[] clipNames)
{
_clipsCache = clipNames;
var clipNameList = clipNames.Select(Path.GetFileNameWithoutExtension).ToList();
_clipsNameList.makeItem = MakeItem;
_clipsNameList.bindItem = BindItem;
_clipsNameList.itemsSource = clipNameList;
MatchStateAndClip();
return;
void BindItem(VisualElement item, int index)
{
if (index >= clipNameList.Count) return;
var clipName = clipNameList[index];
if (item is Label label)
{
label.text = clipName;
}
}
VisualElement MakeItem() => new Label();
}
private void MatchStateAndClip()
{
if (_statesCache is not { Length: > 0 } || _clipsCache is not { Length: > 0 })
{
_matchMessageBox.text = "请先选择AnimatorController和Clips路径";
_matchMessageBox.messageType = HelpBoxMessageType.Warning;
_allMatched = false;
_isAllLoad = false;
return;
}
var clipNames = _clipsCache.Select(Path.GetFileNameWithoutExtension).ToArray();
// 匹配state和clips
var notMatchStates =
(from state in _statesCache where !clipNames.Contains(state.state.name) select state.state.name)
.ToList();
if (notMatchStates.Count == 0)
{
_matchMessageBox.messageType = HelpBoxMessageType.Info;
_matchMessageBox.text = "所有State都已经匹配";
_allMatched = true;
}
else
{
_matchMessageBox.messageType = HelpBoxMessageType.Warning;
_matchMessageBox.text = $"未匹配的State有{notMatchStates.Count}个:\n{string.Join(", ", notMatchStates)}";
_allMatched = false;
}
_isAllLoad = true;
_isChecked = false;
}
private void ImportMerge()
{
if (!_isAllLoad)
{
_matchMessageBox.messageType = HelpBoxMessageType.Warning;
_matchMessageBox.text = "请先选择AnimatorController和Clips路径";
return;
}
if (_isChecked || _allMatched)
{
MergeClips();
_isChecked = false;
_matchMessageBox.messageType = HelpBoxMessageType.Info;
_matchMessageBox.text = "匹配完成";
}
else
{
_matchMessageBox.messageType = HelpBoxMessageType.Warning;
_matchMessageBox.text = "仍有未匹配到Clip的State,确认要继续吗?";
_isChecked = true;
}
}
private void MergeClips()
{
var clipNames = _clipsCache.Select(Path.GetFileNameWithoutExtension).ToArray();
foreach (var state in _statesCache)
{
var idx = Array.IndexOf(clipNames, state.state.name);
if (idx != -1)
{
var newClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(_clipsCache[idx]);
var oldClip = state.state.motion;
var settings = AnimationUtility.GetAnimationClipSettings(newClip);
settings.loopTime = oldClip.isLooping;
AnimationUtility.SetAnimationClipSettings(newClip, settings);
state.state.motion = newClip;
}
}
LoadAnimatorController(_animatorCache);
}
}
}