Unity 生成自定义动画格式并播放

打印 上一主题 下一主题

主题 1007|帖子 1007|积分 3021

原文链接:https://www.cnblogs.com/jingjiangtao/p/16666514.html
目的

Unity的AnimationClip.SetCurve()只在Editor中运行时有用,打包后运行时只对legacy的AnimationClip有用,对其它类型的动画Generic和Humanoid都不起作用。https://docs.unity3d.com/ScriptReference/AnimationClip.SetCurve.html
所以如果想在运行时加载和播放动画,只能用自定义格式。
注意:此自定义格式的生成、加载和播放,都不涉及重定向,通过特定的模型生成的动画,只能在这个模型上播放。
注意:本文只实现了人体模型的自定义动画,其它模型的实现思路相同。
思路

自定义动画格式的生成和播放有三个步骤:

  • 生成:把已经存在的动画片段转换成自定义动画格式
  • 序列化:保存和加载自定义动画格式
  • 播放:播放自定义动画格式
大致过程就是,以一定的帧率记录模型每个物体的position和rotation值,保存在自定义格式中,这种格式要能序列化。之后加载自定义格式,并以一定的帧率每帧设置物体的position和rotation,达到播放的效果。
第三方库

MessagePack

https://github.com/neuecc/MessagePack-CSharp
MessagePack是一种数据交换格式,可以生成体积更小的序列化文件,而且序列化和反序列化的速度更快。动画文件数据量比较大,生成的文件也比较大,所以最好选择生成文件体积更小的序列化方案。
实现

实体类

首先要定义实体类来保存动画数据。实体类的嵌套结构如下:
  1. AnimDataSequence
  2. {
  3.     id: int,
  4.     length: float,
  5.     frameRate: float,
  6.     name: string,
  7.     animPathSequences: List<AnimPathSequence>
  8.     [
  9.         {
  10.             path: string,
  11.             localPosition: CurveVector3
  12.             {
  13.                 x: AnimationCurve,
  14.                 y: AnimationCurve,
  15.                 z: AnimationCurve
  16.             },
  17.             localRotation: CurveQuaternion
  18.             {
  19.                 x: AnimationCurve,
  20.                 y: AnimationCurve,
  21.                 z: AnimationCurve,
  22.                 w: AnimationCurve
  23.             }
  24.         },
  25.         ...
  26.     ]
  27. }
复制代码
最外层是AnimDataSequence类,id字段保存动画id;length保存动画时长,单位秒;frameRate保存动画帧率;name保存动画名称。animPathSequences是一个数组,数组的元素类型是AnimPathSequence,保存着模型中单个子物体位置和旋转值的动画曲线,path表示这个子物体相对模型根节点的路径,所以List保存了模型的所有物体位置和旋转值的动画曲线。
图示:

 
 
每个实体类的代码如下:
AnimDataSequence.cs
  1. [MessagePackObject]
  2. public class AnimDataSequence
  3. {
  4.     [Key(0)]
  5.     public int id;
  6.     [Key(1)]
  7.     public float length;
  8.     [Key(2)]
  9.     public float frameRate = 30f;
  10.     [Key(3)]
  11.     public string name;
  12.     [Key(4)]
  13.     public List<AnimPathSequence> animPathSequences = new List<AnimPathSequence>();
  14.     public List<AnimSequenceFrame> GetFrame(float time)
  15.     {
  16.         List<AnimSequenceFrame> sequenceFrames = new List<AnimSequenceFrame>(animPathSequences.Count);
  17.         foreach (AnimPathSequence sequence in animPathSequences)
  18.         {
  19.             AnimSequenceFrame frame = new AnimSequenceFrame();
  20.             frame.path = sequence.path;
  21.             frame.localPosition =
  22.                 new Vector3(
  23.                     sequence.localPosition.x.Evaluate(time),
  24.                     sequence.localPosition.y.Evaluate(time),
  25.                     sequence.localPosition.z.Evaluate(time));
  26.             frame.localRotation =
  27.                 new Quaternion(
  28.                     sequence.localRotation.x.Evaluate(time),
  29.                     sequence.localRotation.y.Evaluate(time),
  30.                     sequence.localRotation.z.Evaluate(time),
  31.                     sequence.localRotation.w.Evaluate(time));
  32.             sequenceFrames.Add(frame);
  33.         }
  34.         return sequenceFrames;
  35.     }
  36. }
复制代码
GetFrame()函数获取给定时间位置的一帧数据,返回一个元素类型是AnimSequenceFrame的数组。
  1. [Serializable]
  2. public class AnimSequenceFrame
  3. {
  4.     public string path;
  5.     public Vector3 localPosition;
  6.     public Quaternion localRotation;
  7. }
复制代码
 
AnimSequenceFrame中,path表示这个子物体相对于模型根节点的路径,localPosition和localRotation表示这个物体在这一帧的位置和旋转值。
AnimPathSequence.cs
  1. [MessagePackObject]
  2. public class AnimPathSequence
  3. {
  4.     [Key(0)]
  5.     public string path;
  6.     [Key(1)]
  7.     public CurveVector3 localPosition = new CurveVector3();
  8.     [Key(2)]
  9.     public CurveQuaternion localRotation = new CurveQuaternion();
  10. }
复制代码
CurveQuaternion.cs
  1. [MessagePackObject]
  2. public class CurveQuaternion
  3. {
  4.     [Key(0)]
  5.     public AnimationCurve x;
  6.     [Key(1)]
  7.     public AnimationCurve y;
  8.     [Key(2)]
  9.     public AnimationCurve z;
  10.     [Key(3)]
  11.     public AnimationCurve w;
  12. }
复制代码
CurveVector3.cs
  1. [MessagePackObject]
  2. public class CurveVector3
  3. {
  4.     [Key(0)]
  5.     public AnimationCurve x;
  6.     [Key(1)]
  7.     public AnimationCurve y;
  8.     [Key(2)]
  9.     public AnimationCurve z;
  10. }
复制代码
AnimationCurve是Unity自有的类型,表示动画曲线,可以被MessagePack序列化和反序列化。其实,自定义的动画数据,最终都是保存在AnimationCurve中的。
生成

用Unity的Playable API获取动画指定时间的骨骼数据,保存到实体类对象中,最后保存成文件。
  1. public void SaveAnimSequence(AnimationClip clip, Animator animator, string filePath, float frameRate = 0)
  2. {
  3.     // 创建PlayableGraph,用于播放动画
  4.     PlayableGraph playableGraph = PlayableGraph.Create("ConvertHumanoidAnimation");
  5.     playableGraph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
  6.     // 把动画片段连接到PlayableGraph中
  7.     AnimationPlayableOutput animPlayableOutput = AnimationPlayableOutput.Create(playableGraph, "AnimationOutput", animator);
  8.     AnimationClipPlayable animClipPlayable = AnimationClipPlayable.Create(playableGraph, clip);
  9.     animPlayableOutput.SetSourcePlayable(animClipPlayable);
  10.     // 创建自定义动画对象,设置必要的参数
  11.     AnimDataSequence animSequence = new AnimDataSequence();
  12.     animSequence.length = clip.length;
  13.     animSequence.name = clip.name;
  14.     // 如果没有传帧率,就用原始动画片段的参数
  15.     animSequence.frameRate = frameRate == 0 ? clip.frameRate : frameRate;
  16.     // 计算每帧的持续时间
  17.     float frameDuration = 1f / animSequence.frameRate;
  18.     Transform root = animator.transform;
  19.     List<Transform> bones = new List<Transform>();
  20.     // 获取人体骨骼对应的物体Transform数组
  21.     for (int i = 0; i < (int)HumanBodyBones.LastBone; i++)
  22.     {
  23.         Transform boneTransform = animator.GetBoneTransform((HumanBodyBones)i);
  24.         if (boneTransform != null)
  25.         {
  26.             bones.Add(boneTransform);
  27.             AnimPathSequence pathSequence = new AnimPathSequence();
  28.             animSequence.animPathSequences.Add(pathSequence);
  29.             // 获取相对路径
  30.             pathSequence.path = Utils.RelativePath(root, boneTransform);
  31.             pathSequence.localPosition.x = new AnimationCurve();
  32.             pathSequence.localPosition.y = new AnimationCurve();
  33.             pathSequence.localPosition.z = new AnimationCurve();
  34.             pathSequence.localRotation.x = new AnimationCurve();
  35.             pathSequence.localRotation.y = new AnimationCurve();
  36.             pathSequence.localRotation.z = new AnimationCurve();
  37.             pathSequence.localRotation.w = new AnimationCurve();
  38.         }
  39.     }
  40.     // 开始获取动画对应的人体骨骼数据
  41.     float time = 0f;
  42.     while (time <= clip.length)
  43.     {
  44.         // 根据时间点定位动画
  45.         animClipPlayable.SetTime(time);
  46.         playableGraph.Evaluate();
  47.         for (int i = 0; i < bones.Count; i++)
  48.         {
  49.             Transform bone = bones[i];
  50.             AnimPathSequence pathSequence = animSequence.animPathSequences[i];
  51.             pathSequence.localPosition.x.AddKey(time, bone.localPosition.x);
  52.             pathSequence.localPosition.y.AddKey(time, bone.localPosition.y);
  53.             pathSequence.localPosition.z.AddKey(time, bone.localPosition.z);
  54.             pathSequence.localRotation.x.AddKey(time, bone.localRotation.x);
  55.             pathSequence.localRotation.y.AddKey(time, bone.localRotation.y);
  56.             pathSequence.localRotation.z.AddKey(time, bone.localRotation.z);
  57.             pathSequence.localRotation.w.AddKey(time, bone.localRotation.w);
  58.         }
  59.         // 下一帧的时间
  60.         time += frameDuration;
  61.     }
  62.     playableGraph.Destroy();
  63.     // 用MessagePack序列化并保存到文件
  64.     byte[] bytes = MessagePackSerializer.Serialize(animSequence);
  65.     File.WriteAllBytes(filePath, bytes);
  66. }
复制代码
PoseSequencePlay是一个继承了MonoBehaviour的类,需要挂载到Unity场景中运行。
加载的核心函数是PrepareData(),通过MessagePackSerializer.Deserialize()反序列化自定义动画数据。
播放的核心函数有两个:Update()和SetPose()。SetPose()函数根据当前时间将人物模型设置为自定义动画序列中的姿势,Update()函数每帧设置动画姿势,并递增时间。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

玛卡巴卡的卡巴卡玛

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表