SkeletonDataReader.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. #if UNITY_EDITOR
  2. using UnityEngine;
  3. using Spine.Unity;
  4. using System.Collections.Generic;
  5. using UnityEditor;
  6. using System.Linq;
  7. using System.IO;
  8. using System;
  9. using Spine;
  10. using UnityEditor.Animations;
  11. [InitializeOnLoad]
  12. public class SkeletonDataMonitor
  13. {
  14. private static Dictionary<string, CachedPrefabData> _prefabCache = new Dictionary<string, CachedPrefabData>();
  15. private static Dictionary<SkeletonDataAsset, List<string>> _skeletonToPrefabs = new Dictionary<SkeletonDataAsset, List<string>>();
  16. private struct CachedPrefabData
  17. {
  18. public SkeletonDataAsset skeletonDataAsset;
  19. public DateTime lastModified;
  20. }
  21. static SkeletonDataMonitor()
  22. {
  23. if (!EditorApplication.isPlayingOrWillChangePlaymode)
  24. {
  25. Initialize();
  26. }
  27. EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
  28. }
  29. private static void OnPlayModeStateChanged(PlayModeStateChange state)
  30. {
  31. if (state == PlayModeStateChange.ExitingEditMode)
  32. {
  33. // 进入运行模式前清理
  34. Cleanup();
  35. }
  36. else if (state == PlayModeStateChange.EnteredEditMode)
  37. {
  38. // 返回编辑模式时重新初始化
  39. Initialize();
  40. }
  41. }
  42. private static void Initialize()
  43. {
  44. EditorApplication.hierarchyChanged += OnHierarchyChanged;
  45. Selection.selectionChanged += OnSelectionChanged;
  46. AssemblyReloadEvents.afterAssemblyReload += InitializeCache;
  47. InitializeCache();
  48. }
  49. private static void Cleanup()
  50. {
  51. EditorApplication.hierarchyChanged -= OnHierarchyChanged;
  52. Selection.selectionChanged -= OnSelectionChanged;
  53. AssemblyReloadEvents.afterAssemblyReload -= InitializeCache;
  54. }
  55. private static void InitializeCache()
  56. {
  57. _prefabCache.Clear();
  58. _skeletonToPrefabs.Clear();
  59. var allPrefabs = AssetDatabase.FindAssets("t:Prefab")
  60. .Select(AssetDatabase.GUIDToAssetPath)
  61. .Where(p => p.EndsWith(".prefab"));
  62. foreach (var path in allPrefabs)
  63. {
  64. var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
  65. var skeleton = prefab.GetComponentInChildren<SkeletonRenderer>(true);
  66. if (skeleton != null && skeleton.skeletonDataAsset != null)
  67. {
  68. CachePrefab(path, skeleton.skeletonDataAsset);
  69. }
  70. }
  71. }
  72. private static void CachePrefab(string path, SkeletonDataAsset skeletonData)
  73. {
  74. _prefabCache[path] = new CachedPrefabData
  75. {
  76. skeletonDataAsset = skeletonData,
  77. lastModified = File.GetLastWriteTime(path)
  78. };
  79. if (!_skeletonToPrefabs.ContainsKey(skeletonData))
  80. {
  81. _skeletonToPrefabs[skeletonData] = new List<string>();
  82. }
  83. if (!_skeletonToPrefabs[skeletonData].Contains(path))
  84. {
  85. _skeletonToPrefabs[skeletonData].Add(path);
  86. }
  87. }
  88. private static void OnHierarchyChanged()
  89. {
  90. if (EditorApplication.isPlayingOrWillChangePlaymode)
  91. return;
  92. var newSkeletons = GameObject.FindObjectsOfType<SkeletonRenderer>(true)
  93. .Where(s => PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(s.gameObject) != null);
  94. foreach (var skeleton in newSkeletons)
  95. {
  96. if (skeleton.skeletonDataAsset != null)
  97. {
  98. var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(skeleton.gameObject);
  99. if (!_prefabCache.ContainsKey(path) ||
  100. _prefabCache[path].skeletonDataAsset != skeleton.skeletonDataAsset)
  101. {
  102. if (string.IsNullOrEmpty(path))
  103. {
  104. return;
  105. }
  106. ProcessSkeletonChange(path, skeleton.skeletonDataAsset);
  107. }
  108. }
  109. }
  110. }
  111. private static void OnSelectionChanged()
  112. {
  113. if (EditorApplication.isPlayingOrWillChangePlaymode)
  114. return;
  115. if (Selection.activeGameObject != null)
  116. {
  117. var skeleton = Selection.activeGameObject.GetComponentInChildren<SkeletonRenderer>(true);
  118. if (skeleton != null && skeleton.skeletonDataAsset != null)
  119. {
  120. var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(skeleton.gameObject);
  121. if (!string.IsNullOrEmpty(path) &&
  122. (!_prefabCache.TryGetValue(path, out var cache) ||
  123. cache.skeletonDataAsset != skeleton.skeletonDataAsset))
  124. {
  125. ProcessSkeletonChange(path, skeleton.skeletonDataAsset);
  126. }
  127. }
  128. }
  129. }
  130. private static void ProcessSkeletonChange(string prefabPath, SkeletonDataAsset skeletonData, bool forceRefresh = false)
  131. {
  132. var currentModTime = File.GetLastWriteTime(prefabPath);
  133. // 如果是强制刷新或者文件有修改
  134. if (forceRefresh || !_prefabCache.TryGetValue(prefabPath, out var cache) ||
  135. cache.skeletonDataAsset != skeletonData ||
  136. cache.lastModified != currentModTime)
  137. {
  138. WriteData(prefabPath);
  139. CachePrefab(prefabPath, skeletonData);
  140. }
  141. }
  142. static SkeletonMecanim skeletonMecanim;
  143. private static void WriteData(string path)
  144. {
  145. GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
  146. GameObject prefabInstance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
  147. try
  148. {
  149. ReadData(prefabInstance);
  150. PrefabUtility.SaveAsPrefabAsset(prefabInstance, path);
  151. UnityEngine.Object.DestroyImmediate(prefabInstance);
  152. }
  153. catch (Exception ex)
  154. {
  155. Debug.LogError($"处理Prefab时出错({path}): {ex.Message}");
  156. }
  157. finally
  158. {
  159. UnityEngine.Object.DestroyImmediate(prefabInstance);
  160. }
  161. }
  162. static void ReadData(GameObject prefab)
  163. {
  164. Character cha = prefab.GetComponent<Character>();
  165. AttackController attack = prefab.GetComponent<AttackController>();
  166. skeletonMecanim = prefab.GetComponentInChildren<SkeletonMecanim>();
  167. if (skeletonMecanim == null)
  168. {
  169. Debug.LogError("SkeletonMecanim component not found!");
  170. return;
  171. }
  172. // 获取 SkeletonDataAsset
  173. SkeletonDataAsset skeletonDataAsset = skeletonMecanim.SkeletonDataAsset;
  174. if (skeletonDataAsset == null)
  175. {
  176. Debug.LogError("SkeletonDataAsset not found!");
  177. return;
  178. }
  179. // 获取 SkeletonData
  180. SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
  181. if (skeletonData == null)
  182. {
  183. Debug.LogError("SkeletonData not found!");
  184. return;
  185. }
  186. attack.attackKeys = new List<AttackController.SpineAniKey>();
  187. // 遍历所有动画
  188. foreach (var animation in skeletonData.Animations)
  189. {
  190. string animationName = animation.Name;
  191. float animationDuration = animation.Duration;
  192. AttackController.SpineAniKey sak = new();
  193. Debug.Log($"动画名称: {animationName}, 动画时长: {animationDuration}");
  194. if (animationName == "die")
  195. {
  196. cha.totalDieKeepTime = animationDuration;
  197. }
  198. sak.totalTime = animationDuration;
  199. sak.aniName = animationName;
  200. sak.keys = new List<AttackController.AttackKeyType>();
  201. bool isStarKey = true;
  202. int canWrite = 0;
  203. foreach(var timeline in animation.Timelines)
  204. {
  205. if(timeline is EventTimeline eventTimeline)
  206. {
  207. AttackController.AttackKeyType akt = new AttackController.AttackKeyType();
  208. foreach (var eventFrame in eventTimeline.Events)
  209. {
  210. string eventName = eventFrame.ToString();
  211. float eventTime = eventFrame.Time;
  212. if (isStarKey)
  213. {
  214. akt.startKeyName = eventName;
  215. akt.startKeyTime = eventTime;
  216. akt.attackType = AttackController.KeyType.AttackStart;
  217. canWrite = 1;
  218. }
  219. else if(canWrite == 1)
  220. {
  221. akt.endKeyName = eventName;
  222. akt.endKeyTime = eventTime;
  223. akt.endType = AttackController.KeyType.AttackEnd;
  224. canWrite = 2;
  225. sak.keys.Add(akt);
  226. akt = new AttackController.AttackKeyType();
  227. }
  228. isStarKey = !isStarKey;
  229. }
  230. }
  231. }
  232. if (canWrite == 2)
  233. {
  234. attack.attackKeys.Add(sak);
  235. }
  236. }
  237. }
  238. // 监听SkeletonDataAsset文件变化
  239. public class SkeletonDataPostprocessor : AssetPostprocessor
  240. {
  241. static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets,
  242. string[] movedAssets, string[] movedFromAssetPaths)
  243. {
  244. if (EditorApplication.isPlayingOrWillChangePlaymode)
  245. return;
  246. foreach (string path in importedAssets)
  247. {
  248. if (path.EndsWith(".asset"))
  249. {
  250. var asset = AssetDatabase.LoadAssetAtPath<SkeletonDataAsset>(path);
  251. if (asset != null && _skeletonToPrefabs.ContainsKey(asset))
  252. {
  253. foreach (var prefabPath in _skeletonToPrefabs[asset])
  254. {
  255. ProcessSkeletonChange(prefabPath, asset);
  256. }
  257. }
  258. }
  259. }
  260. }
  261. }
  262. [MenuItem("Tools/Spine/强制更新选中SkeletonData")]
  263. private static void ForceRefreshSelectedSkeletonData()
  264. {
  265. if (EditorApplication.isPlayingOrWillChangePlaymode)
  266. {
  267. EditorUtility.DisplayDialog("提示", "该功能只能在编辑模式下使用", "确定");
  268. return;
  269. }
  270. var selected = Selection.activeObject as SkeletonDataAsset;
  271. if (selected == null)
  272. {
  273. EditorUtility.DisplayDialog("提示", "请先选择一个SkeletonDataAsset", "确定");
  274. return;
  275. }
  276. // 强制更新所有使用此SkeletonData的预制体
  277. if (_skeletonToPrefabs.TryGetValue(selected, out var prefabPaths))
  278. {
  279. foreach (var path in prefabPaths)
  280. {
  281. ForceRefreshPrefab(path, selected);
  282. }
  283. AssetDatabase.SaveAssets();
  284. Debug.Log($"已强制更新 {prefabPaths.Count} 个使用 {selected.name} 的预制体");
  285. }
  286. else
  287. {
  288. Debug.Log($"没有找到使用 {selected.name} 的预制体");
  289. }
  290. }
  291. private static void ForceRefreshPrefab(string prefabPath, SkeletonDataAsset skeletonData)
  292. {
  293. GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
  294. GameObject prefabInstance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
  295. try
  296. {
  297. // 强制重新读取数据
  298. WriteData(prefabPath);
  299. PrefabUtility.SaveAsPrefabAsset(prefabInstance, prefabPath);
  300. // 更新缓存
  301. CachePrefab(prefabPath, skeletonData);
  302. }
  303. catch (Exception ex)
  304. {
  305. Debug.LogError($"强制更新预制体 {prefabPath} 时出错: {ex.Message}");
  306. }
  307. finally
  308. {
  309. UnityEngine.Object.DestroyImmediate(prefabInstance);
  310. }
  311. }
  312. [MenuItem("Tools/Spine/手动刷新变更")]
  313. private static void ForceRefreshModified()
  314. {
  315. if (EditorApplication.isPlayingOrWillChangePlaymode)
  316. {
  317. EditorUtility.DisplayDialog("提示", "该功能只能在编辑模式下使用", "确定");
  318. return;
  319. }
  320. foreach (var kvp in _prefabCache.ToList())
  321. {
  322. var currentModTime = File.GetLastWriteTime(kvp.Key);
  323. if (currentModTime != kvp.Value.lastModified)
  324. {
  325. ProcessSkeletonChange(kvp.Key, kvp.Value.skeletonDataAsset);
  326. }
  327. }
  328. }
  329. [MenuItem("Tools/Spine/Generate Simple Animator")]
  330. public static void GenerateSimpleAnimator()
  331. {
  332. SkeletonDataAsset skeletonData = Selection.activeObject as SkeletonDataAsset;
  333. if (skeletonData == null)
  334. {
  335. Debug.LogWarning("请先选择一个SkeletonDataAsset");
  336. return;
  337. }
  338. // 1. 创建Animator Controller
  339. string skeletonFolder = Path.GetDirectoryName(AssetDatabase.GetAssetPath(skeletonData));
  340. string controllerName = $"{skeletonData.name}_Controller.controller";
  341. string path = $"{skeletonFolder}/{controllerName}";
  342. if (File.Exists(path))
  343. {
  344. AssetDatabase.DeleteAsset(path);
  345. }
  346. var controller = AnimatorController.CreateAnimatorControllerAtPath(path);
  347. // 2. 获取Spine动画数据
  348. var skeleton = skeletonData.GetSkeletonData(true);
  349. // 3. 为每个动画创建空Clip并设置正确时长
  350. foreach (var spineAnim in skeleton.Animations)
  351. {
  352. AnimationClip clip = new AnimationClip();
  353. clip.name = spineAnim.Name;
  354. // 设置Clip时长与Spine动画一致
  355. SetClipLength(clip, spineAnim.Duration);
  356. // 添加到Controller
  357. AssetDatabase.AddObjectToAsset(clip, controller);
  358. var state = controller.layers[0].stateMachine.AddState(clip.name);
  359. state.motion = clip;
  360. }
  361. AssetDatabase.SaveAssets();
  362. Debug.Log($"已生成包含 {skeleton.Animations.Count} 个动画的控制器");
  363. }
  364. static void SetClipLength(AnimationClip clip, float duration)
  365. {
  366. // 创建一个空曲线来设置时长
  367. AnimationCurve curve = new AnimationCurve();
  368. curve.AddKey(0, 0);
  369. curve.AddKey(duration, 0);
  370. // 绑定到任意属性(这里使用假属性)
  371. AnimationUtility.SetEditorCurves(clip, new[] {
  372. new EditorCurveBinding {
  373. path = "",
  374. type = typeof(GameObject),
  375. propertyName = "m_IsActive"
  376. }
  377. }, new[] { curve });
  378. }
  379. [MenuItem("Tools/Spine/Generate Simple Animator", true)]
  380. static bool ValidateGenerate()
  381. {
  382. return Selection.activeObject is SkeletonDataAsset;
  383. }
  384. }
  385. #endif