Unity 的动画系统可让您创建精美的动画蒙皮角色。动画系统支持动画混合 (blending)、混组 (mixing)、附加动画 (additive animations)、行走周期时间同步、动画层、动画播放所有方面(时间、速度、混合权重)的控制、每个顶点有 1 个、2 个或 4 个骨骼的网格蒙皮以及支持物理性布娃娃和程序化动画。为了获得最佳结果,建议您阅读创建优化的角色模型页面,了解在 Unity 中创建具有最佳性能的骨架角色的最佳做法和技巧。
制作动画角色的过程涉及两个操作:在世界内_移动_角色并让它做相应的_动画_。如果您想了解有关移动角色的更多信息,请查看 Character Controller 页面。此页面重点介绍动画。角色的实际动画是通过 Unity 脚本接口完成的。
您可以下载示例演示,其中介绍了预设动画角色。在此页面上学习了基础知识后,您还可以查看动画脚本接口。
在当今的游戏中,动画混合 (blending) 是确保角色具有流畅动画的基本功能。动画师们创建单独的动画,例如,行走循环、奔跑循环、空闲动画或射击动画。在游戏过程中的任何时间点,您都需要能够从空闲动画转换到行走循环或反之。当然,您希望过渡平滑并避免突然的运动颠簸。
这便是动画混合的用武之地。在 Unity 中,您可以基于同一个角色播放任意数量的动画。所有动画将混合或添加到一起以生成最终动画。
我们的第一步是要让角色在空闲动画和行走动画之间平滑地混合。为了使脚本编写人员的工作更轻松,我们首先将动画的 Wrap Mode 设置为 Loop 。然后,我们将关闭 Play Automatically 以确保我们的脚本是播放动画的唯一地方。
我们的第一个角色动画脚本非常简单;我们只需要一些方法来检测角色的移动速度,然后在行走动画和空闲动画之间淡入淡出。对于此简单测试,我们将使用标准输入轴:
function Update () {
if (Input.GetAxis("Vertical") > 0.2)
animation.CrossFade ("walk");
else
animation.CrossFade ("idle");
}
要在项目中使用此脚本,请执行以下操作:
按下 Play 按钮后,角色将在您按住向上箭头键时开始原地行走,而在松开按键时恢复到空闲姿势。
“层”是一个非常有用的概念,用于对动画进行分组并确定加权优先级。
Unity 的动画系统可在任意数量的动画剪辑之间进行混合。您可以手动分配混合权重,也可直接使用 animation.CrossFade() 自动分配权重。
假设有一个行走循环和一个奔跑循环,两者的权重都是 1 (100%)。当 Unity 生成最终动画时,它将对权重进行归一化,这意味着行走循环将为动画贡献 50%,而奔跑周期也将贡献 50%。
但是,在播放两段动画时,通常希望优先让其中一段动画获得最大权重。当然,可通过手动方式确保权重总和达到 100%,但这种情况下使用层会更容易。
例如,您可能有一段射击动画、一个空闲循环和一个行走循环。行走动画和空闲动画将根据玩家的速度进行混合,但是当玩家射击时,您只想显示射击动画。因此,射击动画基本上具有更高的优先级。
最简单的做法是在射击时继续播放行走动画和空闲动画。要做到这一点,我们需要确保射击动画比空闲动画和行走动画位于更高的层,这意味着射击动画将首先接收混合权重。仅当射击动画未使用全部 100% 的混合权重时,行走动画和空闲动画才能接收权重。因此,当淡入淡出 (CrossFading) 射击动画时,权重将从零开始,并在短时间内变为 100%。在开始时,行走动画层和空闲动画层仍会接收混合权重,但当射击动画完全淡入时,这两个动画层将不再接收任何权重。这正是我们所需要的效果!
function Start () {
// 将所有动画设置为循环模式
animation.wrapMode = WrapMode.Loop;
// 射击除外
animation["shoot"].wrapMode = WrapMode.Once;
// 将空闲动画和行走动画置于较低的层(默认层始终为 0)
// 因此将产生两个效果
// - 由于射击动画和空闲/行走动画位于不同的层,因此在调用
// CrossFade 时,它们不会影响彼此的播放。
// - 由于射击动画位于较高的层,因此射击动画将在淡入后
// 替换空闲/行走动画。
animation["shoot"].layer = 1;
// 停止正在播放的动画
//(预防用户忘记自动禁用播放)
animation.Stop();
}
function Update () {
// 根据按下的键,
// 播放行走动画或空闲动画
if (Mathf.Abs(Input.GetAxis("Vertical")) > 0.1)
animation.CrossFade("walk");
else
animation.CrossFade("idle");
// 射击
if (Input.GetButtonDown ("Fire1"))
animation.CrossFade("shoot");
}
默认情况下, animation.Play() 和 animation.CrossFade() 将停止或淡出位于同一层的动画。在大多数情况下,这正是我们需要的效果。在我们的射击、空闲、奔跑示例中,播放空闲动画和奔跑动画不会影响射击动画,反之亦然(如果需要,可使用 animation.CrossFade 的可选参数更改此行为)。
通过动画混组 (mixing),可将一些动画仅应用于身体的某个部位,从而减少需要为游戏创建的动画数量。这意味着此类动画可通过各种组合形式与其他动画一起使用。
通过在给定的 AnimationState 上调用 AddMixingTransform() ,可将动画混组变换添加到动画。
一个混组的示例可能是挥手之类的动画。您可能希望角色在空闲或行走时挥手。如果不使用动画混组,必须为空闲状态和行走状态创建单独的挥手动画。但是,如果将肩部变换以混组变换的形式添加到挥手动画,则挥手动画将具有从肩关节到手的完全控制权限。由于身体的其余部位不会受到挥手的影响,因此将继续播放空闲动画或行走动画。所以,只需用一段动画表现挥手,同时让身体的其余部位继续使用空闲动画或行走动画。
/// 使用 Transform 变量添加混组变换
var shoulder : Transform;
animation["wave_hand"].AddMixingTransform(shoulder);
另一个使用路径的示例。
function Start () {
// 改用路径添加混组变换
var mixTransform : Transform = transform.Find("root/upper_body/left_shoulder");
animation["wave_hand"].AddMixingTransform(mixTransform);
}
通过附加动画和动画混组,可将一些动画仅应用于身体的某个部位,从而减少需要为游戏创建的动画数量。
假设您希望创建一个在行走和奔跑时向侧面倾斜的角色。这导致了四种组合:walk-lean-left(向左倾斜行走)、walk-lean-right(向右倾斜行走)、run-lean-left(向左倾斜奔跑)和 run-lean-right(向右倾斜奔跑),每个组合都需要一段动画。即使在这种简单的情况下,为每个组合创建单独的动画也会导致很多额外的工作,但是每增加一个动作,组合的数量就会大大增加。幸好,附加动画和动画混组避免了为简单动作组合生成单独动画的必要。
使用附加动画可将一个动画的效果叠加在可能正在播放的任何其他动画之上。生成附加动画时,Unity 将计算动画剪辑中第一帧与当前帧之间的差异。然后,它将在所有其他播放的动画上应用此差异。
参考前面的示例,您可以制作使角色左右倾斜的动画,然后 Unity 可将它们叠加在行走、空闲或奔跑周期上。通过以下代码可实现这一点:
private var leanLeft : AnimationState;
private var leanRight : AnimationState;
function Start () {
leanLeft = animation["leanLeft"];
leanRight = animation["leanRight"];
// 将倾斜动画放在单独一层中,
// 因此,对 CrossFade 的其他调用不会影响它。
leanLeft.layer = 10;
leanRight.layer = 10;
// 将倾斜动画设置为附加动画
leanLeft.blendMode = AnimationBlendMode.Additive;
leanRight.blendMode = AnimationBlendMode.Additive;
// 设置倾斜动画 ClampForever
// 通过设置 ClampForever,动画
// 在到达剪辑结尾时不会自动停止
leanLeft.wrapMode = WrapMode.ClampForever;
leanRight.wrapMode = WrapMode.ClampForever;
// 启用动画并使其完全淡入
// 我们在此处不使用 animation.Play,因为我们将在 Update 函数中
// 手动调整时间。
// 相反,我们仅启用动画并将其设置为全部权重
leanRight.enabled = true;
leanLeft.enabled = true;
leanRight.weight = 1.0;
leanLeft.weight = 1.0;
// 为进行测试,仅播放 "walk" 动画并对其进行循环
animation["walk"].wrapMode = WrapMode.Loop;
animation.Play("walk");
}
// 每一帧都根据我们需要应用的
// 倾斜程度来设置标准化时间
function Update () {
var lean = Input.GetAxis("Horizontal");
// 在剪辑的第一帧,normalizedTime 为 0,而最后一帧为 1
leanLeft.normalizedTime = -lean;
leanRight.normalizedTime = lean;
}
提示:使用附加动画时,还必须在附加动画中使用的每个变换上播放其他非附加动画,否则动画将添加到最后一帧的结果上。这肯定不是您想要的效果。
有时,您希望以程序化方法对角色的骨骼进行动画化。例如,可能希望角色的头部注视 3D 空间中的特定点,最好由跟踪目标点的脚本进行处理。幸运的是,Unity 很容易实现这一点,因为骨骼就是驱动蒙皮网格的变换组件。所以,可以像游戏对象的变换组件一样,通过脚本来控制角色的骨骼。
必须注意,动画系统在 Update() 函数之后和 LateUpdate() 函数之前更新变换组件。因此,如果您需要执行 LookAt() 函数,应在 LateUpdate() 中执行该函数,从而确保真正覆盖动画。
布娃娃将以同样的方式创建。只需将刚体 (Rigidbodies)、角色关节 (Character Joints) 和胶囊碰撞体 (Capsule Colliders) 连接到不同的骨骼。这样就会实际动画化您的蒙皮角色。
本部分介绍在引擎播放 Unity 中的动画时如何对这些动画采样。
动画剪辑 (AnimationClips) 通常是以固定帧率创作的。例如,可在 Autodesk® 3ds Max® 或 Autodesk® Maya® 中创建帧率为 60 帧/秒 (fps) 的动画。在 Unity 中导入动画时,导入器将读取此帧率,因此所导入动画的数据也以 60 fps 的帧率采样。
但是,游戏通常以可变帧率运行。一些计算机上的帧率可能高于其他计算机上的帧率,而根据摄像机在任何给定时刻观看的视图复杂性,每一秒的帧率也可能不同。基本上,这意味着我们不能对游戏运行的确切帧率做出任何假设。这也就是说,即使动画是以 60 fps 创作的,也可能以不同的帧率播放动画,例如 56.72 fps 或 83.14 fps 的帧率,甚至几乎任何其他帧率值。
因此,Unity 必须以可变帧率对动画进行采样,并且不能保证最初设计的帧率。幸好,3D 计算机图形动画不是由离散帧组成的,而是由连续曲线组成。在任何时间点都可以对这些曲线进行采样,而不是局限于与原始动画中的帧相对应的时间点。事实上,如果游戏的运行帧率高于创作动画时的设计帧率,那么动画在实际游戏中看起来比在动画软件中看起来更流畅、更稳定。
在大多数实际情况下,您可以忽略 Unity 以可变帧率采样动画这件事。但是,如果游戏玩法逻辑依赖于动画的变换组件或属性到非常具体的配置,那么您需要知道重新采样过程在后台进行。例如,如果有一段动画在 30 帧内将对象从 0 度旋转到 180 度,您希望从代码中知道何时到达中间位置,此情况下不应在代码中使用条件语句(通过条件语句检查当前旋转是否为 90 度)来实现此目的。因为 Unity 根据游戏的可变帧率对动画进行采样,所以可以在旋转刚好低于 90 度时对其进行采样,而在下次到达 90 度后再进行采样。如果您需要在到达动画中的特定点时收到通知,应改用 AnimationEvent。
另外请注意,由于可变帧率采样的原因,使用 WrapMode.Once 模式播放的动画可能无法在最后一帧的确切时间进行采样。在游戏的一帧中,正好可在动画结束之前进行动画采样,而在下一帧中,时间可能会超过动画的长度,因此会禁用这一帧动画,不对其进一步采样。如果您必须要对动画的最后一帧采样,应使用 WrapMode.ClampForever 模式,这种模式将无限期对最后一帧进行采样,直到您主动停止动画为止。