赞
踩
Spine动画在使用过程中需要对动画对象进行位移控制,虽然官方给出SkeletonRootMotion的脚本,但实际项目中通常需要配合移动逻辑一起计算,这个方法就不能很好控制。
这里提供一种实现方法:通过获取动画位移数据,在移动逻辑中进行插值运算;代码如下:
- #if UNITY_EDITOR
- using System;
- using System.IO;
- using Spine;
- using Spine.Unity;
- using Spine.Unity.AnimationTools;
- using System.Collections.Generic;
- using UnityEditor;
- using UnityEngine;
-
- public class SpineRootMotionMaker : EditorWindow
- {
- private static GameObject s_PreSelectGameObject = null;
- private GameObject m_SelectGameObject = null;
- private GameObject m_PrevSelectGameObject = null;
- private List<string> m_ListBones = new List<string>();
- private int m_nSelectBoneIndex = 0;
- private string rootMotionBoneName = "root";
- private int rootMotionBoneIndex = -1;
- private Bone rootMotionBone = null;
- private Dictionary<string, List<Vector3>> DicData = new Dictionary<string, List<Vector3>>();
- private List<int> nListFrame = new List<int>();
-
- private Vector2 m_v2TableScroll = Vector2.zero;
-
- [MenuItem("Lop/GenerateSpineRootMotion")]
- private static void Init()
- {
- s_PreSelectGameObject = null;
- GetWindow<SpineRootMotionMaker>(true, "Export Root Motion Data");
- }
-
- [MenuItem("Assets/RootMotion/GenerateSpineRootMotion")]
- static void OpenDialog()
- {
- bool bCanCreate = true;
- if (Selection.activeObject == null) bCanCreate = false;
- if (Selection.activeObject && Selection.activeObject.GetType() != typeof(GameObject)) bCanCreate = false;
-
- if (bCanCreate == false)
- {
- EditorUtility.DisplayDialog("Error", "选中GameObject可以继续进行", "OK");
- return;
- }
-
- s_PreSelectGameObject = (GameObject)Selection.activeObject;
- GetWindow<SpineRootMotionMaker>(true, "Export Root Motion Data");
- }
-
- private void OnGUI()
- {
- if (s_PreSelectGameObject != null)
- {
- m_SelectGameObject = s_PreSelectGameObject;
- s_PreSelectGameObject = null;
- }
- GUILayout.BeginHorizontal();
- EditorGUILayout.LabelField("Select Object", GUILayout.Width(100));
- m_SelectGameObject = (GameObject)EditorGUILayout.ObjectField(m_SelectGameObject, typeof(GameObject), false);
- GUILayout.EndHorizontal();
-
- RefreshBoneList();
- if (m_SelectGameObject != null)
- {
- GUILayout.BeginHorizontal();
- EditorGUILayout.LabelField(" - Select Bone", GUILayout.Width(100));
- rootMotionBoneIndex = EditorGUILayout.Popup(rootMotionBoneIndex, m_ListBones.ToArray());
- GUILayout.EndHorizontal();
- }
-
- OnGUICurve();
-
- GUILayout.FlexibleSpace();
- GUILayout.BeginHorizontal();
- bool bCheckSize = false;
-
- if (position.width - 200 > 0) bCheckSize = true;
- if (bCheckSize) GUILayout.Space(position.width - 200);
-
- if (GUILayout.Button("Cancel", GUILayout.Width(100), GUILayout.ExpandWidth(bCheckSize)) == true)
- {
- Close();
- GUIUtility.ExitGUI();
- }
- GUI.enabled = (m_SelectGameObject != null && m_nSelectBoneIndex != -1) ? true : false;
- if (GUILayout.Button("Create", GUILayout.Width(100), GUILayout.ExpandWidth(bCheckSize)) == true)
- {
- GameObject objInst = GameObject.Instantiate(m_SelectGameObject);
- objInst.name = m_SelectGameObject.name;
- objInst.hideFlags = HideFlags.HideAndDontSave;
- DicData.Clear();
- nListFrame.Clear();
-
- string p = AssetDatabase.GetAssetPath(m_SelectGameObject);
- string path = Path.GetDirectoryName(p) + "/" + Path.GetFileNameWithoutExtension(p);
- GenerationRootMotion(path, objInst);
-
- GameObject.DestroyImmediate(objInst);
-
- //Close();
- //GUIUtility.ExitGUI();
- }
- GUI.enabled = true;
-
- GUILayout.EndHorizontal();
- }
-
- private void OnGUICurve()
- {
- m_v2TableScroll = GUILayout.BeginScrollView(m_v2TableScroll);
- foreach (var kv in DicData)
- {
- int frame = kv.Value.Count;
- float time = 0f;
- int idx = 0;
- Vector3 tmp = Vector3.zero;
- AnimationCurve curve = new AnimationCurve();
- foreach (var v in kv.Value)
- {
- time += 1f / 30f;
- tmp += v;
- curve.AddKey(time, Vector2.Distance(Vector2.zero, v));
- }
- GUILayout.BeginHorizontal();
- EditorGUILayout.LabelField(kv.Key, GUILayout.Width(100));
- EditorGUILayout.CurveField(curve, GUILayout.Height(50));
- GUILayout.EndHorizontal();
- }
- GUILayout.EndScrollView();
- }
-
- private void RefreshBoneList()
- {
- if (m_PrevSelectGameObject == m_SelectGameObject) return;
- m_PrevSelectGameObject = m_SelectGameObject;
- m_ListBones.Clear();
- rootMotionBoneIndex = 0;
- if (m_SelectGameObject == null) return;
-
- GameObject objInst = GameObject.Instantiate(m_SelectGameObject);
- objInst.name = m_SelectGameObject.name;
- objInst.hideFlags = HideFlags.HideAndDontSave;
-
- SkeletonAnimation animation = objInst.GetComponent<SkeletonAnimation>();
- if(animation == null)
- animation = objInst.GetComponentInChildren<SkeletonAnimation>();
- if (animation != null)
- {
- Skeleton skeleton = animation.Skeleton;
- int index = skeleton.FindBoneIndex(rootMotionBoneName);
- if (index >= 0)
- {
- rootMotionBoneIndex = skeleton.FindBoneIndex(rootMotionBoneName);
- rootMotionBone = skeleton.Bones.Items[index];
- }
- else
- {
- Debug.Log("Bone named \"" + rootMotionBoneName + "\" could not be found.");
- rootMotionBoneIndex = 0;
- rootMotionBone = skeleton.RootBone;
- }
-
- SkeletonData data = animation.SkeletonDataAsset.GetSkeletonData(true);
- if (data != null)
- {
- foreach (var b in data.Bones.Items)
- {
- m_ListBones.Add(b.Name);
- }
- }
- }
- GameObject.DestroyImmediate(objInst);
- }
-
- public void GenerationRootMotion(string szFullPath, GameObject obj)
- {
- SkeletonAnimation skeletonAnimation = obj.GetComponent<SkeletonAnimation>();
- if (skeletonAnimation == null)
- skeletonAnimation = obj.GetComponentInChildren<SkeletonAnimation>();
- if(skeletonAnimation == null)
- {
- return;
- }
- Spine.AnimationState state = skeletonAnimation.AnimationState;
- Skeleton skeleton = skeletonAnimation.Skeleton;
- if (state != null) state.ClearTrack(0);
- skeleton.SetToSetupPose();
-
- bool bExistKey = false;
- EditorUtility.DisplayProgressBar("RootMotion PreCalclater", "Calculate animation", 0);
- int nCount = 0;
- float fFps = 30f;
- foreach (var item in skeleton.Data.Animations)
- {
- if (item.Name.Contains("_NoMotion"))
- continue;
- if (DicData.ContainsKey(item.Name))
- {
- Debug.Log("contain key : " + item.Name);
- continue;
- }
- int nFrame = (int)(item.Duration * fFps);
- Vector2 vPrevPosition = Vector2.zero;
-
- List<Vector3> vListPos = new List<Vector3>();
- float lastTime = 0f;
- for (int i = 1; i <= nFrame; i++)
- {
- float end = (item.Duration / nFrame) * i;
- Vector3 vTemp = GetAnimationRootMotion(lastTime, end, item);
- if (vTemp.sqrMagnitude > 0.0f)
- bExistKey = true;
- vListPos.Add(vTemp);
- }
-
- DicData.Add(item.Name, vListPos);
- nListFrame.Add(nFrame);
-
- EditorUtility.DisplayProgressBar("RootMotion PreCalclater", "Calculate animation", 1.0f / (int)skeleton.Data.Animations.Count * nCount);
- nCount++;
- }
- EditorUtility.ClearProgressBar();
-
- if (bExistKey == false)
- {
- return;
- }
- FileStream fs = new FileStream(szFullPath + "_RootMotionData.bytes", FileMode.Create, FileAccess.Write);
- BinaryWriter bw = new BinaryWriter(fs);
-
- int nIndex = 0;
- bw.Write(DicData.Count);
- foreach (var Pair in DicData)
- {
- Debug.Log("Export : " + Pair.Key + " " + nListFrame[nIndex]);
- bw.Write(Pair.Key);
- bw.Write(nListFrame[nIndex]);
- for (int i = 0; i < Pair.Value.Count; i++)
- {
- bw.Write(Pair.Value[i].x);
- bw.Write(Pair.Value[i].y);
- bw.Write(Pair.Value[i].z);
- }
- nIndex++;
- }
- Debug.Log("Export : " + szFullPath + "_RootMotionData.bytes");
- bw.Close();
- fs.Dispose();
- AssetDatabase.Refresh();
- }
-
- public Vector2 GetAnimationRootMotion(float startTime, float endTime,
- Spine.Animation animation)
- {
-
- var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
- if (timeline != null)
- {
- return GetTimelineMovementDelta(startTime, endTime, timeline, animation);
- }
- return Vector2.zero;
- }
-
- private Vector2 GetTimelineMovementDelta(float startTime, float endTime,
- TranslateTimeline timeline, Spine.Animation animation)
- {
-
- Vector2 currentDelta;
- if (startTime > endTime) // Looped
- currentDelta = (timeline.Evaluate(animation.Duration) - timeline.Evaluate(startTime))
- + (timeline.Evaluate(endTime) - timeline.Evaluate(0));
- else if (startTime != endTime) // Non-looped
- currentDelta = timeline.Evaluate(endTime) - timeline.Evaluate(startTime);
- else
- currentDelta = Vector2.zero;
- return currentDelta;
- }
- }
- #endif
移动逻辑代码如下:
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using System.IO;
- using Spine.Unity;
- using Spine;
-
- public class test : MonoBehaviour
- {
- public SkeletonAnimation spine;
- public TextAsset rootmotion;
- private Dictionary<string, List<Vector3>> m_DicRootPositionPart1 = null;
- public bool IsRootMotion = true;
- public float Speed = 1f;
- public bool DirectLeft = false;
- float startTime = -1f;
- string curAction = string.Empty;
- private float prevFrame = 0;
- private void Awake()
- {
- Application.targetFrameRate = 30;
- }
- // Start is called before the first frame update
- void Start()
- {
- m_DicRootPositionPart1 = LoadRootMotion(rootmotion);
- }
-
- private void OnEnable()
- {
-
- }
-
- // Update is called once per frame
- void Update()
- {
- spine.Skeleton.FlipX = DirectLeft;
- if(IsRootMotion && startTime >= 0 && !string.IsNullOrEmpty(curAction))
- {
- TrackEntry track = spine.AnimationState.GetCurrent(0);
- var animation = track.Animation;
- float start = track.AnimationLast;
- if (start < 0)
- start = 0f;
- float end = track.AnimationTime;
- if (start == end)
- {
- startTime = -1f;
- return;
- }
-
- float fFrame = (int)(animation.Duration * 30.0f);
- float fPrevFrame = prevFrame;
- float fCurFrame = end * 30.0f;
- prevFrame = fCurFrame;
-
- fPrevFrame = Mathf.Clamp(fPrevFrame, 0, fFrame - 0.001f);
- fCurFrame = Mathf.Clamp(fCurFrame, 0, fFrame - 0.001f);
-
- Vector3 vec = GetAniDistance(curAction, fPrevFrame, fCurFrame);
- vec *= Speed;
- vec.x *= DirectLeft ? -1 : 1;
- transform.position += transform.TransformVector(vec);
- }
- if (Input.GetKeyDown(KeyCode.J))
- {
- startTime = Time.time;
- prevFrame = 0f;
- curAction = "Jump";
- spine.AnimationState.SetAnimation(0, curAction, false);
- }
- if (Input.GetKeyDown(KeyCode.K))
- {
- DirectLeft = !DirectLeft;
- }
- if (Input.GetKeyDown(KeyCode.L))
- {
- spine.AnimationState.ClearTracks();
- spine.Skeleton.SetToSetupPose();
- spine.AnimationState.SetAnimation(0, "Jump", false);
- }
- }
-
- public Dictionary<string, List<Vector3>> LoadRootMotion(TextAsset textAsset)
- {
- if (textAsset == null) return null;
- Dictionary<string, List<Vector3>> DicPosition = new Dictionary<string, List<Vector3>>();
-
- MemoryStream ms = new MemoryStream(textAsset.bytes);
- BinaryReader br = new BinaryReader(ms);
-
- int nClipCount = br.ReadInt32();
-
- for (int i = 0; i < nClipCount; i++)
- {
- List<Vector3> vList = new List<Vector3>();
-
- string szName = br.ReadString();
- int nFrame = br.ReadInt32();
-
- vList.Add(Vector3.zero);
- for (int j = 0; j < nFrame; j++)
- {
- vList.Add(new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()));
- }
- DicPosition.Add(szName, vList);
- }
-
- br.Close();
- ms.Dispose();
- return DicPosition;
- }
-
- public Vector3 GetAniDistance(string szName, float fPrevFrame, float fCurFrame)
- {
- if (m_DicRootPositionPart1 == null)
- return Vector3.zero;
- List<Vector3> vList = null;
-
- if (m_DicRootPositionPart1 != null && m_DicRootPositionPart1.ContainsKey(szName))
- vList = m_DicRootPositionPart1[szName];
-
- if (null == vList)
- return Vector3.zero;
- Vector3 v1 = GetInterpolationValue(vList, fPrevFrame);
- Vector3 v2 = GetInterpolationValue(vList, fCurFrame);
-
- Vector3 vDistance = v2 - v1;
-
- vDistance.z = 0;
-
- return vDistance;
- }
- private Vector3 GetInterpolationValue(List<Vector3> vList, float fFrame)
- {
- try
- {
- int nFrame = (int)fFrame;
- return Vector3.Lerp(vList[nFrame], vList[nFrame + 1], fFrame - (float)nFrame);
- }
- catch
- {
- return Vector3.zero;
- }
- }
- }
关闭Spine动作自身的动画位移代码:
Assets\Spine\Runtime\spine-csharp\Animation.cs脚本内TranslateTimeline类添加如下代码:
总之,这个方法比较方便程序逻辑控制,但是需要导出配置文件,并且动画有更新就需要重新导出,各有利弊吧!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。