unity Animation 控制播放

2015年01月22日 09:46 0 点赞 0 评论 更新于 2025-11-21 15:03

对于 Unity Animation 控制播放的使用,我并非十分精通。但近期查阅了一些资料,在此简单分享我对该控制播放的总结与思路。

动画脚本 Animation Scripting

Unity 的动画系统支持创建精美的动画蒙皮角色,具备动画融合、混合、添加动画、步调周期时间同步、动画层等功能,还能控制动画回放的各个方面,如时间、速度和混合权重。此外,它支持每个顶点有 1、2、4 个骨骼影响的 mesh,以及基于物理系统的布娃娃系统和程序动画。为获得最佳效果,建议在制作模型和动画绑定前阅读 “Modeling Optimized Characters” 章节。

制作动画角色主要涉及两个方面:角色在世界中的移动及相应动画。若想了解角色移动的更多内容,可参阅 “Character Controller page”。实际上,角色动画是通过 Unity 的脚本界面实现的。

你可以下载 “example demos” 中预设好的动画角色。学完本页基础内容后,还可查看 “animation script interface”。

你可以点击并快速转到以下主题:

  • Animation Blending 动画融合
  • Animation Layers 动画层
  • Animation Mixing 动画混合
  • Additive Animation 附加动画
  • Procedural Animation 程序动画
  • Animation Playback 和 Sampling 动画重放和取样

Animation Blending 动画融合

在当今游戏中,动画融合是确保游戏动画顺畅过渡的基本特性。动画师会创建多种动画,如行走循环、奔跑循环、原地空闲动画或射击动画。在游戏的任何时刻,角色都可能在空闲站立和走动状态之间切换,我们期望这些动作能平滑过渡,而非突然跳转。

Unity 允许同一角色拥有任意数量的动画,这些动画可融合为一个总动画。

下面为角色添加原地空闲站立和走动两个动画,并实现平滑过渡。为简化脚本编写,先将动画的 Wrap Mode 设置为 Loop,再关闭 “Play Automatically”,让脚本控制动画播放。

第一个动画脚本很简单,我们需要探查角色的移动速度,然后在行走和站立动画之间进行淡入淡出。在这个简单测试中,使用预设置的输入轴:

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

让脚本运行的步骤如下:

  1. 创建一个 js 脚本:Assets -> Create Other -> Javascript。
  2. 将代码复制到脚本中。
  3. 将脚本拖拽到角色上(需与动画所在的 GameObject 相同)。

点击 “Play” 按钮,按下上下键时角色走动,松开时角色站立不动。

动画层 Animation Layers

层是一个非常实用的概念,可将动画片段任意分组并区分优先级。

在 Unity 的动画系统中,可以混合任意数量的动画片段。你可以手动分配权重,也可使用 animation.CrossFade() 自动分配。

混合权重在应用前会被规格化。例如,有一个行走循环和一个奔跑循环,权重均为 1(100%),Unity 计算最终动画时会规格化权重,即行走和奔跑循环各占 50% 权重。

多数情况下这没问题,但当两个动画片段同时运行且其中一个权重明显大于另一个时,手动调整权重会比较麻烦,而使用动画层可简化此过程。

制作动画层的范例 Layering Example

假设有射击动画、空闲站立动画和走动循环动画。需要根据玩家的移动速度在行走和站立动作间持续淡入淡出,而玩家射击时,只展示射击动画,即射击动画优先级最高。

实现这一目标的简单方法是,射击时保持行走和空闲动画,同时确保射击动画所在层高于行走和空闲动画层。这意味着射击动画将首先获得混合权重,行走和空闲动画只有在射击动画未使用 100% 混合权重时才会接收权重。当淡入射击动画时,其权重会在短时间内从 0 达到 100%,开始阶段行走和空闲动画层仍可获得混合权重,但射击动画完全切入后,它们将不再获得权重。

以下是实现代码:

function Start () {
// Set all animations to loop 设置所有动画为循环
animation.wrapMode = WrapMode.Loop;
// except shooting 除了射击(不循环)
animation["shoot"].wrapMode = WrapMode.Once;
// 放置 idle 和 walk 进低一级别的 layers  (默认 layer 总是 0)
// This will do two things 这将作两件事情
// - 当 calling CrossFade 时,由于 shoot 和 idle/walk 在不同的 layers 中
//   它们将不会影响互相之间的重放.
// - 由于 shoot 在高一级的 layer, 当 faded in 时 shoot 动画将替换
//   idle/walk 动画 .
animation["shoot"].layer = 1;
// Stop animations that are already playing 停止已经播放的动画
// (万一 user 忘记的话,自动 disable 播放)
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 的可选参数更改此行为)。

动画混合 Animation Mixing

动画混合可减少为游戏制作的动画片断数量,方法是制作只影响身体某部分的动画,这些动画可与其他动画合并使用。

若要为动画添加混合变换,可在给定的 AnimationState 上调用 AddMixingTransform()

混合范例 Mixing Example

假设有一个挥手动画,希望空闲站立或走动的角色都能挥手。若没有动画混合,可能需要制作两个挥手动画,分别用于空闲和行走状态。但如果将挥手动画作为混合变换添加到肩膀变换上,挥手动画将仅控制肩膀,身体其他部位不受影响,下半身会继续播放空闲或行走动画,这样只需一个挥手动画。

以下是示例代码:

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

// Another example using a path.
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);
}

附加动画 Additive Animations

附加动画和动画混合可减少为游戏制作的动画片断数量,对面部动画尤为重要。

假设要创建一个在跑和转身时身体可自动倾斜的角色。已制作好行走和奔跑循环动画,若按照传统方法,还需制作走动左倾、走动右倾、跑左倾、跑右倾四个动画,这会增加大量工作量。而附加动画和混合可显著减少这些工作量。

附加动画范例 Additive Animation Example

附加动画可覆盖其他可能播放的动画效果。制作附加动画时,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 和 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 和 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 和 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 和 1 at the last frame in the clip
leanLeft.normalizedTime = -lean;
leanRight.normalizedTime = lean;
}

提示:使用附加动画时,必须确保在附加动画所使用的每个变换上同时播放一些其他非附加动画,否则动画将添加到最后一帧结果的顶部,这通常不是我们想要的效果。

程序动画角色 Procedurally Animating Characters

有时需要程序化地驱动角色骨骼,例如让角色的头注视 3D 空间中的某个点,这可通过脚本来实现。在 Unity 中,所有骨骼驱动蒙皮网格的变换,因此可以像处理其他 GameObject 一样为角色骨骼编写脚本。

需要注意的是,动画系统在 Update() 函数调用之后、LateUpdate() 函数调用之前更新变换。因此,若要调用 LookAt() 函数,应在 LateUpdate() 中调用,以确保真正覆盖动画。

布娃娃系统也是通过类似方法实现的,可将刚性物体、角色关节和胶囊碰撞体连接到不同骨骼,使物理系统作用于蒙皮角色(在射击类游戏中,当角色快接近地面时,四肢瘫软的效果就是布娃娃系统自动计算的结果)。

动画重放和取样 Animation Playback 和 Sampling

这部分将说明引擎在动画重放时的取样方式。

动画片断制作时通常有特定的速率,例如在 Max 或 Maya 中创建的动画帧速可能为 60 帧每秒(fps)。导入 Unity 后,输入模块会读取该帧速,导入的动画帧速仍为 60 fps。

然而,游戏运行时的速率是变化的,不同电脑的帧速不同,同一台电脑在不同时刻的帧速也可能不同。因此,游戏开始运行时无法确定精确的帧速,这意味着动画重放时的速率可能与制作时不同,可能是 56.72 fps 或 83.14 fps 等。

Unity 会对这些变化的速率进行取样,而不依赖于制作时的速率。由于 3D 电脑图形动画由连续曲线构成,可在任何时间点取样,而非仅适配原始帧的时间点。这意味着如果游戏运行速率高于原始制作速率,动作会更平滑流畅。

对于大多数应用场景,无需干预 Unity 对变化帧速的采样。但如果游戏逻辑依赖的动画变化或道具结构特殊,则需注意。例如,有一个动画是在 30 帧内将物体从 0 旋转到 180 度,若想从代码中得知动画何时完成一半,不能简单检查旋转值是否为 90 度,因为 Unity 会根据游戏变化速率采样,可能在接近 90 度或超过 90 度时采样。若需要通报动画中特殊点的到达,可使用 AnimationEvent

此外,使用 WrapMode.Once 模式重放的动画采样不一定是精确的最后一帧,可能是结束前的某一帧。若需要精确采样最后一帧,可使用 WrapMode.ClampForever,这样动画将持续采样最后一帧,直到手动停止。