最近开始跟着SIKI学院系统学习Unity,这篇文章就是Unity中的动画系统和Timeline的笔记
以前我都是在动画中一步一步来做动画,从来不知道还有这个录制功能,厉害了
我们点击Animation编辑版中左上角的红点即可开始录制,录制状态中我们调整物体就可以自动创建帧。
另外我们还可以在Curves中编辑动画曲线,点击动画曲线的关键点我们还可以设置动画曲线变化的均匀程度
不同的建模软件会导出不同的模型,但是要记住.fbx是最支持Unity的格式。
一般我们的模型除了模型以外还会有材质和贴图,这两个东西就是模型的皮肤,非常重要。
导出的一般有两个方式:第一种是把动画和模型放在一起,第二种是将每一个动画导出成一个单独的文件(例如:[email protected] 这种格式)
首先要确保场景模型的材质球可以编辑,如果不能编辑怎么办?不要急,打开文件中的模型(是文件中,不是场景中!)然后在Inspector面板中的Materials下这样设置
use external materials(Legacy):使用外部材料(遗产)
use embeddedmaterials :使用嵌入材料
选好使用外部材料后,我们双击模型,找到他的网格渲染组件(Mesh Renderer)看看是否缺少材质球,如果不缺材质,那就是贴图的问题,在材质球编辑处添加适当的贴图即可。
指定好贴图后,我们可以改一下shader为标准,可以看得更真实,再加一下法线贴图也好。
指定好这些属性后及时你将场景中的模型删除了,文件中也已经指定好贴图了。
Unity中导入的模型主要是由3DMAX、Maya等建模软件制作的,后缀为.fbx的文件。
点击文件后我们会发现关于动画导入的一些设置
Rig面板下:
我们的Animation type主要有如下选项:
Legacy:已经启用的导入方式(无法使用状态机)。
Humanoid:只能人形动画使用。
Generic:人形非人形都可以使用。
注意Humanoid和Generic使用状态动画机播放,不能使用Animation播放
后两者的区别:当有两个骨骼结构相同的模型时,其中一个有动画而另一个没有。就可以把两者都设置为Humanoid,没有动画的模型的Avatar Definition设为Copy From Other Avatar。赋值有动画模型的Avatar,就可以使用动画了。
我们来分别介绍一下Generic和Humanoid
Generic(通用的)
它是新的动画系统,支持非人形(怪物)动画,也支持人形动画,应用它会生成一套骨骼(Avatar)。
但它无法使用Humanoid动画重定向功能。即美术给一个模型做的动画,这些做的动画只能给这个模型使用,不能给其他模型使用。而Humanoid的动画重定向功能,可以实现一个模型的动画,给其他模型使用。
Create From This Model:使用这个Model创建骨架
Copy From Other Avatar:使用其他骨骼(前提是和另一个模型的骨骼相同)
选择模型根节点
Humanoid(人形的)
Humanoid最牛X的地方就是支持动画重定向
选择Generic或者Humanoid后,系统都自动为Perfab模型生成Avatar。这个Avatar可以提供给其他同Humanoid的骨骼用来共用Avator(动画重定向)
在我们将Avatar Definition选择CreateFromThisModel后,点击Apply后可以点击Configure来配置骨骼映射,会跳转到大概这样的场景
这里我们可以调整骨骼映射。
这里的白色,绿色骨骼都是我们的素材创造的骨骼,而Mapping(映射)里面为Unity自带骨骼,我们创建的骨骼要映射到Unity自带的骨骼上。
绿色、白色都是Unity内置骨骼,会跟人物的骨骼节点映射,白色为未映射正确的。
实线为必须映射骨骼,虚线为非必须的。
更改映射方法:点击Model里的白色骨骼,在Hierarchy里选择正确的骨骼节点,拖到它的Mapping(映射)对话框中
动画重定向:
Unity引擎中动画重定向的实现不是一个直观的方法,而是封装在了Humanoid类型的动画系统里面,也就是必须是人形的骨架、使用Humanoid才可以使用它。Unity没有像前文描述的基本原理那样去定义两套骨架之间的映射关系,而是自己在内部定义一套骨架模板,所有的Avatar骨骼都必须映射到这套模板上才可以由同一个Animator来驱动产生Retargeting之后的动画效果。
比如我们给A创建了骨骼,并与Unity模板映射成功,给A的状态机配置了动画;现在我们来了个B人物,我们将它的骨骼配置一下与Unity骨骼模板基本绑定后,我们就可以让B去做出A动画状态机的动画(但是骨骼要填入各自的骨骼)
在物体的Animation中可以进行动画的切割
按照提示直接切割就好。
下面的Loop Match 是用来检测动画片段的第一帧和最后一帧是否重合,方便我们做循环动画。
Animator.StringToHash("字符串");
它是Animator的静态方法,用Animator直接调用即可。
Animator.GetCurrentAnimatorStateInfo(a)
确定当前第a层动画的AnimatorStateInfo 对象(有关当前或下一个状态的动画器信息)。
属性:
完整路径可以结合上面的StringToHash这样利用:
表示当前外层动画状态是否为外层的idle
补充:得到当前动画状态机播放的动画的方法:
//GetCurrentAnimatorStateInfo 获取动画控制器中指定层的状态信息
AnimatorStateInfo info = _anim.GetCurrentAnimatorStateInfo(0);
//现在播放的动画是第0层的normal
if ( Animator.StringToHash("normal").Equals(info.shortNameHash))
{
}
bool IsInTransition(int layerIndex);
检测当前是否在过渡
layerIndex The layer’s index.
该层的索引。
0表示Base Layer
我们很多时候经常用一个变量来作为三个动画的转换条件,比如速度等于0则站着,大于0则走,再大点就跑起来,这样我们要做三个动画在后期是很不方便管理的,我们可以直接使用混合树来混合多个动画。
混合树也是一个state,但是可以混合多个动画,用一个值来做划分
我们在状态机中右击选择
生成新混合树后,双击就可以进入混合树编辑中,在混合树编辑中双击空白地方就可以退出混合树编辑。
如我设置的,就是根据一个参数speed,当它大于0,则向walk转换,大于0.5则开始向Run装换。
Automate Thresholds:自动设置阈值(我们可以取消勾选来亲自设置范围限制)
Adjust Time Scale:调整时间比例
这样,我们就做好了一个一维混合树,一个state就控制了站·走·跑的转换。
二维混合树就是有了两个参数可以决定动画,我们常用的操作就是在站·走·跑的基础上加上
PosY(即speedz参数)可以控制站·走·跑的播放,而PosX(即speedRotate参数)可以控制旋转动画的播放
我们可以Compute Position来自动计算阈值(根据动画分析阈值)
x的大小来自于动画的角速度(角度为单位)
在脚本中控制两个参数的设置即可。
private int speedRotateID = Animator.StringToHash("speedRotate");
private int speedZ = Animator.StringToHash("speedz");
void Update()
{
animator.SetFloat(speedZ, Input.GetAxis("Vertical")*4.1f);
animator.SetFloat(speedRotateID, Input.GetAxis("Horizontal") * 121f);
}
关于2D混合树有很多不同的类型,我们要选择恰当的类型
官网奉上
我来简单翻译一下:
2D Simple Directional(简单方向): 当你的动作代表不同的方向时,例如「向前走」、「向后走」、「向左走」、「向右走」、「向上走」、「向下看」、「向左看」及「向右看」 ,你就可以使用这些动作。 可以选择包括位置(0,0)的单个运动,如“空转”或“瞄准直线”。 在简单方向类型中,不应该有同一方向的多个动作,例如“向前走”和“向前跑”。
2D Freeform Directional(自由方向): 当你的动作代表不同的方向时,也可以使用这种混合类型,但是你可以在同一个方向上有多个动作,例如“向前走”和“向前跑”。 在自由方向类型的运动集应始终包括一个单一的运动在位置(0,0) ,如“空闲”。
2D Freeform Cartesian(自由的笛卡尔): 当你的动作不代表不同的方向时最好使用。 使用自由笛卡尔坐标系,你的 x 参数和 y 参数可以代表不同的概念,比如角速度和线速度。 例如“向前走不转弯”、“向前跑不转弯”、“向前走向右转弯”、“向前跑向右转弯”等动作。
Direct: 这种类型的混合树允许用户直接控制每个节点的权重。 有用的面部形状或随机闲置混合。
相机跟随的代码,自行参考
public class CameraControl : MonoBehaviour
{
private Transform player;
private Vector3 offset;
private float smoothing = 3;
// Start is called before the first frame update
void Start()
{
player = GameObject.FindGameObjectWithTag("Player").transform;
offset = transform.position - player.position;
}
// Update is called once per frame
void Update()
{
//player.TransformDirection(offset)将offset作为player的局部坐标再转换成世界坐标
//这样子,player的转向会对offset产生影响
Vector3 targetPosition = player.position+player.TransformDirection(offset);
transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * smoothing);
transform.LookAt(player.position);
}
}
我们有的动作会需要和场景中的东西互动(比如爬墙,跳跃),这个时候,为了保证真实性,我们需要就匹配目标
示例代码:
void Update()
{
animator.SetFloat(speedZ, Input.GetAxis("Vertical")*4.1f);
animator.SetFloat(speedRotateID, Input.GetAxis("Horizontal") * 121f);
bool isVault = false;
if (animator.GetFloat(speedZ) > 3&&animator.GetCurrentAnimatorStateInfo(0).IsName("locomotion")) {
RaycastHit hit;
//距离我们有墙,则跳跃
if (Physics.Raycast(transform.position + Vector3.up * 0.3f, transform.forward, out hit, 3.1f)) {
if (hit.collider.tag == "Obstacle") {
if (hit.distance > 2.9)
{
Vector3 point = hit.point;
point.y = hit.collider.transform.position.y + hit.collider.bounds.size.y+0.1f;
matchTarget = point;
isVault = true;
}
}
}
}
animator.SetBool(vaultID, isVault);
if (animator.GetCurrentAnimatorStateInfo(0).IsName("Vault")&&animator.IsInTransition(0)==false) {
animator.MatchTarget(matchTarget,Quaternion.identity,AvatarTarget.LeftHand,
new MatchTargetWeightMask(Vector3.one,0),0.25f,0.45f);
}
}
我们可以绑定某个状态机内的变量跟随动画的进行而改变。
在我们的资源中的animation里会有这么一栏
其中的Curves就是曲线的意思,我们给Curves取个名字,在相应动画状态机中的同名变量就会被绑定。
而这个曲线的波动就是随着动画的进行这个变量的取值。
动过下面的预览模式配合给曲线加关键帧就可以调整曲线。
动画状态机可以进行分层来决定不同的动作
建立新层后,点击层右边的设置就可以来配置该层
我们可以在Asset资源区新建一个骨骼遮罩——Avatar Mask,然后就是如下的画面
绿色就是可控制,红色就是不可控制,将骨骼遮罩赋值给动画层后,即可实现该层动画只对某部位的操作生效。
比如可以让手去拿起木头。
而实际操作中经常手和木头的位置不匹配,这个时候我们就可以借助反向动力学来解决这个问题。
IK是Inverse Kinematic的缩写,也就是反向动力学。
是根据骨骼的终节点来推算其他父节点的位置的一种方法。
比如通过手的位置推算手腕、胳膊肘的骨骼的位置。”
我们来看示例:
先运行该动画层IK Pass,并且骨骼遮罩允许需要的部分IK
然后我们可以创造空物体来把持位置,比如创造两个左手右手的空物体,移动到这个木头左右
然后就是代码控制
//每一帧都会调用
//每个勾选IK pass的层都会调用,可以通过参数判断哪一层调用了OnAnimatorIK
private void OnAnimatorIK(int layerIndex)
{
if (layerIndex == 1) {
//isHoldLogID为真则执行双手拿木头的动画
int weight = animator.GetBool(isHoldLogID) ? 1 : 0;
//左手位置以及旋转跟随lefthand的位置和旋转,weight控制权值
animator.SetIKPosition(AvatarIKGoal.LeftHand,lefthand.position);
animator.SetIKRotation(AvatarIKGoal.LeftHand, lefthand.rotation);
animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, weight);
animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, weight);
//右手位置以及旋转跟随righthand的位置和旋转,weight控制权值
animator.SetIKPosition(AvatarIKGoal.RightHand, righthand.position);
animator.SetIKRotation(AvatarIKGoal.RightHand, righthand.rotation);
animator.SetIKPositionWeight(AvatarIKGoal.RightHand, weight);
animator.SetIKRotationWeight(AvatarIKGoal.RightHand, weight);
}
}
这样一来,我们就可以自由固定双手的位置了
这一个技术相对于其他动画系统,最大的区别就是,TimeLine针对多个游戏物体做出的一系列动画,主要用于过场动画的制作,实现电影级的那种分镜效果
这一技术很牛逼哦!
我们可以通过菜单栏中的 Window-》Sequencing-》Timeline,打开 timeline 编辑器
然后就可以选择某游戏物体来创建一个Timeline。
在一个timeline中,我们可以通过拖进来物体来创建轨道,然后就可以随心编辑轨道了。
更多关于timeline
更多关于timeline
利用Timeline可以轻松制作很多炫酷的分镜头
我们还可以实现自定义扩展
在Asset资源区中找个合适的目录创建一个适用于Timeline的脚本
然后创建第二个脚本——Playable Asset ,示例代码如下:
[System.Serializable] //序列化,让外边能显示
public class TestAssert : PlayableAsset
{
[Header("对话框")]
public ExposedReference dialog;
[Multiline(3)]
public string dialogStr;
private Test test = new Test();
// Factory method that generates a playable based on this asset
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
{
test.dialog = dialog;
test.dialogStr = dialogStr;
Playable playable = ScriptPlayable.Create(graph,test);
return playable;
}
}
其中:ExposedReference:是一个泛型类型,可用于创建对场景对象的引用,以及通过使用上下文对象在运行时解析它们的实际值。ScriptableObject 或 PlayableAsset 等资源可使用它来创建对场景对象的引用。如果不使用ExposeReference,只是Text等类型,无法从那个Unity面板得到引用。
然后创建第一个脚本——Playable Behavior,示例代码如下:
public class Test : PlayableBehaviour
{
public ExposedReference dialog;
private Text _dialog;
public string dialogStr;
// Called when the owning graph starts playing
public override void OnGraphStart(Playable playable)
{
//Resole:根据 ExposedPropertyResolver 上下文对象,通过解析此引用的值来获取该值。
_dialog = dialog.Resolve(playable.GetGraph().GetResolver());
}
// Called when the state of the playable is set to Play
public override void OnBehaviourPlay(Playable playable, FrameData info)
{
//文本框显示文字
_dialog.gameObject.SetActive(true);
_dialog.text = dialogStr;
}
// Called when the state of the playable is set to Paused
public override void OnBehaviourPause(Playable playable, FrameData info)
{
if (_dialog)
{
//播放暂停时关闭文字的显示
_dialog.gameObject.SetActive(false);
}
}
}
设置好之后,我们将Playable Asset拖拽到timeline中,然后设置时间段上的参数,即可实现对应效果。
示例:
Playable Asset是沟通Playable Behavior和Unity timeline编辑界面的桥梁
Playable Behavior才是实现自定义功能的脚本
另外,在Playable Behavior中的几个方法的调用时机分别是:
欢迎访问我的博客:is-hash.com
商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢