# 对 Unity 游戏动画基础知识的调研

0. 写在前面 #

动画在游戏开发中很重要, 缺乏动画的支持, 则难以建立完整的游戏和真正意义上的交互式体验. 如果游戏中的元素处于静止状态, 将缺乏生命力.

从最基本的层面上看, 动画表示为两个特定且独立的特征之间的关系, 即变化和时间. 从技术讲, 动画定义了一段时间内的变化, 通过动画以及一段时间内的特征变化, 可以有效地表达情绪,氛围,场景,以及理念等特征.

对动画基础知识的掌握需要理解的内容有 : 动画的概念(帧,关键帧) | 动画的类型(刚体动画,骨骼动画,精灵动画,物理动画,变形动画,视频动画,粒子动画,可编程动画) 以及编写代码实现动画.

1. 动画的概念 #

1.1 帧

帧 : 将动画表达中的时间划分为变化过程中独立且离散的单位.

作为离散事物, 人们可以实时感受并产生更为明确的场景体验, 此类事物可发生在某一时刻或者位于某一帧内. 动画帧提供了属性变化的机会, 如游戏角色的移动行为或颜色的变化等. 在视频游戏中, 每秒必须维持或包含特定数量的帧. 每秒的帧数量称为 FPS 每秒帧数, 通常用作游戏性能的测算标准.

1.2 关键帧(或称重要帧)

如果每秒内传递了多个帧, 但并不是每一个帧都需要改变, 那么动画设计师可以在动画序列中定义关键帧, 随后计算机可以自动生成中间帧. 由于定义了动画中的关键时刻, 因此此类状态被称为关键帧.

在关键帧的基础上, Unity 自动生成中间帧, 即补间动画, 例如对房门定义两个关键状态 : 开启状态和闭合状态, 定义动画序列的起始点和结束点, 对应着起始帧和结束帧, 在这两帧之间的中间帧可以平滑的旋转门, 中间帧的数学处理过程称作插值计算.

2. 动画的类型 #

从技术的视角区分视频游戏中不同的动画类型. 不同的动画类型的实现方式也有所不同. 由于本质上的差别, 需采用不同的处理和工作方式, 包括特定的工作流以及所涉猎的技术.

2.1 刚体动画

刚体动画用于创建预制动画序列, 将对象视为整体实体, 与具有少量可运动部分的物体不同. 在刚体动画中, 虽然对象在关键帧之间有所变化, 但依然是作为独立的整体进行操作, 动画的过程中要始终保持物体的物理结构, 仅仅可以是位置,旋转和缩放产生变化.

因此, 刚体动画中, 关键帧之间的改变仅仅作用于整体物体及其高层属性中, 没有渗透到其子属性或内部组成中, 对象的本质内部形式一定保持不变.

这一类动画在 U3D 或 3D 动画软件中直接加以定义, 并于随后通过网格文件导入至 U3D 中.

2.2 骨骼动画

骨骼动画也称为搭建动画, 这一类动画并不改变对象的位置,旋转和缩放内容, 但是会在帧间对其内部组成进行移动或变形. 例如手臂,腿部,头部转动,嘴部运动以及舞动的树叶等等. 通常情况下, 各个动画需要在 3D 建模软件中作为完整的动画序列予以创建, 随后通过网格文件导入至 U3D 中.

2.3 精灵动画

有时候需要使用包含动画纹理,标准的四边形和平面网格, 比如 2D 动画,图形用户界面以及各种 3D 特效(Eg.水面波纹). 这个时候, 对象不需要像刚体动画一样产生运动, 也不需要内部结构产生任何变化, 因为纹理自身就处于动画状态. 这样的动画类型就叫做精灵动画.

精灵动画利用图像或帧序列以特定的帧速率播放, 进而呈现出一致的动画外观. 例如 2D 横向卷轴游戏中某一角色的行走动作循环过程.

2.4 物理动画

在玩家或其他各种场景因素无法预知的情况下, 动画需要呈现真实效果并采用动态方式与场景反馈, 比如, 采用了 U3D 的物理系统就可以有因重力作用下坠落到地面的物体. Component>Physics

2.5 变形动画

有些情况下, 刚体动画,骨骼动画,精灵动画或物理动画都无法满足要求, 例如用户可能需要呈现不同物体之间的变形效果(从人变成狼人,从青蛙变成王子,从巧克力棒变成一座城堡等等).

变形动画可以做形状混合, 将网格状态混合或者通过平滑的方式从一帧合并至另一帧的不同状态中去. 实际上, 该方法依赖于动画关键帧之间网格的对应顶点, 以及中间帧不同状态间的混合效果.

计算量大, 影响性能, 但是产生精美逼真的外观.

2.6 视频动画

U3D 可以播放视频文件, 接受 OGV(Ogg Theora) 格式的视频, 并将其作为资源文件. 将音视频作为场景网格对象上的动画纹理进行回放.

功能强大, 影响性能.

2.7 粒子动画

不同于精灵对象或网格对象有些物体无法被清晰地定义形状, 即不具备特定形状的非实体对象, 例如, 火焰,气泡,花火,烟雾,蜂群,焰火以及云朵等等.

粒子系统不可或缺. 粒子系统整体可表示为一类可配置的对象, 并用于模拟雨,雪和鸟群等效果.

2.8 可编程动画

大多数常见的动画类型都是可编程动画或者动态动画, 我们经常需要对一类行为进行编程设计(比如, 角色到达门口的时候门的开启行为, 玩家操作游戏角色在环境中移动等等), 开发人员针对特定的功能编写代码, 使得一段时间内对象属性的变化. 与其他形式不同, 编程行为无法通过设计师事先构建, 不过, 多数情况下, 动画的效果可通过设计师制作完成, 代码仅在运行期间简单地对动画行为进行触发和指引.

3. 编写代码实现动画 #

3.1 脚本

基于代码的动画效果是启动动画行为的主要方式, 可以通过 C# 脚本与运动对象实现绑定, 脚本内容一般默认包括两个函数 : Start() 和 Update()

Update 函数与动画行为密不可分, 并与帧和帧率相关, 对于 FPS = 70 的游戏, 基于脚本的各个对象, Update 函数每秒会被执行 70 次, 进而使该对象处于活动状态.

1
2
3
4
5
6
7
8
9
10
using UnityEngine;
using System.Collections;

public class Mover : MonoBehaviour {
void Start() {}
void Update() {
Transform thisTransform = GetComponent<Transform>();
thisTransform.position += new Vector3(1f, 0f, 0f);
}
}

3.2 一致性动画(速度,时间和 deltaTime 变量)

由于帧速率在不同的计算机上有所不同, 甚至同一计算机的一段时间内也会发生变化. 在 FPS = 70 的系统上, 对象每秒将更新 70 个单位, 在 FPS = 90 的系统上则为 90 个, 如果这样用户将得不到一致的游戏体验, 也会导致多玩家的游戏全部玩家无法处于同步状态的情况. 这是绝对不可以的.

行进距离 = 速度 x 时间. 时间可以在不同的计算设备间保持一致, 所以应该直接将运动行为映射至时间.

1
thisTransform.position += new Vector3(1f * Time.deltaTime, 0f, 0f);

放在 Update 函数中的代码是以帧来执行的. 如果我们需要物体的移动以秒来执行, 就需要将物体移动的值乘以 Time.deltaTime. 在各帧中, Time.deltaTime 显示了距上一帧所经历了的时间值(秒). 使用这个变量和游戏帧速率无关. 实际表示的将是 : 每秒移动物体 1 米而不是每帧移动 1 米.

当从 MonoBehaviour 的 FixedUpdate 里调用时, 返回固定帧速率增量时间(fixedDeltaTime).

另外, OnGUI 里不应该依赖 Time.deltaTime, 因为 OnGUI 可以在每帧被多次调用并且每个调用 deltaTime 将持有相同的值, 直到下一帧再次更新.

3.3 某一方向上的运动

这里是向量的概念, 形如 (x, y, z) 的向量包含了 3 个分量, 并以此为某一方向. 三个轴同步运动.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using UnityEngine;
using System.Collections;

// 方向控制
public class Mover : MonoBehaviour {
public float speed = 1f;
public Vector3 direction = Vector3.zero;

void Update() {
Transform thisTransform = GetComponent<Transform>();
thisTransform.position += direction.normalized * speed * Time.deltaTime;
}

}

3.4 动画曲线对中间帧进行编码

游戏对象还需要实现曲线运动, 仅仅直线路径想必是不足以的, 或者需要对象实现变速运动, 以上对此可以使用动画曲线这一特定的对象, 构建定义动画的中间帧的曲线, 进而控制对象在关键帧之间的变化方式.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using UnityEngine;
using System.Collections;

// 速度调整
public class Mover : MonoBehaviour {
public float speed = 1f;
public Vector3 direction = Vector3.zero;

public AnimationCurve animCurve;

void Update() {
Transform thisTransform = GetComponent<Transform>();
thisTransform.position += direction.normalized * speed * animCurve.Evaluate(Time.time) * Time.deltaTime;
}
}

在对象查看器中对其进行考察, 单击查看器中的曲线, 曲线编辑器可以作为一个单独的对话框显示, 可以通过速度值控制中间帧.

曲线编辑器的水平轴表示时间(秒), 垂直轴表示为速度值. 用户可以在左下角的预制图生成初始曲线, 并针对对象速度控制其插值计算, 还可以在曲线上双击插入新的控制点, 并对曲线加以控制, 如果最新控制点使曲线产生了边角失去平滑特征, 可以使用 Free Smooth

3.5 利用协同程序旋转对象

编写一个脚本, 缓慢平滑地旋转对象, 并使其一直朝向目标对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class LookAt : MonoBehaviour {
private Transform thisTransform = null;
public Transform target = null;
public float rotateSpeed = 100f;

void Awake() {
thisTransform = GetComponent<Transform>();
}

void Start() {
StartCoroutine(TrackRotation(target));
}

IEnumerator TrackRotation(Transform target) {
while(true) {
if (thisTransform != null && target != null) {
Vector3 relativePos = target.position - thisTransform.position;

Quaternion newRotation = Quaternion.LookRotation(RelativePos);
thisTransform.rotation = Quaternion.RotateTowards(thisTransform.rotation, newRotation, rotateSpeed * Time.deltaTime);
}
yield return null;
}
}

void OnDrawGizmos(){
Gizmos.DrawLine(thisTransform.position, thisTransform.forward.normalized * 5f);
}
}

协同程序工作方式与常规函数不同, IEnumerator 返回类型可视为其特征, 并至少包含一条 yield 语句. 在程序重新执行后, 协同程序可与其调用处理过程并行运行, 这一点类似于线程或背景线程, 即多任务机制, 可与其他处理共同运行.

对动画而言, 当其他处理过程运行时, 可以实现动画效果并改变对象的属性.

3.6 材质和贴图动画

需要通过编程方式调整或修改一段时间内网格顶点间的 UV 坐标, 进而移动表面纹理. 过程中不改变纹理内的像素, 且映射至表面上的像素可实现动画效果. 水流,岩浆,云朵,魔幻隧道的效果等等.

End.

学习自 : Book <<Unity Animation Essentials>>