动画脚本(旧版)

Unity 的动画系统 (Animation System) 可让您创建漂亮的动画蒙皮角色。动画系统 (Animation System) 支持动画融合、混合、叠加动画、步行周期时间同步、动画层、动画回放各个方面(时间、速度、融合权重)的控制、每个顶点带有 1、2 或 4 块骨骼的网格蒙皮以及基于物理的布娃娃和程序动画。为获得最佳效果,建议您阅读建模优化角色页面上关于在 Unity 中创建具有最佳性能的绑定角色的最佳实践和技术。

制作动画角色涉及到两件事:在世界空间移动角色及相应动画化角色。如果想了解更多关于移动角色的内容,请参阅角色控制器 (Character Controller) 页面。该页面主要介绍动画。实际上,角色是通过 Unity 的脚本界面实现动画化的。

可下载展示预先设定动画角色的示例演示。学习本页面的基本原理之后,另请参阅动画脚本界面

本页面包含以下部分:-

动画融合

在如今的游戏中,动画融合是确保角色拥有平滑动画的一项重要功能。动画器创建分段动画,例如走路循环、跑步循环、空闲动画或射击动画。在游戏的任何时间点,需要能够从空闲动画转换成走路循环,反之亦然。自然,您希望能够平滑转换,避免动作突然跳转。

这正是动画融合的用武之地。在 Unity 中,您可以在相同角色上播放任何数量的动画。所有动画融合或添加到一起来生成最终动画。

我们的第一步是让角色在空闲动画和走路动画之间平滑融合。为了使脚本程序的工作变得更简单,我们先将动画的循环模式 (Wrap Mode) 设为循环(Loop)。然后关闭自动播放 (Play Automatically),确保仅由我们的脚本播放动画。

我们用于动画化角色的第一个脚本非常简单;只需使用一些方式来检测角色移动的速度,然后在走路和空闲动画之间渐变。对于这项简单的测试,我们将使用标准输入轴:-

function Update () {
if (Input.GetAxis("Vertical") > 0.2)
animation.CrossFade ("walk");
else
animation.CrossFade ("idle");
} 

要在工程中使用该脚本:-

  1. 使用资源 (Assets)->创建其他 (Create Other)->Javascript 创建一个 Javascript 文件。
  2. 复制代码并粘贴到文件中
  3. 将脚本拖到角色上(需附加到拥有相同动画的游戏对象 (GameObject)

点击“播放 (Play)” 按钮后,角色将在按住向上箭头时开始在恰当的位置上走动,在释放按键时返回空闲姿势。

动画层

层是非常有用的概念,可让您将动画分组,并划定权重优先级。

Unity 的动画系统可以融合您想要数量的动画片段。可以手动或只需使用 animation.CrossFade()(将自动动画化权重)分配融合权重。

融合权重总是在应用之前被规格化

假如您有一个走路循环和跑步循环,二者的权重都为 1 (100%)。Unity 在生成最终动画时会规格化权重,这意味着,走路循环和跑步动画各占动画权重的 50%。

然而,在播放两个动画时,您通常希望优先考虑哪个动画接收更多权重。当然可以确保手动调整的权重之和为 100%,但是使用层实现该目标就容易得多。

层示例

例如,您拥有一个射击动画、空闲和走路循环。走路和空闲动画根据玩家的速度融合,但当玩家射击时,您希望只显示射击动画。因此,射击动画基本上拥有更高优先级。

做到这点的最简单方法是在射击时保持只播放行走和空闲动画。为此,我们需要确保相比空闲和行走动画,射击动画应在更高的层,这就意味着射击动画将先接收融合权重。行走和空闲动画仅在射击动画未使用 100% 的融合权重时才接收权重。因此,交叉淡入 (CrossFading) 射击动画时,权重将从零开始,在很短时间内变为 100%。在开始时,行走和空闲层仍接收融合权重,但当射击动画完全淡入时,它们完全不接收权重。这正是我们需要的!

function Start () {
// Set all animations to loop
animation.wrapMode = WrapMode.Loop;
// except shooting
animation["shoot"].wrapMode = WrapMode.Once;

// Put idle and walk into lower layers (The default layer is always 0)
// This will do two things
// - Since shoot and idle/walk are in different layers they will not affect
//   each other's playback when calling CrossFade.
// - Since shoot is in a higher layer, the animation will replace idle/walk
//   animations when faded in.
animation["shoot"].layer = 1;

// Stop animations that are already playing
//(In case user forgot to disable play automatically)
animation.Stop();
}

function Update () {
// Based on the key that is pressed,
// play the walk animation or the idle animation
if (Mathf.Abs(Input.GetAxis("Vertical")) > 0.1)
animation.CrossFade("walk");
else
animation.CrossFade("idle");

// Shoot
if (Input.GetButtonDown ("Fire1"))
animation.CrossFade("shoot");
} 

默认情况下,animation.Play()animation.CrossFade() 将停止或淡出在同一层里的动画。这正是我们在绝大多数情况下所需要的。在射击、空闲和跑步示例中,播放空闲和跑步不会影响射击动画,反之亦然(如果您喜欢,可以用 animation.CrossFade 的可选参数更改该行为)。

动画混合

动画混合可通过将一些动画仅应用于身体的一部分,来削减需要为游戏创建的动画数量。这意味着,这些动画可与其他动画以各种组合一起使用。

可以通过调用给定 AnimationState 上的 AddMixingTransform() 来向动画添加动画混合变换。

混合示例

混合示例类似于摆手动画。您可能想让角色在空闲或走路时摆手。没有动画混合,必须针对空闲和走路状态创建单独的摆手动画。然而,如果要向摆手动画添加肩膀变换作为混合变换,那么摆手动画只完全控制从肩关节到手的部位。由于身体其他部位不受摆手动画影响,程序会继续播放空闲或走路动画。因此,当身体其他部位使用空闲或走路动画时,只需要一个动画来展示摆手。

/// Adds a mixing transform using a Transform variable
var shoulder :Transform;
animation["wave_hand"].AddMixingTransform(shoulder);

使用路径的另一个示例。

function Start () {
// Adds a mixing transform using a path instead
var mixTransform :Transform = transform.Find("root/upper_body/left_shoulder");
animation["wave_hand"].AddMixingTransform(mixTransform);
}

叠加动画

叠加动画和动画混合可削减为游戏创建的动画数量,对于创建面部动画而言非常重要。

假设您想要创建一个角色在走路和跑步转弯时,身体向两侧倾斜。这将导致四种组合(走路左倾、走路右倾、跑步左倾、跑步右倾),每种组合都需要制作一个动画。即使在这个简单的案例中,为每种组合创建单独的动画也显然会导致大量的额外工作,每增加一个动作,组合数量会随之剧增。幸运的是,叠加动画和混合避免了因组合简单动作而制作单独动画的需要。

叠加动画示例

叠加动画让您在可能正在播放的任何其他动画上面叠加动画效果。制作叠加动画时,Unity 会计算动画片段的第一帧与当前帧之间的差异,然后将该差异应用到所有其他正在播放的动画上面。

参考前一个示例,您可以制作右倾和左倾动画,Unity 可以将这些动画叠加到走路、空闲或跑步循环上。使用下列代码可实现该操作:-

private var leanLeft :AnimationState;
private var leanRight :AnimationState;

function Start () {
leanLeft = animation["leanLeft"];
leanRight = animation["leanRight"];

// Put the leaning animation in a separate layer
// So that other calls to CrossFade won't affect it.
leanLeft.layer = 10;
leanRight.layer = 10;

// Set the lean animation to be additive
leanLeft.blendMode = AnimationBlendMode.Additive;
leanRight.blendMode = AnimationBlendMode.Additive;

// Set the lean animation ClampForever
// With ClampForever animations will not stop 
// automatically when reaching the end of the clip
leanLeft.wrapMode = WrapMode.ClampForever;
leanRight.wrapMode = WrapMode.ClampForever;

// Enable the animation and fade it in completely
// We don't use animation.Play here because we manually adjust the time
// in the Update function.
// Instead we just enable the animation and set it to full weight
leanRight.enabled = true;
leanLeft.enabled = true;
leanRight.weight = 1.0;
leanLeft.weight = 1.0;

// For testing just play "walk" animation and loop it
animation["walk"].wrapMode = WrapMode.Loop;
animation.Play("walk");
}

// Every frame just set the normalized time
// based on how much lean we want to apply
function Update () {
var lean = Input.GetAxis("Horizontal");
// normalizedTime is 0 at the first frame and 1 at the last frame in the clip
leanLeft.normalizedTime = -lean;
leanRight.normalizedTime = lean;
} 

提示:使用叠加动画 (Additive animations) 时,在每个也用于叠加动画的变换上播放一些其他非叠加动画,这一点至关重要,否则动画会添加到最后一帧的结果上面。这肯定不是您想要的结果。

动画角色程序化

有时,您想按程序动画化角色的骨骼。例如,您可能想让角色的头注视着三维空间的某个特定点,这个最好让跟踪目标点的脚本来处理。幸运的是,Unity 可以轻松完成该任务,因为骨骼只是驱动蒙皮网格的变换 (Transforms)。因此,您可以像控制游戏对象 (GameObject) 的变换 (Transforms) 一样用脚本控制角色骨骼。

需了解的一个重要事项是动画系统会在 Update() 函数之后 LateUpdate() 函数之前更新变换 (Transforms)。因此,如果想要调用 LookAt() 函数,应当在 LateUpdate() 中进行,以确保能够真正重写动画。

用相同的方法创建布娃娃。您只需将刚体 (Rigidbodies)、角色关节 (Character Joints) 和胶囊碰撞器 (Capsule Colliders) 附加到不同的骨骼。这样将从物理上动画化蒙皮角色。

动画回放和取样

本部分说明动画由引擎回放时,如何在 Unity 中取样。

AnimationClips 通常以固定的帧速率制作。例如,可以在 3ds Max 或 Maya 中以每秒 60 帧 (fps) 的帧速率创建动画。导入动画到 Unity 时,这个帧速率将由导入器读取,因此,导入动画的数据取样速率也是 60 fps。

然而,游戏通常以变化的帧速率运行。帧速率在有些计算机上可能比其他计算机上快,即使在同一台计算机上,帧速率因为相机视角在任何给定时刻的复杂性而出现前一秒与后一秒不同。这基本上意味着,我们无法假设游戏运行的精确帧速率。这意味着,即使动画是以 60 fps 的帧速率制作,但其也可能以不同的帧速率回放,例如 56.72 fps 或 83.14 fps,或者几乎可以是任何其他值。

因此,Unity 必须以变化的帧速率取样动画,且无法保证最初设计的帧速率。幸运的是,三维计算机图形不是由离散帧组成,而是由连续曲线组成。这些曲线可以在任何时间点取样,而不仅仅是与原始动画帧对应的时间点。事实上,如果游戏运行的帧速率高于动画制作时的帧速率,动画在游戏中会比动画软件中看上去更加平衡和流畅。

从最实用的角度来说,您可以忽略 Unity 以变化的帧速率取样动画的事实。然而,如果您的游戏逻辑依赖于将变换或属性动画化成非常特殊的配置的动画,则需要知道幕后进行的再取样。例如,如果您的动画在 30 帧内把一个物体从 0 度旋转到 180 度,而您想通过代码知道何时达到一半角度,则您不能在代码中写一个条件语句来检查当前旋转是否达到 90 度。因为 Unity 根据游戏变化的帧速率取样动画,它可能在快要旋转到 90 度时进行取样,或者在刚过 90 度时取样。如果需要在达到动画中的特定点时得到通知,则应当使用 AnimationEvent

还请注意,以变化帧速率取样的结果就是,使用 WrapMode.Once 回放的动画不一定在最后一帧的确切时间被取样。在游戏的某一帧中,可能刚好在动画结束前取样动画,在下一帧中,时间超过了动画的长度,所以游戏被禁用,不能再进一步取样。如果的确需要恰好在动画的最后一帧进行取样,则应当使用 WrapMode.ClampForever,它会确保无限取样最后一帧,直至您自己停止动画。

Page last updated: 2013-07-02