目录
数据导出:
数据导入
解析数据播放动画
根据曲线插值每帧计算数据,模拟Unity中动画播放系统,实现不通过动画控制器播放动画的功能,解决帧同步中动画结果无法预测问题,其实有可能涉及到对动画插值算法的模拟。
数据导出:
首先我们要大概梳理一下Unity中动画控制器播放动画时的原理和动画控制器中动画数据的存储方式。
在Unity中有三个组件,一个是Animator动画控制器,一个是Animation,还有一个是AnimationClip,按照官方文档的介绍是
Use the Animator component to assign animation to a GameObject in your Scene. The Animator component requires a reference to an Animator Controller which defines which animation clips to use, and controls when and how to blend and transition between them.
大概翻译过来就是:
使用 Animator 组件可以将动画分配给场景中的游戏对象。Animator 组件需要引用 Animator Controller(动画控制器),后者定义要使用哪些Animation(动画片段),并控制何时以及如何在Animation(动画片段)之间进行混合和过渡。
关于Animator参考:Unity - Manual: Animator component
Animator Controller 参考:Unity - Manual: Animator Controller
Animation Clip参考:Unity - Manual: Animation tab
首先我们如果要读取一个动画控制器中动画片段的数据,我们先要了解一个Unity提供给我们的API------AnimationUnitily,这个API给我们提供了修改和编辑动画片段的一些函数,同样我们也可以通过将这些数据存储下来,实现我们数据导出功能,不过这个API只能在编辑器模式下使用哦,需要放到Editor文件下中,这个API具体的我们可以参考官方文档AnimationUnility。
既然我们要保存这些数据,那我们首先要了解Unity中动画数据的存储方式,我们首先通过AnimationUtility.GetCurveBindings方式得到float曲线绑定数据,这个方法会返回EditorCurveBinding[]这样一个数组,这个里面会存储一个Animation Clip(动画片段)中所有的float曲线绑定(直白讲大概就是这样的,一个动画片段有很多动画曲线数据,这些曲线数据对应这个对象中不同的类型的值,比如坐标,旋转等等),
EditorCurveBinding这个类会定义如何将一个曲线附加到它所控制的对象,其中:
path:存储对象转换的路径,或者说是相对于父物体的路径,
PropertyName:当前这个动画属性的名称
之后我们通过AnimationUtility.GetEditorCurve这个方法获取AnimationCurve对象,这个对象存储的是一个关键帧的集合,随着时间的推移去计算值,AnimationCurve这个类中会定义所有这个曲线中每一个关键节点的信息(这个节点信息就是我们在Unity中看到的这些节点,见下图)
也就是keys这个字段,这个字段是一个Keyframe[]这样的数组,每一个Keyframe对应一个节点的值,这个节点就是可以对应到动画曲线中的单个关键帧信息,存储这起始时间,值,曲线正切值(或者说函数在该点的导数),在我们这个例子中,我们根据时间去获取对应属性在相同时间下值,在数据导入时对这些数据进行解析,之后通过其他方法去更改这些对应属性的值来实现不通过动画控制器播放动画的功能,其中最关键的动画曲线采样算法是参考Unity动画实现原理探究这篇文章来实现的。
具体代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using UnityEditor;
using UnityEngine;
public class AnimationDataOutput : MonoBehaviour
{
[Header("文件名")]
public string OutPutName = "";
[Header("动画片段")]
public AnimationClip currentClip;
string filePath = "";
[ContextMenu("OutputData")]
void OutputData()
{
if (string.IsNullOrEmpty(OutPutName))
{
Debug.LogError("文件名不能为空");
return;
}
string folderPath = Path.Combine(Application.persistentDataPath, "AnimationData");
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
filePath = folderPath + "/" + OutPutName + ".bytes";
float frameStep = 1 / currentClip.frameRate;
//总帧数=总时间/每帧时间
float frameCount = currentClip.averageDuration / frameStep; //持续时间
AnimationData animationData = new AnimationData();
animationData.m_bIsLoop = currentClip.isLooping;
animationData.m_fDurationTime = currentClip.averageDuration;
animationData.m_fFrameRate = currentClip.frameRate;
animationData.m_sAnimationName = currentClip.name;
foreach (var item in AnimationUtility.GetCurveBindings(currentClip))
{
KeyData keyData = new KeyData();
keyData.m_fDurationTime = currentClip.averageDuration;
keyData.m_sPath = item.path;
keyData.m_sPropertyName = item.propertyName;
AnimationCurve animationCurve = AnimationUtility.GetEditorCurve(currentClip, item);
for (int i = 0; i < animationCurve.keys.Length; i++)
{
KeyFrameInfo keyFrameInfo = new KeyFrameInfo();
keyFrameInfo.time = animationCurve.keys[i].time;
keyFrameInfo.value = animationCurve.keys[i].value;
keyFrameInfo.inTangent = animationCurve.keys[i].inTangent;
keyFrameInfo.outTangent = animationCurve.keys[i].outTangent;
keyFrameInfo.inWeight = animationCurve.keys[i].inWeight;
keyFrameInfo.outWeight = animationCurve.keys[i].outWeight;
keyFrameInfo.weightedMode = animationCurve.keys[i].weightedMode;
keyData.m_szKeyData.Add(keyFrameInfo);
}
animationData.m_szFrameData.Add(keyData);
}
SaveObjectFile(filePath, animationData);
}
public static void SaveObjectFile(string fileName, object data)
{
Stream write = null;
try
{
FileInfo fileInfo = new FileInfo(fileName);
if (fileInfo.Exists) fileInfo.Delete();
write = fileInfo.OpenWrite();
BinaryFormatter binaryFormatter = new BinaryFormatter();
byte[] value = CompressedToBytes(data);
binaryFormatter.Serialize(write, value);
}
catch (System.Exception)
{
throw;
}
finally
{
if (write != null)
{
write.Close();
}
}
}
public static object GetObjectData(string fileName)
{
Stream Read = null;
string strErr = "";
try
{
FileInfo FI = new FileInfo(fileName);
if (FI.Exists)
{
Read = FI.OpenRead();
BinaryFormatter BF = new BinaryFormatter();
byte[] aa = (byte[])BF.Deserialize(Read);
return DecompressToObject(aa);
}
else
{
return null;
}
}
catch (Exception ex)
{
strErr = ex.ToString();
}
finally
{
if (Read != null)
{
Read.Close();
}
}
return null;
}
public static byte[] CompressedToBytes(object obj)
{
MemoryStream ms = new MemoryStream();
DeflateStream zip = new DeflateStream(ms, CompressionMode.Compress, true);
try
{
BinaryFormatter serializer = new BinaryFormatter();
serializer.Serialize(zip, obj);
zip.Close();
byte[] ary = ms.ToArray();
ms.Close();
return ary;
}
catch (Exception)
{
//Log.write(e.Message);
zip.Close();
ms.Close();
return null;
}
}
public static object DecompressToObject(byte[] ary)
{
MemoryStream ms = new MemoryStream(ary);
DeflateStream UnZip = new DeflateStream(ms, CompressionMode.Decompress);
try
{
BinaryFormatter serializer = new BinaryFormatter();
object obj = serializer.Deserialize(UnZip);
UnZip.Close();
ms.Close();
return obj;
}
catch (Exception)
{
//Log.write(e.Message);
UnZip.Close();
ms.Close();
return null;
}
}
}
数据导入
数据导入就简单了,对我们压缩好的数据进行解析。
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
public class AnimationDataInput : MonoBehaviour
{
public static AnimationDataInput instance;
public static List<KeyData> LoadAnimationData(string filePath)
{
filePath = Path.Combine(Application.persistentDataPath, filePath);
object data=GetObjectData(filePath);
return (List<KeyData>)data;
}
public static void SaveObjectFile(string fileName, object data)
{
Stream write = null;
try
{
FileInfo fileInfo = new FileInfo(fileName);
if (fileInfo.Exists) fileInfo.Delete();
write = fileInfo.OpenWrite();
BinaryFormatter binaryFormatter = new BinaryFormatter();
byte[] value = CompressedToBytes(data);
binaryFormatter.Serialize(write, value);
}
catch (System.Exception)
{
throw;
}
finally
{
if (write != null)
{
write.Close();
}
}
}
public static object GetObjectData(string fileName)
{
Stream Read = null;
string strErr = "";
try
{
FileInfo FI = new FileInfo(fileName);
if (FI.Exists)
{
Read = FI.OpenRead();
BinaryFormatter BF = new BinaryFormatter();
byte[] aa = (byte[])BF.Deserialize(Read);
return DecompressToObject(aa);
}
else
{
return null;
}
}
catch (Exception ex)
{
strErr = ex.ToString();
}
finally
{
if (Read != null)
{
Read.Close();
}
}
return null;
}
public static byte[] CompressedToBytes(object obj)
{
MemoryStream ms = new MemoryStream();
DeflateStream zip = new DeflateStream(ms, CompressionMode.Compress, true);
try
{
BinaryFormatter serializer = new BinaryFormatter();
serializer.Serialize(zip, obj);
zip.Close();
byte[] ary = ms.ToArray();
ms.Close();
return ary;
}
catch (Exception)
{
//Log.write(e.Message);
zip.Close();
ms.Close();
return null;
}
}
public static object DecompressToObject(byte[] ary)
{
MemoryStream ms = new MemoryStream(ary);
DeflateStream UnZip = new DeflateStream(ms, CompressionMode.Decompress);
try
{
BinaryFormatter serializer = new BinaryFormatter();
object obj = serializer.Deserialize(UnZip);
UnZip.Close();
ms.Close();
return obj;
}
catch (Exception)
{
//Log.write(e.Message);
UnZip.Close();
ms.Close();
return null;
}
}
}
[Serializable]
public class AnimationData
{
public float m_fDurationTime { get; set; }
public string m_sAnimationName { get; set; }
public float m_fFrameRate { get; set; }
public float m_fFrameStep
{
get
{
return 1 / m_fFrameRate;
}
}
public bool m_bIsLoop { get; set; }
public List<KeyData> m_szFrameData { get; set; } = new List<KeyData>();
}
[SerializeField]
public class KeyData
{
public float m_fDurationTime { get; set; }
public string m_sPath { get; set; }
public string m_sPropertyName { get; set; }
public List<KeyFrameInfo> m_szKeyData { get; set; } = new List<KeyFrameInfo>();
}
[Serializable]
public class KeyFrameInfo
{
//
// 摘要:
// The time of the keyframe.
public float time { get; set; }
//
// 摘要:
// The value of the curve at keyframe.
public float value { get; set; }
//
// 摘要:
// Sets the incoming tangent for this key. The incoming tangent affects the slope
// of the curve from the previous key to this key.
public float inTangent { get; set; }
//
// 摘要:
// Sets the outgoing tangent for this key. The outgoing tangent affects the slope
// of the curve from this key to the next key.
public float outTangent { get; set; }
//
// 摘要:
// Sets the incoming weight for this key. The incoming weight affects the slope
// of the curve from the previous key to this key.
public float inWeight { get; set; }
//
// 摘要:
// Sets the outgoing weight for this key. The outgoing weight affects the slope
// of the curve from this key to the next key.
public float outWeight { get; set; }
//
// 摘要:
// Weighted mode for the keyframe.
public WeightedMode weightedMode { get; set; }
}
解析数据播放动画
之前我们存储的属性名这会派上用场了,通过对比来决定当前值所代表的含义。
/// <summary>
/// 设置数据
/// </summary>
/// <param name="trans"></param>
/// <param name="value"></param>
/// <param name="propertyName"></param>
void SetData(Transform trans, FP value, string propertyName)
{
//YhLog.YhTempLog("set value start TransName=" + trans.name + ",value=" + value + ",propertyName=" + propertyName + ",pos=" + trans.position + ",localpos=" + trans.localPosition) ;
if (propertyName == "m_LocalPosition.x")
{
TSVector data = trans.localPosition.ToTSVector();
data.x = value;
trans.localPosition = data.ToVector();
}
else if (propertyName == "m_LocalPosition.y")
{
TSVector data = trans.localPosition.ToTSVector();
data.y = value;
trans.localPosition = data.ToVector();
}
else if (propertyName == "m_LocalPosition.z")
{
TSVector data = trans.localPosition.ToTSVector();
data.z = value;
trans.localPosition = data.ToVector();
}
else if (propertyName == "m_LocalRotation.x")
{
Quaternion data = trans.localRotation;
data.x = value.AsFloat();
trans.localRotation = data;
}
else if (propertyName == "m_LocalRotation.y")
{
Quaternion data = trans.localRotation;
data.y = value.AsFloat();
trans.localRotation = data;
}
else if (propertyName == "m_LocalRotation.z")
{
Quaternion data = trans.localRotation;
data.z = value.AsFloat();
trans.localRotation = data;
}
else if (propertyName == "m_LocalRotation.w")
{
Quaternion data = trans.localRotation;
data.w = value.AsFloat();
trans.localRotation = data;
}
else if (propertyName == "m_LocalScale.x")
{
Vector3 data = trans.localScale;
data.x = value.AsFloat();
trans.localScale = data;
}
else if (propertyName == "m_LocalScale.y")
{
Vector3 data = trans.localScale;
data.y = value.AsFloat();
trans.localScale = data;
}
else if (propertyName == "m_LocalScale.z")
{
Vector3 data = trans.localScale;
data.z = value.AsFloat();
trans.localScale = data;
}
}
记录一个碰到的问题:
在序列化数据的过程中发现,所有序列化对象中的字段都必须要为可序列化字段,如List<T>类型的T必须也为序列化对象。
先写这么多吧,具体原理有时间再好好梳理一下。
~.~文章来源:https://www.toymoban.com/news/detail-412515.html
参考:Unity动画实现原理探究文章来源地址https://www.toymoban.com/news/detail-412515.html
到了这里,关于Unity中实现动画数据导出导入的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!