AnimationClip
前言
最近在做工具设计的时候,需要修改脚本属性,属性变更要有自定义的Mixer过程,做着做着发现除了过渡不一样外,其他设计越来越向Animation靠近了。后来就借用了animation的编辑器,转化成自己的数据。
AnimationClip作用
项目中我们用到的动画一个是动作同学导入的,一个是我们可以通过Window/Animation/Animation窗口通过关键帧的方式创建动画,二者都是一样的。
通过AnimationClip我们可以控制物体的暴露的属性数据,比如位置,材质属性等等。
组成和控制原理
AnimationClip和AniamtionCurve关系
AnimationClip实际上是无数个AniamtionCurve的组合。
如果我们要去修改一个属性我们需要如何定位:
步骤 | 解决方式 |
---|---|
1.修改哪个GameObject | 记录路径,通过GameObject.Find获取 |
2.修改GameObject的哪个Component | 通过组件的Type标记 |
3.修改Component的哪个数据 | 通过propertyName来标记 |
4.修改为什么数据 | 根据AnimationCurve去记录 |
Ok~~AniamtionClip就是记录了以上信息的Asset,运行时通过Animator去修改数据。存储这些信息的数据结构是这样的。
using System;
using UnityEngine;
namespace UnityEditor
{
/// <summary>
/// <para>An AnimationClipCurveData object contains all the information needed to identify a specific curve in an AnimationClip. The curve animates a specific property of a component material attached to a game object animated bone.</para>
/// </summary>
public class AnimationClipCurveData
{
/// <summary>
/// <para>The path of the game object / bone being animated.</para>
/// </summary>
public string path;
/// <summary>
/// <para>The type of the component / material being animated.</para>
/// </summary>
public Type type;
/// <summary>
/// <para>The name of the property being animated.</para>
/// </summary>
public string propertyName;
/// <summary>
/// <para>The actual animation curve.</para>
/// </summary>
public AnimationCurve curve;
internal int classID;
internal int scriptInstanceID;
public AnimationClipCurveData()
{
}
public AnimationClipCurveData(EditorCurveBinding binding)
{
this.path = binding.path;
this.type = binding.type;
this.propertyName = binding.propertyName;
this.curve = null;
this.classID = binding.m_ClassID;
this.scriptInstanceID = binding.m_ScriptInstanceID;
}
}
}
//--通过如下方法从AnimationClip中获取这些数据,中间定义的EditorCurveBinding与AnimationClipCurveData结构
//类似,就不贴出来了
public static AnimationClipCurveData[] GetAllCurves(AnimationClip clip, [DefaultValue("true")] bool includeCurveData)
{
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
AnimationClipCurveData[] array = new AnimationClipCurveData[curveBindings.Length];
for (int i = 0; i < array.Length; i++)
{
array[i] = new AnimationClipCurveData(curveBindings[i]);
if (includeCurveData)
{
array[i].curve = AnimationUtility.GetEditorCurve(clip, curveBindings[i]);
}
}
return array;
}
使用分析
我们制作一个如下的动画信息图片信息:
我们看输出信息:
上面的信息显示的代码参看我的这篇文章
注意路径信息为名称这句话,也就是说,我们将Capsule重新命名后,则不会受到控制了,如果我们在Capsule同级目录下新建一个节点命名为Capsule(将来原来的Capsule重名命),则新的被控制。
多组件的获取问题
如果我们的组件获取时,同一个Gameobject的上面挂了两个相同的组件,AniamtionClip是如果做得区分的,结果是没有区分,选第一个。 纳尼?那要是我一个组件上挂了多个自定义的脚本,岂不是只能有一个有效果?没错~~
如图:我们挂在boxCollider,通过key帧改变boxcollider的size的x。
上图可以看到,我们只有一个BoxCollider可以选择。下面是运行的动画:
可以看出,当我们调换位置后,依旧是第一个运行,其实不用重新,当我们Animator enable=false在设置为true就会重新绑定一次。
Avatar、Humanoid、Motion和AnimationClip的关系
Humanoid的动画导入问题
unity人形动画主要功能是IK,LayerMask,以及动画共享。
由于我们主要讲解AnimationClip,所以上述功能这里不过过分介绍。我们通过问题来引出他们之间的存储关系:当动作做好的动作导入unity后设置为Humanoid的格式后,1.动作同学自定义的bone骨骼的运动轨迹错了~怎么搞?
2.导入之后怎么会有写warning?
针对第一个问题,我们看下改成Humanoid后AnimationClip保存的信息,
首先我们发现路径信息都成空,而属性名为一些固定的名称,类型都为Animator。其实这些名称都是通过Avatar映射的骨骼,而动作同学自定义的bone骨骼在avatar中找不到对应的对应部分,所以转换后没有找到的部分就会被丢弃。但是怎么办呢?比如做的武器骨骼没有,动作就错了啊~~不要慌,我们可以通过这样设置来让AnimationClip保存那些自定义的骨骼。
勾选后,我们再次看看AnimationClip保存的信息
我们发现,自定义的骨骼已经被保存上了,保存方式还是路径,类型,属性名的方式,这样我们自定义的骨骼就有了动画轨迹。
对于导入的warning是什么呢?因为unity的avatar系统识别的CS骨骼结构是固定的,而动作同学做的骨骼结构和unity的不匹配,所以就会以warning的形式输出,当我们看到这些信息时候需要跟动作同学说明结构让他们按照这种骨骼结构重新进行蒙皮。我们截图看下unity识别的骨骼结构是这样的
而max自动生成的cs骨骼是这样的:
Motion动画的设置
unity的动画有一个rootmotion功能,什么意思呢?就是设置角色的位置,通过动画来设置角色的位置,比如跳跃的时候,如果程序在做的时候运动的轨迹很难跟动作的表现完美的配合,如果我们让动作同学做动作的时候直接做了位移那表现岂不是完美!!没有,就是这样。我们可以指定一个节点,也可以使用自定的方式去设定位移,只需要实现OnAnimatorMove即可。
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(Animator))]
public class RootMotionScript : MonoBehaviour {
void OnAnimatorMove()
{
Animator animator = GetComponent<Animator>();
if (animator)
{
Vector3 newPosition = transform.position;
newPosition.z += animator.GetFloat("Runspeed") * Time.deltaTime;
transform.position = newPosition;
}
}
}
Generic和Humanoid的applyRootMotion的区别
rootmotion这块我感觉unity的信息公布就有点模模糊糊,总是和数据匹配不上。
Genreic类型的RootMotion
首先我们设置动画类型为Generic。测试Root Transform Rotation的是如何作用的?我们会发现无论Root节点如何旋转,角色的旋转似乎只有Y轴再动,于是我们如下的方式进行的方式进行对比,将RootBone的角度我们使其旋转至Y轴朝上,然后将记录,通过开启和关闭ApplyRootMotion查看旋转角度大小。
using UnityEngine;
public class RootMotionTest : MonoBehaviour
{
public Transform rootBone;
public Transform A;
public bool UseSelf;
void Update()
{
if (UseSelf)
{
Quaternion temp = Quaternion.FromToRotation(rootBone.up, Vector3.up);
A.rotation = temp * rootBone.rotation;
}
}
float min = float.MaxValue;
float max = float.MinValue;
private void OnGUI()
{
if (GUI.Button(new Rect(10,10,50,50),"重置"))
{
min = float.MaxValue;
max = float.MinValue;
}
if (A.eulerAngles.y> max)
{
max = A.eulerAngles.y;
}
if (A.eulerAngles.y<min)
{
min = A.eulerAngles.y;
}
GUI.Label(new Rect(10,70,500,50),"Min:"+min+" Max:"+max+" 差值:"+(max-min));
}
}
测试结果如下:
由于可见,对于角度的处理跟我们的设想是一直的。同理设置测试Postion (Y XZ),只是将RootNode的位置赋值给我们的绑定的Gameobject(拥有Animator组件的)的位置。
与Humanoid模式下差异1.:Generic模式下的RootTransformPosion(Y)和RootTransformPosion(XZ)是相对自身作坐标系,这点与Humanoid模式下不同
什么意思呢?就比如我们的rootnode是bip001,动作同学做移动的时候bip001的移动方向在自身的x轴(相对角色而说是角色的z轴),则applyrootmotion之后,角色则是沿着自身的x轴移动,而humanoid模式下则是沿着自身前方移动。
比如动作如下(沿着Z轴反方向移动):
我们设置ApplyRootmotion之后(沿着Z轴移动,关于Humanoid模式下如何请往下看):
与Humanoid模式下差异2.:如果我们在Generic模式下勾选了BakeIntoPose相当于关闭ApplyRootMotion选项。
Humanoid类型的RootMotion
关于Humanoid的RootMotion有很多讲解了,我发现我越写越跑题了,赶紧刹车,看这个文章吧
看起来还是挺乱的哈~~整个图示吧
我的疑问
以前一直认为Humanoid类型的动画就是Avatar的动画,ApplyRootMotion只不过是将Avatar的RootNode的RootPosition给到绑定物体上,可是输出的值总是有偏差(avatar.rootPosition和animator的rootPosition)。
经典API-SampleAnimation(GameObject go, float time)
这个API可以在go没有Animator组件的情况下,执行AnimationClip存储的信息。这个API刚做Unity的时候也没注意到,直到做结构设计的时候,发现这个真是省时方便啊,因为Animator和AnimatiorController的初始化和运行效率并不乐观~~而且要自定义动画控制也相当不爽。
有个这个API我们把AnimationClip当做一个Asset去使用,不仅存储功能强大,而且还可以不用做可视化编辑,对美术策划同学还是比较友好的。美滋滋~
结束语
这个说的有点多了。跑题跑的偏了~~哎,就这样吧。