当前位置:   article > 正文

Unity-Spine导出动画位移的实现方法_skeletonrootmotion

skeletonrootmotion

       Spine动画在使用过程中需要对动画对象进行位移控制,虽然官方给出SkeletonRootMotion的脚本,但实际项目中通常需要配合移动逻辑一起计算,这个方法就不能很好控制。

        这里提供一种实现方法:通过获取动画位移数据,在移动逻辑中进行插值运算;代码如下:

  1. #if UNITY_EDITOR
  2. using System;
  3. using System.IO;
  4. using Spine;
  5. using Spine.Unity;
  6. using Spine.Unity.AnimationTools;
  7. using System.Collections.Generic;
  8. using UnityEditor;
  9. using UnityEngine;
  10. public class SpineRootMotionMaker : EditorWindow
  11. {
  12. private static GameObject s_PreSelectGameObject = null;
  13. private GameObject m_SelectGameObject = null;
  14. private GameObject m_PrevSelectGameObject = null;
  15. private List<string> m_ListBones = new List<string>();
  16. private int m_nSelectBoneIndex = 0;
  17. private string rootMotionBoneName = "root";
  18. private int rootMotionBoneIndex = -1;
  19. private Bone rootMotionBone = null;
  20. private Dictionary<string, List<Vector3>> DicData = new Dictionary<string, List<Vector3>>();
  21. private List<int> nListFrame = new List<int>();
  22. private Vector2 m_v2TableScroll = Vector2.zero;
  23. [MenuItem("Lop/GenerateSpineRootMotion")]
  24. private static void Init()
  25. {
  26. s_PreSelectGameObject = null;
  27. GetWindow<SpineRootMotionMaker>(true, "Export Root Motion Data");
  28. }
  29. [MenuItem("Assets/RootMotion/GenerateSpineRootMotion")]
  30. static void OpenDialog()
  31. {
  32. bool bCanCreate = true;
  33. if (Selection.activeObject == null) bCanCreate = false;
  34. if (Selection.activeObject && Selection.activeObject.GetType() != typeof(GameObject)) bCanCreate = false;
  35. if (bCanCreate == false)
  36. {
  37. EditorUtility.DisplayDialog("Error", "选中GameObject可以继续进行", "OK");
  38. return;
  39. }
  40. s_PreSelectGameObject = (GameObject)Selection.activeObject;
  41. GetWindow<SpineRootMotionMaker>(true, "Export Root Motion Data");
  42. }
  43. private void OnGUI()
  44. {
  45. if (s_PreSelectGameObject != null)
  46. {
  47. m_SelectGameObject = s_PreSelectGameObject;
  48. s_PreSelectGameObject = null;
  49. }
  50. GUILayout.BeginHorizontal();
  51. EditorGUILayout.LabelField("Select Object", GUILayout.Width(100));
  52. m_SelectGameObject = (GameObject)EditorGUILayout.ObjectField(m_SelectGameObject, typeof(GameObject), false);
  53. GUILayout.EndHorizontal();
  54. RefreshBoneList();
  55. if (m_SelectGameObject != null)
  56. {
  57. GUILayout.BeginHorizontal();
  58. EditorGUILayout.LabelField(" - Select Bone", GUILayout.Width(100));
  59. rootMotionBoneIndex = EditorGUILayout.Popup(rootMotionBoneIndex, m_ListBones.ToArray());
  60. GUILayout.EndHorizontal();
  61. }
  62. OnGUICurve();
  63. GUILayout.FlexibleSpace();
  64. GUILayout.BeginHorizontal();
  65. bool bCheckSize = false;
  66. if (position.width - 200 > 0) bCheckSize = true;
  67. if (bCheckSize) GUILayout.Space(position.width - 200);
  68. if (GUILayout.Button("Cancel", GUILayout.Width(100), GUILayout.ExpandWidth(bCheckSize)) == true)
  69. {
  70. Close();
  71. GUIUtility.ExitGUI();
  72. }
  73. GUI.enabled = (m_SelectGameObject != null && m_nSelectBoneIndex != -1) ? true : false;
  74. if (GUILayout.Button("Create", GUILayout.Width(100), GUILayout.ExpandWidth(bCheckSize)) == true)
  75. {
  76. GameObject objInst = GameObject.Instantiate(m_SelectGameObject);
  77. objInst.name = m_SelectGameObject.name;
  78. objInst.hideFlags = HideFlags.HideAndDontSave;
  79. DicData.Clear();
  80. nListFrame.Clear();
  81. string p = AssetDatabase.GetAssetPath(m_SelectGameObject);
  82. string path = Path.GetDirectoryName(p) + "/" + Path.GetFileNameWithoutExtension(p);
  83. GenerationRootMotion(path, objInst);
  84. GameObject.DestroyImmediate(objInst);
  85. //Close();
  86. //GUIUtility.ExitGUI();
  87. }
  88. GUI.enabled = true;
  89. GUILayout.EndHorizontal();
  90. }
  91. private void OnGUICurve()
  92. {
  93. m_v2TableScroll = GUILayout.BeginScrollView(m_v2TableScroll);
  94. foreach (var kv in DicData)
  95. {
  96. int frame = kv.Value.Count;
  97. float time = 0f;
  98. int idx = 0;
  99. Vector3 tmp = Vector3.zero;
  100. AnimationCurve curve = new AnimationCurve();
  101. foreach (var v in kv.Value)
  102. {
  103. time += 1f / 30f;
  104. tmp += v;
  105. curve.AddKey(time, Vector2.Distance(Vector2.zero, v));
  106. }
  107. GUILayout.BeginHorizontal();
  108. EditorGUILayout.LabelField(kv.Key, GUILayout.Width(100));
  109. EditorGUILayout.CurveField(curve, GUILayout.Height(50));
  110. GUILayout.EndHorizontal();
  111. }
  112. GUILayout.EndScrollView();
  113. }
  114. private void RefreshBoneList()
  115. {
  116. if (m_PrevSelectGameObject == m_SelectGameObject) return;
  117. m_PrevSelectGameObject = m_SelectGameObject;
  118. m_ListBones.Clear();
  119. rootMotionBoneIndex = 0;
  120. if (m_SelectGameObject == null) return;
  121. GameObject objInst = GameObject.Instantiate(m_SelectGameObject);
  122. objInst.name = m_SelectGameObject.name;
  123. objInst.hideFlags = HideFlags.HideAndDontSave;
  124. SkeletonAnimation animation = objInst.GetComponent<SkeletonAnimation>();
  125. if(animation == null)
  126. animation = objInst.GetComponentInChildren<SkeletonAnimation>();
  127. if (animation != null)
  128. {
  129. Skeleton skeleton = animation.Skeleton;
  130. int index = skeleton.FindBoneIndex(rootMotionBoneName);
  131. if (index >= 0)
  132. {
  133. rootMotionBoneIndex = skeleton.FindBoneIndex(rootMotionBoneName);
  134. rootMotionBone = skeleton.Bones.Items[index];
  135. }
  136. else
  137. {
  138. Debug.Log("Bone named \"" + rootMotionBoneName + "\" could not be found.");
  139. rootMotionBoneIndex = 0;
  140. rootMotionBone = skeleton.RootBone;
  141. }
  142. SkeletonData data = animation.SkeletonDataAsset.GetSkeletonData(true);
  143. if (data != null)
  144. {
  145. foreach (var b in data.Bones.Items)
  146. {
  147. m_ListBones.Add(b.Name);
  148. }
  149. }
  150. }
  151. GameObject.DestroyImmediate(objInst);
  152. }
  153. public void GenerationRootMotion(string szFullPath, GameObject obj)
  154. {
  155. SkeletonAnimation skeletonAnimation = obj.GetComponent<SkeletonAnimation>();
  156. if (skeletonAnimation == null)
  157. skeletonAnimation = obj.GetComponentInChildren<SkeletonAnimation>();
  158. if(skeletonAnimation == null)
  159. {
  160. return;
  161. }
  162. Spine.AnimationState state = skeletonAnimation.AnimationState;
  163. Skeleton skeleton = skeletonAnimation.Skeleton;
  164. if (state != null) state.ClearTrack(0);
  165. skeleton.SetToSetupPose();
  166. bool bExistKey = false;
  167. EditorUtility.DisplayProgressBar("RootMotion PreCalclater", "Calculate animation", 0);
  168. int nCount = 0;
  169. float fFps = 30f;
  170. foreach (var item in skeleton.Data.Animations)
  171. {
  172. if (item.Name.Contains("_NoMotion"))
  173. continue;
  174. if (DicData.ContainsKey(item.Name))
  175. {
  176. Debug.Log("contain key : " + item.Name);
  177. continue;
  178. }
  179. int nFrame = (int)(item.Duration * fFps);
  180. Vector2 vPrevPosition = Vector2.zero;
  181. List<Vector3> vListPos = new List<Vector3>();
  182. float lastTime = 0f;
  183. for (int i = 1; i <= nFrame; i++)
  184. {
  185. float end = (item.Duration / nFrame) * i;
  186. Vector3 vTemp = GetAnimationRootMotion(lastTime, end, item);
  187. if (vTemp.sqrMagnitude > 0.0f)
  188. bExistKey = true;
  189. vListPos.Add(vTemp);
  190. }
  191. DicData.Add(item.Name, vListPos);
  192. nListFrame.Add(nFrame);
  193. EditorUtility.DisplayProgressBar("RootMotion PreCalclater", "Calculate animation", 1.0f / (int)skeleton.Data.Animations.Count * nCount);
  194. nCount++;
  195. }
  196. EditorUtility.ClearProgressBar();
  197. if (bExistKey == false)
  198. {
  199. return;
  200. }
  201. FileStream fs = new FileStream(szFullPath + "_RootMotionData.bytes", FileMode.Create, FileAccess.Write);
  202. BinaryWriter bw = new BinaryWriter(fs);
  203. int nIndex = 0;
  204. bw.Write(DicData.Count);
  205. foreach (var Pair in DicData)
  206. {
  207. Debug.Log("Export : " + Pair.Key + " " + nListFrame[nIndex]);
  208. bw.Write(Pair.Key);
  209. bw.Write(nListFrame[nIndex]);
  210. for (int i = 0; i < Pair.Value.Count; i++)
  211. {
  212. bw.Write(Pair.Value[i].x);
  213. bw.Write(Pair.Value[i].y);
  214. bw.Write(Pair.Value[i].z);
  215. }
  216. nIndex++;
  217. }
  218. Debug.Log("Export : " + szFullPath + "_RootMotionData.bytes");
  219. bw.Close();
  220. fs.Dispose();
  221. AssetDatabase.Refresh();
  222. }
  223. public Vector2 GetAnimationRootMotion(float startTime, float endTime,
  224. Spine.Animation animation)
  225. {
  226. var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
  227. if (timeline != null)
  228. {
  229. return GetTimelineMovementDelta(startTime, endTime, timeline, animation);
  230. }
  231. return Vector2.zero;
  232. }
  233. private Vector2 GetTimelineMovementDelta(float startTime, float endTime,
  234. TranslateTimeline timeline, Spine.Animation animation)
  235. {
  236. Vector2 currentDelta;
  237. if (startTime > endTime) // Looped
  238. currentDelta = (timeline.Evaluate(animation.Duration) - timeline.Evaluate(startTime))
  239. + (timeline.Evaluate(endTime) - timeline.Evaluate(0));
  240. else if (startTime != endTime) // Non-looped
  241. currentDelta = timeline.Evaluate(endTime) - timeline.Evaluate(startTime);
  242. else
  243. currentDelta = Vector2.zero;
  244. return currentDelta;
  245. }
  246. }
  247. #endif

移动逻辑代码如下:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using System.IO;
  5. using Spine.Unity;
  6. using Spine;
  7. public class test : MonoBehaviour
  8. {
  9. public SkeletonAnimation spine;
  10. public TextAsset rootmotion;
  11. private Dictionary<string, List<Vector3>> m_DicRootPositionPart1 = null;
  12. public bool IsRootMotion = true;
  13. public float Speed = 1f;
  14. public bool DirectLeft = false;
  15. float startTime = -1f;
  16. string curAction = string.Empty;
  17. private float prevFrame = 0;
  18. private void Awake()
  19. {
  20. Application.targetFrameRate = 30;
  21. }
  22. // Start is called before the first frame update
  23. void Start()
  24. {
  25. m_DicRootPositionPart1 = LoadRootMotion(rootmotion);
  26. }
  27. private void OnEnable()
  28. {
  29. }
  30. // Update is called once per frame
  31. void Update()
  32. {
  33. spine.Skeleton.FlipX = DirectLeft;
  34. if(IsRootMotion && startTime >= 0 && !string.IsNullOrEmpty(curAction))
  35. {
  36. TrackEntry track = spine.AnimationState.GetCurrent(0);
  37. var animation = track.Animation;
  38. float start = track.AnimationLast;
  39. if (start < 0)
  40. start = 0f;
  41. float end = track.AnimationTime;
  42. if (start == end)
  43. {
  44. startTime = -1f;
  45. return;
  46. }
  47. float fFrame = (int)(animation.Duration * 30.0f);
  48. float fPrevFrame = prevFrame;
  49. float fCurFrame = end * 30.0f;
  50. prevFrame = fCurFrame;
  51. fPrevFrame = Mathf.Clamp(fPrevFrame, 0, fFrame - 0.001f);
  52. fCurFrame = Mathf.Clamp(fCurFrame, 0, fFrame - 0.001f);
  53. Vector3 vec = GetAniDistance(curAction, fPrevFrame, fCurFrame);
  54. vec *= Speed;
  55. vec.x *= DirectLeft ? -1 : 1;
  56. transform.position += transform.TransformVector(vec);
  57. }
  58. if (Input.GetKeyDown(KeyCode.J))
  59. {
  60. startTime = Time.time;
  61. prevFrame = 0f;
  62. curAction = "Jump";
  63. spine.AnimationState.SetAnimation(0, curAction, false);
  64. }
  65. if (Input.GetKeyDown(KeyCode.K))
  66. {
  67. DirectLeft = !DirectLeft;
  68. }
  69. if (Input.GetKeyDown(KeyCode.L))
  70. {
  71. spine.AnimationState.ClearTracks();
  72. spine.Skeleton.SetToSetupPose();
  73. spine.AnimationState.SetAnimation(0, "Jump", false);
  74. }
  75. }
  76. public Dictionary<string, List<Vector3>> LoadRootMotion(TextAsset textAsset)
  77. {
  78. if (textAsset == null) return null;
  79. Dictionary<string, List<Vector3>> DicPosition = new Dictionary<string, List<Vector3>>();
  80. MemoryStream ms = new MemoryStream(textAsset.bytes);
  81. BinaryReader br = new BinaryReader(ms);
  82. int nClipCount = br.ReadInt32();
  83. for (int i = 0; i < nClipCount; i++)
  84. {
  85. List<Vector3> vList = new List<Vector3>();
  86. string szName = br.ReadString();
  87. int nFrame = br.ReadInt32();
  88. vList.Add(Vector3.zero);
  89. for (int j = 0; j < nFrame; j++)
  90. {
  91. vList.Add(new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()));
  92. }
  93. DicPosition.Add(szName, vList);
  94. }
  95. br.Close();
  96. ms.Dispose();
  97. return DicPosition;
  98. }
  99. public Vector3 GetAniDistance(string szName, float fPrevFrame, float fCurFrame)
  100. {
  101. if (m_DicRootPositionPart1 == null)
  102. return Vector3.zero;
  103. List<Vector3> vList = null;
  104. if (m_DicRootPositionPart1 != null && m_DicRootPositionPart1.ContainsKey(szName))
  105. vList = m_DicRootPositionPart1[szName];
  106. if (null == vList)
  107. return Vector3.zero;
  108. Vector3 v1 = GetInterpolationValue(vList, fPrevFrame);
  109. Vector3 v2 = GetInterpolationValue(vList, fCurFrame);
  110. Vector3 vDistance = v2 - v1;
  111. vDistance.z = 0;
  112. return vDistance;
  113. }
  114. private Vector3 GetInterpolationValue(List<Vector3> vList, float fFrame)
  115. {
  116. try
  117. {
  118. int nFrame = (int)fFrame;
  119. return Vector3.Lerp(vList[nFrame], vList[nFrame + 1], fFrame - (float)nFrame);
  120. }
  121. catch
  122. {
  123. return Vector3.zero;
  124. }
  125. }
  126. }

关闭Spine动作自身的动画位移代码:

Assets\Spine\Runtime\spine-csharp\Animation.cs脚本内TranslateTimeline类添加如下代码:

总之,这个方法比较方便程序逻辑控制,但是需要导出配置文件,并且动画有更新就需要重新导出,各有利弊吧!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/118466
推荐阅读
相关标签
  

闽ICP备14008679号