Unity Animation Rigging 程序化行走
Posted 示申○言舌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity Animation Rigging 程序化行走相关的知识,希望对你有一定的参考价值。
Unity Animation Rigging 程序化行走
为什么要程序化行走
制作Unity行走时动画时,如果仅仅使用传统的动画系统,往往无法做到与地形完美的贴合,比如上坡时,脚下踩到石头时,效果就不太好。此时就可以考虑程序化行走啦。
效果视频
Unity Animation Rigging 程序化行走
原理
首先得明白IK,即反向运动学,其实就是根据目标的位置,推算骨骼系统各个关节的运动位置和朝向,以达到尽量靠近目标的目的。比如要拿起桌子上的苹果,人的大脑会瞬间进行计算,控制肩膀、胳膊肘、手腕等关键进行运动,最终使手触达苹果,这个过程就是IK。然后,程序化行走就是基于IK的,这需要逆向思考一下,其实程序化行走,并不是腿把身体支撑了起来,而实际上,我们控制的是身体,然后根据当前的坐标位置,判断跟身体匹配的下一步的落脚点,最后利用IK去反向计算腿部的关节。
最简单的情况:以身体坐标为参考,针对每个腿,赋予一个恰当的偏移量(上图红球位置),然后朝地面发射一根射线,用来检测落脚点,然后就能根据hit.point
知道落脚点的地面高度了,然后将腿部的IK目标点设置为该点,剩下的就是交给Unity引擎了,unity会根据目标点位置,还有IK的各类约束(比如有的关节会约束旋转的角度,比如人的膝盖,永远不能向前打弯),去自动计算腿部的姿态。
然后就简单了:
/* --------------------
* 此代码挂在脚上
* --------------------*/
private void Update()
// lerp表示上一步落脚点到下一步落脚点的行进进度(0-1)
if (lerp < 1)
lerp += walkerBody.StepSpeed * Time.deltaTime;
if (lerp >= 1)
// 如果已经踩到新的落脚点
transform.position = targetPos;
oldPosition = targetPos;
walkerBody.OnStepDone(this);
else
// 正在迈步,通过高度上增加Sin函数,则可以让脚步的位置有弧线变化。
Vector3 pos = Vector3.Lerp(oldPosition, targetPos, lerp);
pos.y += Mathf.Sin(lerp * Mathf.PI) * walkerBody.StepHight;
transform.position = pos;
else
// 不管身体位置如何变化,IK目标点总是设置到落脚点上
transform.position = oldPosition;
// 判断身体的位置移动,是否超过步长,超过就要迈步。
if (Physics.Raycast(walkerBody.transform.position + stepOffset, Vector3.down, out RaycastHit hit,
stepOffset.y * 2, walkerBody.GroundLayerMask))
// 左脚和右脚不能同时迈步,这里判断一下不能同时迈步的其他脚是否正在迈步。
if (CantSimultaneous != null && CantSimultaneous.Any(other => other.IsWalking))
return;
if (Vector3.Distance(hit.point, oldPosition) > walkerBody.StepDistance)
lerp = 0;
targetPos = hit.point;
上面的代码是根据官方例子修改而来,然而有些不太完美的地方在于,**迈步动作太被动了。**往往身体都移动了半个步长了,脚才开始迈步,好像腿被打断了拉在后面一样。
后来,在落脚点检测的时候,又增加了行进方向上的偏移,相当于身体迈步前,就朝身体运动的方向提前一点判断落脚点,然后整个动画就很舒服了。并且把迈步的机制,从“脚”移动到了“身体”。
/* --------------------
* 此代码挂在身体上
* --------------------*/
private void Update()
Vector3 dir = new(Input.GetAxis("Vertical"), 0, -Input.GetAxis("Horizontal"));
if (dir.sqrMagnitude > 0.25f)
Vector3 realdir = transform.TransformDirection(dir).normalized;
Controller.SimpleMove(realdir * MoveSpeed);
if (IsMoving)
Vector3 offset = transform.position - oldPosition;
if (offset.magnitude > StepDistance)
NextStep(realdir);
else
IsMoving = true;
NextStep(realdir);
else
IsMoving = false;
最后再来个2足的机器人效果:
Unity Animation Rigging 程序化行走
继续完善
其实可以继续完善的地方还有很多,比如在传统行走动画的基础上,增加IK约束,预判障碍物形状,使用不同的翻越动画越过障碍等。
Unity之Animation
Unity之Animation
一、Avatar
Unity使用Avatar系统来识别布局中的特定动画模型是否为人形,以及模型的哪部分对应于腿、手臂、头和躯干,Avatar可将动画从一个人形角色映射到另一个角色,允许重定位和反向动力学(IK)。
1.创建Avatar
在导入一个角色动画模型之后,可以在 Import Settings 面板中的 Rig 选项下指定角色动画模型的动画类型,包括 Legacy、Generic 以及 Humanoid 3 种模式,如下图所示。
(1)Legacy
旧版动画类型,Unity4.0版本以前推出的动画系统,一般动画由Mecanim系统导入,但无法使用人形动画的专有功能。
(2)Generic
一般动画类型,支持非人形(怪物)动画,也支持人形动画,但它无法使用Humanoid动画重定向功能。即模型只能使用自带骨骼所制作的动画,不能给其他模型使用
(3)Humanoid
使用Humanoid(人形动画),如上图所示,点击Animation Type选项右侧的下拉列表,选择Humanoid,然后点击Apply,Mecanim动画系统会自动将用户所提供的骨架结构与系统内部自带的简易骨架进行匹配,如果匹配成功,Avatar Definition 下的 Configure 复选框会被选中,同时在 Assets 文件夹中,一个 Avatar 子资源会被添加到模型资源中。
二、Animation视图
1.动画时间轴
动画视图面板右边是当前编辑的时间轴,每个动画属性的关键帧都显示在此时间轴中,时间轴视图有两个模式:关键帧清单模式(Dopesheet)和时间线模式,点击对应按钮进行对用模式切换。
(1)关键帧清单时间轴模式
可通过框选方式选择多个关键点。通过此功能可一次选择和操作多个关键点。
(2)曲线时间轴模式
可通过框选方式选择多个关键点。通过此功能可一次选择和操作多个关键点。
(3)回放和帧导航
2.创建新动画剪辑
可如下图所示建立:
可以在录制的情况下拖动录制动画的对象,也可在对象的属性面板里修改属性来录制动画,也可在动画时间轴直接创建对象某个属性,再在时间轴右侧手动添加关键帧,输入对应的动画数值。
3.支持的可动画属性
Float、Color、Vector2、Vector3、Vector4、Quaternion、Boolean。
4.曲线编辑
将动画视图切换到Curves,点击关键帧可以对其进行拖动,修改其倾斜角度,也可点击右键进行更复杂的属性设置。
5.添加动画事件
如下图所示添加动画事件:
三、Animator Controller
在 Animator 窗口中可创建、查看和修改 Animator Controller 资源。Animator 窗口有两个主要部分:主要网格化布局区域以及左侧 Layers 和 Parameters 面板。带有深灰色网格的主要部分是布局区域。使用此区域可在Animator Controller中创建、排列和连接状态。左侧面板可在 Parameters 视图和 Layers 视图之间切换。Parameters 视图 允许您创建、查看和编辑 Animator Controller 参数。这些参数是您定义的变量,充当状态机的输入。要添加参数,请单击加号图标,然后从弹出菜单中选择参数类型。要删除参数,请在列表中选择参数,然后按 Delete 键(在 macOS 上使用 fn-Delete 来删除所选参数)。
1.动画参数
可以通过动画曲线更新参数的值,然后通过脚本访问参数来控制一些特定的属性值。脚本同样可设置被Mecanim拾取的参数值,例如,脚本可以设置参数来控制混合树。可在Animator窗口的Parameters界面来设置默认参数值,参数值分为四个基本类型:
Integer - 整数
Float - 带小数部分的数字
Bool - true 或 false 值(由复选框表示)
Trigger - 当被过渡使用时,由控制器重置的布尔值参数(以圆形按钮表示)
可使用Animator类中的函数,从脚本为参数赋值:
using UnityEngine;
using System.Collections;
public class SimplePlayer : MonoBehaviour {
Animator animator;
// 使用此函数进行初始化
void Start () {
animator = GetComponent<Animator>();
}
// 每帧调用一次 Update
void Update () {
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
bool fire = Input.GetButtonDown("Fire1");
animator.SetFloat("Forward",v);
animator.SetFloat("Strafe",h);
animator.SetBool("Fire", fire);
}
void OnCollisionEnter(Collision col) {
if (col.gameObject.CompareTag("Enemy"))
{
animator.SetTrigger("Die");
}
}
}
2.状态机
Create Sub-State Machine创建,角色通常具有包含若干阶段的复杂动作。合理的做法是识别单独阶段并将单独状态用于每个阶段,而不是用单个状态来处理整个动作。允许您将一组状态折叠为状态机图中的单个指定项,这些折叠的状态组称为子状态机,若要创建子状态机,可右键单击 Animator Controller 窗口中的空白空间,并从上下文菜单中选择 Create Sub-State Machine。子状态机在编辑器中用细长六边形表示以区别于正常状态。
3.动画层
单击窗口右侧的齿轮可显示该层的设置。
在每一层上,您可以指定遮罩(应用动画的动画模型的一部分)以及混合类型。Override 表示将忽略其他层的信息,而 Additive 表示将在先前层之上添加动画。
可以通过按小部件上方的 + 来添加新层。
Mask 属性用于指定此层上使用的遮罩。例如,如果您只想播放模型上半身的投掷动画,同时让角色也能够行走、奔跑或站立不动,则可以在层上使用一个遮罩,从而在定义上半身部分的位置播放投掷动画:
Layers 侧边栏中显示“M”符号,表示该层已应用遮罩。
动画层同步可以实现不同层中复用同一状态机,例如,如果想要模拟“受伤”行为,并生成“受伤”状态下的行走/奔跑/跳跃动画,而不是“健康”状态下的动画,您可以单击其中一个层上的 Sync 复选框,然后选择要同步的层。随后状态机的结构便会相同,但状态使用的实际动画剪辑不同。
这意味着同步的层根本没有自己的状态机定义,而是同步层状态机的一个实例。在同步层视图中对状态机的布局或结构所做的任何更改(例如,添加/删除状态或过渡)都是针对同步层的源进行的。同步层的唯一独特更改是每个状态内使用的选定动画。
通过 Timing 复选框,Animator 可调整同步层中每个动画所需的时间(由权重决定)。如果取消选中 Timing,则会调整同步层上的动画。该调整会将动画的长度拉伸到与原始层上的一致。如果选中该选项,则动画长度将在两个动画之间平衡(基于权重)。在两种情况下(选中和不选中),Animator 都将调整动画的长度。如果不选中,则原始层将是唯一母版。如果选中,则采用折中方案。
4.Mute 和Solo
Mute会禁用过度
Solo仅播放该过度
可设置多个 Solo 过渡以仅播放启用了 Solo 的过渡。如果一个过渡启用了 Solo,则 Unity 将对其他过渡启用 Mute。如果同时启用 Solo和 Mute,则 Mute将优先执行。
控制器图并非始终反映引擎的内部 Mute 状态。
5.目标匹配
通常在游戏中可能出现以下情况:角色必须以某种方式移动,使得手或脚在某个时间落在某个地方。例如,角色可能需要跳过踏脚石或跳跃并抓住顶梁。想象一下,您想安排一个角色跳到一个平台的情况,并对这种情况已经有名为 Jump Up 的动画剪辑。首先,您需要在动画剪辑中找到角色开始离地的位置,注意在本示例中,此位置是动画剪辑中标准化时间的 14.1% 或 0.141,您还需要在动画剪辑中找到角色即将落地的位置,在本示例中,此位置为 78.0% 或 0.78。
using UnityEngine;
using System;
[RequireComponent(typeof(Animator))]
public class TargetCtrl : MonoBehaviour {
protected Animator animator;
//场景中的平台对象
public Transform jumpTarget = null;
void Start () {
animator = GetComponent<Animator>();
}
void Update () {
if(animator) {
if(Input.GetButton("Fire1"))
animator.MatchTarget(jumpTarget.position, jumpTarget.rotation,
AvatarTarget.LeftFoot, new MatchTargetWeightMask(Vector3.one, 1f),
0.141f, 0.78f);
}
}
}
6.反向动力学
大多数动画是通过将骨架中的关节角度旋转到预定值来生成的。子关节的位置根据父关节的旋转而改变,因此可从父关节包含的各个关节的角度和相对位置来确定关节链的终点。这种构建骨架的方法被称为正向动力学。
然而,从相反视角看待构建关节的任务通常很有用:在空间中选择一个位置后,向后找到一种有效的关节定位方法,使终点落在该位置。如果您希望角色触摸位于用户选定位置的对象或让角色的双脚牢牢扎入不平坦的表面,这种方法可能很有用。此方法称为反向动力学 (IK),可在 Mecanim 中用于_已正确配置的任何人形Avatar骨骼。
要为角色设置 IK,通常要在场景周围放置与角色互动的对象,然后通过脚本(尤其是,诸如 SetIKPositionWeight、 SetIKRotationWeight、 SetIKPosition、 SetIKRotation、 SetLookAtPosition、 bodyPosition、 bodyRotation之类的 Animator 函数)来设置 IK
在上图中,我们展示了一个抓住圆柱形物体的角色。我们如何将其实现?
我们从拥有有效的 Avatar角色开始。
下一步创建 Animator Controller,使其包含该角色的至少一个动画。然后,在 Animator 窗口的 Layers 面板中,单击层的齿轮设置图标,并选中弹出框中的 IK Pass 复选框。
设置默认层的 IK Pass 复选框。
确保已将 Animator Controller 分配给角色的 Animator 组件 (Animator Component):
接下来,为其附加一个实际处理 IK 的脚本,将此脚本命名为 IKControl。此脚本为角色的右手设置 IK 目标,并设置角色的观察位置以使其观看所持物体:
using UnityEngine;
using System;
using System.Collections;
[RequireComponent(typeof(Animator))]
public class IKControl : MonoBehaviour {
protected Animator animator;
public bool ikActive = false;
public Transform rightHandObj = null;
public Transform lookObj = null;
void Start ()
{
animator = GetComponent<Animator>();
}
// 用于计算 IK 的回调
void OnAnimatorIK()
{
if(animator) {
// 如果 IK 处于活动状态,请将位置和旋转直接设置为目标。
if(ikActive) {
// 设置观察目标位置(如果已分配)
if(lookObj != null) {
animator.SetLookAtWeight(1);
animator.SetLookAtPosition(lookObj.position);
}
// 设置右手目标位置和旋转(如果已分配)
if(rightHandObj != null) {
animator.SetIKPositionWeight(AvatarIKGoal.RightHand,1);
animator.SetIKRotationWeight(AvatarIKGoal.RightHand,1);
animator.SetIKPosition(AvatarIKGoal.RightHand,rightHandObj.position);
animator.SetIKRotation(AvatarIKGoal.RightHand,rightHandObj.rotation);
}
}
// 如果 IK 未处于活动状态,请将手和头部的位置和旋转设置回原始位置
else {
animator.SetIKPositionWeight(AvatarIKGoal.RightHand,0);
animator.SetIKRotationWeight(AvatarIKGoal.RightHand,0);
animator.SetLookAtWeight(0);
}
}
}
}
因为我们不打算让角色的手伸到物体内部中心(圆柱的轴心点),所以放置一个空的子对象(在此情况下,命名为“Cylinder Grab Handle”(圆柱抓握把手)),确保手应该放在圆柱上,并将其相应旋转。然后,这只手瞄准此子对象。
一个空的子游戏对象充当 IK 目标,因此这只手将正确放在可见的圆柱对象上
然后,应将此“抓握把手”游戏对象分配为 IKControl 脚本的“Right Hand Obj”属性
在此示例中,我们把观察目标设置为圆柱本身,因此即使把手靠近底部,角色也会直接看向物体的中心。
四、混合树
1.创建混合树
鼠标光标放到网格空白区域右键Create State->From New Blend Tree:
使用动画剪辑和输入参数来设置混合树时,Inspector 窗口通过图形显示动画如何随着参数值变化而进行组合(拖动滑动条时,树根中的箭头会改变其着色以显示其控制的动画剪辑)。
2.混合类型
(1)1D混合:
混合节点的 Inspector中的第一个选项是 Blend Type。此下拉选单用于选择不同混合类型之一;这些类型可以根据一个或两个参数进行混合。1D 混合根据单个参数来混合子运动。
(2)2D混合:
2D Simple Directional:最好在运动表示不同方向(例如“向前走”、“向后退”、“向左走”和“向右走”或者“向上瞄准”、“向下瞄准”、“向左瞄准”和“向右瞄准”)时使用。根据需要可以包括位置 (0, 0) 处的单个运动,例如“空闲”或“瞄准”。在 Simple Directional 类型中,在同一方向上_不_应该有多个运动,例如“向前走”和“向前跑”。
2D Freeform Directional:运动表示不同方向时,也使用此混合类型,但是您可以在同一方向上有多个运动,例如“向前走”和“向前跑”。在 Freeform Directional 类型中,运动集应始终包括位置 (0, 0) 处的单个运动,例如“空闲”。
2D Freeform Cartesian:最好在运动不表示不同方向时使用。凭借 Freeform Cartesian,X 参数和 Y 参数可以表示不同概念,例如角速度和线速度。一个示例是诸如“向前走不转弯”、“向前跑不转弯”、“向前走右转”、“向前跑右转”之类的运动。
(3)直接混合:
使用直接混合树可将 Animator 参数映射到 BlendTree 子项的权重。如果您想要精确控制所混合的各种动画而不是使用一个或两个参数来间接混合它们(1D 和 2D 混合树便是这种情况),这将非常有用。
3.Animator Override Controller
Animator Override Controller 是一种资源,它可让您扩展现有 Animator Controller,从而替换使用的特定动画但保留其原始结构、参数和逻辑。
因此,您可以创建同一个基本状态机的多个变体,但是每个变体使用不同的动画集。例如,您的游戏可能有各种各样的 NPC 类角色生活在世界上,但每种类型(小妖精、恶魔、小精灵等)都有自己独特的行走、空闲、坐定等动画。
通过创建一个包含所有 NPC 类型角色逻辑的“基础”Animator Controller,即可为每种角色类型创建一个重写,并放入各自的动画文件。
五、性能和优化
1.动画系统
(1) 控制器
未设置控制器的 Animator 不会花时间执行处理。
(2)简单动画
播放没有混合的单个动画剪辑会使 Unity 的速度比旧版动画系统更慢。旧系统非常直接,对曲线采样并直接写入变换中。Unity 的当前动画系统具有用于混合的临时缓冲区,并会对采样曲线和其他数据进行额外复制。当前系统布局已针对动画混合和更复杂设置进行优化。
(3)缩放曲线
动画化缩放曲线比动画化移动和旋转曲线的成本更高。为了改善性能,请避免使用缩放动画。
注意:这不适用于常量曲线(具有相同动画剪辑长度值的曲线)。常量曲线经过优化,成本低于比普通曲线。常量曲线的值与默认场景值相同时,常量曲线不会每帧都写入场景。
(4)层
大多数时间,Unity 都在估算动画,并将动画层和动画状态机的开销保持在最低水平。向 Animator 添加另一层(无论同步与否)的成本取决于层播放的动画和混合树。层的权重为零时,Unity 会跳过层更新。
(5)人形动画类型与通用动画类型
以下提示可帮助您选择具体类型:
导入人形动画时,如果不需要 IK(反向动力学)目标或手指动画,请使用 Avatar 遮罩 (class-AvatarMask) 将它们移除。
使用通用类型时,使用根运动比不使用根运动的成本更高。如果动画没有使用根运动,请确保未指定根骨骼。
(6)场景级别优化
可进行许多优化,一些有用的提示如下:
使用哈希而不是字符串来查询 Animator。
实现一个小的 AI 层来控制 Animator。您可以让它为 OnStateChange、OnTransitionBegin 和其他事件提供简单回调。
使用状态标记可轻松地将 AI 状态机与 Unity 状态机匹配。
使用其他曲线来模拟事件。
使用其他曲线来标记动画;例如,与目标匹配一起使用。
2.运行时的优化
始终通过将 Animator 的 Culling Mode 设置为 Based on Renderers 来优化动画,并禁用蒙皮网格渲染器的 Update When Offscreen 属性。这样即可在角色不可见时让 Unity 不必更新动画。
参考文献:
Unity官方手册
以上是关于Unity Animation Rigging 程序化行走的主要内容,如果未能解决你的问题,请参考以下文章
Unity 动画系统(Animation,Animator,Timeline)