| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- #if UNITY_EDITOR
- using UnityEngine;
- using Spine.Unity;
- using System.Collections.Generic;
- using UnityEditor;
- using System.Linq;
- using System.IO;
- using System;
- using Spine;
- using UnityEditor.Animations;
- [InitializeOnLoad]
- public class SkeletonDataMonitor
- {
- private static Dictionary<string, CachedPrefabData> _prefabCache = new Dictionary<string, CachedPrefabData>();
- private static Dictionary<SkeletonDataAsset, List<string>> _skeletonToPrefabs = new Dictionary<SkeletonDataAsset, List<string>>();
- private struct CachedPrefabData
- {
- public SkeletonDataAsset skeletonDataAsset;
- public DateTime lastModified;
- }
- static SkeletonDataMonitor()
- {
- if (!EditorApplication.isPlayingOrWillChangePlaymode)
- {
- Initialize();
- }
- EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
- }
- private static void OnPlayModeStateChanged(PlayModeStateChange state)
- {
- if (state == PlayModeStateChange.ExitingEditMode)
- {
- // 进入运行模式前清理
- Cleanup();
- }
- else if (state == PlayModeStateChange.EnteredEditMode)
- {
- // 返回编辑模式时重新初始化
- Initialize();
- }
- }
- private static void Initialize()
- {
- EditorApplication.hierarchyChanged += OnHierarchyChanged;
- Selection.selectionChanged += OnSelectionChanged;
- AssemblyReloadEvents.afterAssemblyReload += InitializeCache;
- InitializeCache();
- }
- private static void Cleanup()
- {
- EditorApplication.hierarchyChanged -= OnHierarchyChanged;
- Selection.selectionChanged -= OnSelectionChanged;
- AssemblyReloadEvents.afterAssemblyReload -= InitializeCache;
- }
- private static void InitializeCache()
- {
- _prefabCache.Clear();
- _skeletonToPrefabs.Clear();
- var allPrefabs = AssetDatabase.FindAssets("t:Prefab")
- .Select(AssetDatabase.GUIDToAssetPath)
- .Where(p => p.EndsWith(".prefab"));
- foreach (var path in allPrefabs)
- {
- var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
- var skeleton = prefab.GetComponentInChildren<SkeletonRenderer>(true);
- if (skeleton != null && skeleton.skeletonDataAsset != null)
- {
- CachePrefab(path, skeleton.skeletonDataAsset);
- }
- }
- }
- private static void CachePrefab(string path, SkeletonDataAsset skeletonData)
- {
- _prefabCache[path] = new CachedPrefabData
- {
- skeletonDataAsset = skeletonData,
- lastModified = File.GetLastWriteTime(path)
- };
- if (!_skeletonToPrefabs.ContainsKey(skeletonData))
- {
- _skeletonToPrefabs[skeletonData] = new List<string>();
- }
- if (!_skeletonToPrefabs[skeletonData].Contains(path))
- {
- _skeletonToPrefabs[skeletonData].Add(path);
- }
- }
- private static void OnHierarchyChanged()
- {
- if (EditorApplication.isPlayingOrWillChangePlaymode)
- return;
- var newSkeletons = GameObject.FindObjectsOfType<SkeletonRenderer>(true)
- .Where(s => PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(s.gameObject) != null);
- foreach (var skeleton in newSkeletons)
- {
- if (skeleton.skeletonDataAsset != null)
- {
- var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(skeleton.gameObject);
- if (!_prefabCache.ContainsKey(path) ||
- _prefabCache[path].skeletonDataAsset != skeleton.skeletonDataAsset)
- {
- ProcessSkeletonChange(path, skeleton.skeletonDataAsset);
- }
- }
- }
- }
- private static void OnSelectionChanged()
- {
- if (EditorApplication.isPlayingOrWillChangePlaymode)
- return;
- if (Selection.activeGameObject != null)
- {
- var skeleton = Selection.activeGameObject.GetComponentInChildren<SkeletonRenderer>(true);
- if (skeleton != null && skeleton.skeletonDataAsset != null)
- {
- var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(skeleton.gameObject);
- if (!string.IsNullOrEmpty(path) &&
- (!_prefabCache.TryGetValue(path, out var cache) ||
- cache.skeletonDataAsset != skeleton.skeletonDataAsset))
- {
- ProcessSkeletonChange(path, skeleton.skeletonDataAsset);
- }
- }
- }
- }
- private static void ProcessSkeletonChange(string prefabPath, SkeletonDataAsset skeletonData, bool forceRefresh = false)
- {
- var currentModTime = File.GetLastWriteTime(prefabPath);
- // 如果是强制刷新或者文件有修改
- if (forceRefresh || !_prefabCache.TryGetValue(prefabPath, out var cache) ||
- cache.skeletonDataAsset != skeletonData ||
- cache.lastModified != currentModTime)
- {
- WriteData(prefabPath);
- CachePrefab(prefabPath, skeletonData);
- }
- }
- static Character cha;
- static SkeletonMecanim skeletonMecanim;
- static AttackController attack;
- private static void WriteData(string path)
- {
- GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
- GameObject prefabInstance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
- try
- {
- cha = prefabInstance.GetComponent<Character>();
- attack = prefabInstance.GetComponent<AttackController>();
- ReadData(prefabInstance);
- PrefabUtility.SaveAsPrefabAsset(prefabInstance, path);
- UnityEngine.Object.DestroyImmediate(prefabInstance);
- }
- catch (Exception ex)
- {
- Debug.LogError($"处理Prefab时出错({path}): {ex.Message}");
- }
- finally
- {
- UnityEngine.Object.DestroyImmediate(prefabInstance);
- }
- }
- static void ReadData(GameObject prefab)
- {
- skeletonMecanim = prefab.GetComponentInChildren<SkeletonMecanim>();
- if (skeletonMecanim == null)
- {
- Debug.LogError("SkeletonMecanim component not found!");
- return;
- }
- // 获取 SkeletonDataAsset
- SkeletonDataAsset skeletonDataAsset = skeletonMecanim.SkeletonDataAsset;
- if (skeletonDataAsset == null)
- {
- Debug.LogError("SkeletonDataAsset not found!");
- return;
- }
- // 获取 SkeletonData
- SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
- if (skeletonData == null)
- {
- Debug.LogError("SkeletonData not found!");
- return;
- }
- attack.attackKeys = new List<AttackController.SpineAniKey>();
- // 遍历所有动画
- foreach (var animation in skeletonData.Animations)
- {
- string animationName = animation.Name;
- float animationDuration = animation.Duration;
- Debug.Log($"动画名称: {animationName}, 动画时长: {animationDuration}");
- if (animationName == "die")
- {
- cha.totalDieKeepTime = animationDuration;
- Debug.Log(animationDuration);
- }
- else if (animationName == "attack_summon")
- {
- cha.totalAttack_summonTime = animationDuration;
- }
- else if (animationName == "attack_march")
- {
- cha.totalAttack_marchTime = animationDuration;
- }
- AttackController.SpineAniKey sak;
- sak.aniName = animationName;
- sak.keys = new List<AttackController.AttackKeyType>();
- AttackController.AttackKeyType akt = new AttackController.AttackKeyType();
- bool isStarKey = true;
- int canWrite = 0;
- foreach(var timeline in animation.Timelines)
- {
- if(timeline is EventTimeline eventTimeline)
- {
- foreach(var eventFrame in eventTimeline.Events)
- {
- string eventName = eventFrame.ToString();
- float eventTime = eventFrame.Time;
- if (isStarKey)
- {
- akt.startKeyName = eventName;
- akt.startKeyTime = eventTime;
- akt.attackType = AttackController.KeyType.AttackStart;
- canWrite = 1;
- }
- else if(canWrite == 1)
- {
- akt.endKeyName = eventName;
- akt.endKeyTime = eventTime;
- akt.endType = AttackController.KeyType.AttackEnd;
- canWrite = 2;
- }
- isStarKey = !isStarKey;
- }
- }
- }
- if (canWrite == 2)
- {
- sak.keys.Add(akt);
- attack.attackKeys.Add(sak);
- }
- }
- }
- // 监听SkeletonDataAsset文件变化
- public class SkeletonDataPostprocessor : AssetPostprocessor
- {
- static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets,
- string[] movedAssets, string[] movedFromAssetPaths)
- {
- if (EditorApplication.isPlayingOrWillChangePlaymode)
- return;
- foreach (string path in importedAssets)
- {
- if (path.EndsWith(".asset"))
- {
- var asset = AssetDatabase.LoadAssetAtPath<SkeletonDataAsset>(path);
- if (asset != null && _skeletonToPrefabs.ContainsKey(asset))
- {
- foreach (var prefabPath in _skeletonToPrefabs[asset])
- {
- ProcessSkeletonChange(prefabPath, asset);
- }
- }
- }
- }
- }
- }
- [MenuItem("Tools/Spine/强制更新选中SkeletonData")]
- private static void ForceRefreshSelectedSkeletonData()
- {
- if (EditorApplication.isPlayingOrWillChangePlaymode)
- {
- EditorUtility.DisplayDialog("提示", "该功能只能在编辑模式下使用", "确定");
- return;
- }
- var selected = Selection.activeObject as SkeletonDataAsset;
- if (selected == null)
- {
- EditorUtility.DisplayDialog("提示", "请先选择一个SkeletonDataAsset", "确定");
- return;
- }
- // 强制更新所有使用此SkeletonData的预制体
- if (_skeletonToPrefabs.TryGetValue(selected, out var prefabPaths))
- {
- foreach (var path in prefabPaths)
- {
- ForceRefreshPrefab(path, selected);
- }
- AssetDatabase.SaveAssets();
- Debug.Log($"已强制更新 {prefabPaths.Count} 个使用 {selected.name} 的预制体");
- }
- else
- {
- Debug.Log($"没有找到使用 {selected.name} 的预制体");
- }
- }
- private static void ForceRefreshPrefab(string prefabPath, SkeletonDataAsset skeletonData)
- {
- GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
- GameObject prefabInstance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
- try
- {
- // 强制重新读取数据
- WriteData(prefabPath);
- PrefabUtility.SaveAsPrefabAsset(prefabInstance, prefabPath);
- // 更新缓存
- CachePrefab(prefabPath, skeletonData);
- }
- catch (Exception ex)
- {
- Debug.LogError($"强制更新预制体 {prefabPath} 时出错: {ex.Message}");
- }
- finally
- {
- UnityEngine.Object.DestroyImmediate(prefabInstance);
- }
- }
- [MenuItem("Tools/Spine/手动刷新变更")]
- private static void ForceRefreshModified()
- {
- if (EditorApplication.isPlayingOrWillChangePlaymode)
- {
- EditorUtility.DisplayDialog("提示", "该功能只能在编辑模式下使用", "确定");
- return;
- }
- foreach (var kvp in _prefabCache.ToList())
- {
- var currentModTime = File.GetLastWriteTime(kvp.Key);
- if (currentModTime != kvp.Value.lastModified)
- {
- ProcessSkeletonChange(kvp.Key, kvp.Value.skeletonDataAsset);
- }
- }
- }
- [MenuItem("Tools/Spine/Generate Simple Animator")]
- public static void GenerateSimpleAnimator()
- {
- SkeletonDataAsset skeletonData = Selection.activeObject as SkeletonDataAsset;
- if (skeletonData == null)
- {
- Debug.LogWarning("请先选择一个SkeletonDataAsset");
- return;
- }
- // 1. 创建Animator Controller
- string skeletonFolder = Path.GetDirectoryName(AssetDatabase.GetAssetPath(skeletonData));
- string controllerName = $"{skeletonData.name}_Controller.controller";
- string path = $"{skeletonFolder}/{controllerName}";
- if (File.Exists(path))
- {
- AssetDatabase.DeleteAsset(path);
- }
- var controller = AnimatorController.CreateAnimatorControllerAtPath(path);
- // 2. 获取Spine动画数据
- var skeleton = skeletonData.GetSkeletonData(true);
- // 3. 为每个动画创建空Clip并设置正确时长
- foreach (var spineAnim in skeleton.Animations)
- {
- AnimationClip clip = new AnimationClip();
- clip.name = spineAnim.Name;
- // 设置Clip时长与Spine动画一致
- SetClipLength(clip, spineAnim.Duration);
- // 添加到Controller
- AssetDatabase.AddObjectToAsset(clip, controller);
- var state = controller.layers[0].stateMachine.AddState(clip.name);
- state.motion = clip;
- }
- AssetDatabase.SaveAssets();
- Debug.Log($"已生成包含 {skeleton.Animations.Count} 个动画的控制器");
- }
- static void SetClipLength(AnimationClip clip, float duration)
- {
- // 创建一个空曲线来设置时长
- AnimationCurve curve = new AnimationCurve();
- curve.AddKey(0, 0);
- curve.AddKey(duration, 0);
- // 绑定到任意属性(这里使用假属性)
- AnimationUtility.SetEditorCurves(clip, new[] {
- new EditorCurveBinding {
- path = "",
- type = typeof(GameObject),
- propertyName = "m_IsActive"
- }
- }, new[] { curve });
- }
- [MenuItem("Tools/Spine/Generate Simple Animator", true)]
- static bool ValidateGenerate()
- {
- return Selection.activeObject is SkeletonDataAsset;
- }
- }
- #endif
|