这是用户在 2025-8-3 13:28 为 https://learn.unity.com/tutorial/jing-ling-dong-hua?uv=2020.3&projectId=5facf921edbc2a2003a58d3a# 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
Unity Learn 主页
查看教程内容

精灵动画

教程
初级
1 小时
(641)
摘要
在先前的教程中,你创建了一个世界,其中包含大量对象、可四处走动的角色以及要避开的敌人。但是到目前为止,所有这些都是静态的。在本教程中,你将向角色添加动画
选择 Unity 版本
最后更新:2021 四月 12
2020.3
2020.2
2020.1
2019.4
2019.3
2019.2
2019.1
2018.4
2018.3
语言
中文

1.Animator

为了在游戏对象上播放动画,Unity 提供了一个名为 Animator 的组件:
1.进入机器人预制件预制件模式(提醒:请双击 Project 窗口中的预制件,或双击 Hierarchy 窗口中的 Robot 右侧的箭头)。 2. Inspector 中,单击Add Component 按钮并搜索 Animator 组件。
选择要展开的图像
输入图像描述 (可选)

Animator 组件上最重要的设置是 Controller 设置
Controller 组件负责基于你定义的规则来选择要播放的动画(例如,当角色的速度大于 0 时,使角色从站立变为奔跑动画)。你稍后将探索这些规则。

2.创建新的 Controller

现在,我们创建一个新的 Controller 并在 Robot Animator 上设置这个 Controller:
1.在 Project 窗口中,找到 Animations 文件夹。此文件夹包含项目的预制动画,可用于加快后续的工作速度。 2.在 Animations 文件夹中,右键单击并从上下文菜单中选择 Create > Animator Controller。将此 Controller 命名为 Robot
3. 现在,在你添加到机器人预制件上的 Animator 中(如果你已退出预制件模式,请确保再次进入该模式),将你刚创建的 Controller 分配到 Controller 设置中(单击小圆圈并在随后打开的窗口中找到 Controller,或者将 Controller 从 Animation 文件夹拖放到 Controller 设置中)。
选择要展开的图像
输入图像描述 (可选)

请记住使用 Scene 视图右上角的 Save 按钮来保存对预制件的更改。

3.动画

你现在已经有了 Animator Controller,接下来便需要创建供 Controller 使用的动画。动画是存储在 Project 文件夹中的资源。 要在 Unity 编辑器中创建动画,请使用 Animation 窗口
1. 选择 Window > Animation > Animation 来打开 Animation 窗口
如果在 Hierarchy 中选择 Robot,或者在机器人预制件上处于预制件模式,则 Animation 窗口将显示以下消息:“To begin animating [预制件/游戏对象名称], create an Animation Clip”。
2.单击 Create 按钮。
选择要展开的图像
输入图像描述 (可选)

3.现在选择 Animations 文件夹作为保存动画剪辑的位置,然后将该动画剪辑命名为“RobotLeft.anim”
Animation 窗口被分为两个部分:
  • 左侧用于动画化属性
  • 右侧的时间轴显示每个属性的关键帧
选择要展开的图像
输入图像描述 (可选)


4.更改精灵

你可以使用 Animator游戏对象任何组件中的任何属性进行随时间变化的动画处理。此动画可以是你希望随时间推移而变化的精灵颜色,也可以是大小变化。在此示例中,你希望更改 Sprite Renderer 使用的精灵
你可以通过更改 Sprite Renderer 随时间推移而使用的精灵来产生移动的视觉效果。
1. 你可以在 Art > Sprites > Characters 文件夹内(位于名为 MrClockworkSheet精灵图集 (Sprite Atlas)(也称为精灵图集 (Sprite Sheet)中)找到你的机器人的所有精灵
可以看到,单个图像上有多个精灵,就像你先前看到的瓦片面板 (Tile Palette) 一样。在此示例中,你已经将图像拆分为不同的精灵
2.单击图像旁边的箭头以查看所有精灵
选择要展开的图像
输入图像描述 (可选)

3.按住 Shift 键并单击第一个和最后一个行走动画精灵(面朝左)以选择以下全部四个精灵:
  • MrClockworkWalkSides1
  • MrClockworkWalkSides2
  • MrClockworkWalkSides3
  • MrClockworkWalkSides4
4.将这些精灵拖放到 Animation 窗口中。此时将使用四个精灵来创建一段动画。 5.Animation 窗口中按 Play 按钮以预览动画:
选择要展开的图像
输入图像描述 (可选)

可以看到,这个动画运行得太快了。这是因为这个动画的样本大小为 60;该值是在 Animation 窗口内的动画属性上方的 Samples 设置中设定的。
请注意,时间轴在 0:001:00 之间有 60 条垂线。因此,动画会以每秒 60 帧的速度运行,这意味着 Unity 每秒将精灵渲染 60 次
你还可以看到你的 4 个精灵只作为一行中的前 4 个条目,这意味着每个精灵仅在屏幕上停留 1/60(即 0.016)秒。要解决此问题,只需将 Samples 值设置为 4 即可使动画每秒仅更改 4 次,因此每个精灵会在屏幕上停留 ¼ 秒
你的动画现在应该会以恰当速度运行。请尝试将该值更改为所需的随意值,使动画以你想要的速度运行。
这样就完成了一段动画!我们还需要再完成 3 段动画才能得到一套完整动画!

5.创建动画

1.要创建动画,请单击窗口左上方的当前动画名称,选择 Create New Clip,输入名称 Right_Run,然后选择 Animation 文件夹
选择要展开的图像
输入图像描述 (可选)

你可能想知道我们将如何制作向右行走的动画,因为没有机器人向右行走的精灵。那是因为我们可以翻转向左行走的动画。
Sprite Renderer 具有 Flip 属性,请注意你可以将所有属性动画化。
2.通过创建新剪辑、拖动 4 个精灵并将 Samples 值设置为 4 来重新创建向左动画
3.单击 Add Property,然后单击 Sprite Renderer 旁边的三角形,再单击 Flip X 旁边的 + 图标:
选择要展开的图像
输入图像描述 (可选)

现在,你可以为每个帧更改属性的值。你要设置相应值的当前帧显示在 Animation 窗口顶栏前进按钮旁的框中,并在左侧用白色竖条表示。
4.在帧 0 和帧 4 上的时候,选中属性名称旁的切换开关,即可将属性设置为 true,这样 Flip 在整个动画中将保持选中状态:
选择要展开的图像
输入图像描述 (可选)

请注意,对于第 0 帧和第 4 帧,有菱形会与 Flip 属性显示在同一行中。这些菱形表示该帧的值发生改变。如果没有菱形,则该帧将使用最后设置的值。
5.向上奔跑向下奔跑的动画重复上述操作(创建新剪辑、拖动帧、将 Samples 值设置为 4)。
你现在已准备好机器人的所有行走动画,接下来便可以设置你在先前创建的 Controller 了!

6.构建 Controller

Controller 定义动画之间的关系,比如如何从一段动画切换到另一段动画。
要编辑 Controller,请执行以下操作:
  • 打开 Animator 窗口(菜单:Windows > Animation > Animator)。 注意:确保在 Project 文件夹中选择了机器人预制件Robot Animator
Animator 分为两个部分,左侧是 LayersParameters,右侧是动画状态机 (Animation State Machine)
选择要展开的图像
输入图像描述 (可选)

第 1 部分:Layers 和 Parameters
Layers 可用于 3D 动画,因为你可以将动画用于角色的不同部分。
Parameters 由我们的脚本用来向 Controller 提供信息。
第 2 部分:动画状态机
动画状态机以图形方式显示动画的所有状态以及如何从一段动画过渡到另一段动画。
现在,你已经创建段动画,第一段动画 RobotLeft 链接到 Entry,这表示此动画将在游戏开始时播放。
你可以在所有动画之间创建链接来表示诸如“玩家使机器人向右转时,将机器人的方向从左更改为右”的目的。但是这意味着每段动画都将有三条链接,即每条链接指向其他的每段动画。
一种更简单的方法是使用混合树 (Blend Tree),这种混合树允许你根据参数来混合多段动画。
此处,你将根据机器人的移动方向来混合四段动画,使 Controller 能够播放正确的动画。

7.使用混合树

1. 首先通过选择动画并按 Delete 或者通过右键单击动画并选择 Delete来删除所有动画。
2. 然后,在图中某处右键单击,然后选择 Create State > From New Blend Tree
选择要展开的图像
输入图像描述 (可选)

现在,游戏开始时,Controller 将播放 Blend Tree。但是现在是空的,所以不会播放任何动画。
3.双击 Blend Tree 以将其打开。
选择要展开的图像
输入图像描述 (可选)

4.现在单击该“Blend Tree”节点,随即将在 Inspector 中显示相应设置:
选择要展开的图像
输入图像描述 (可选)

Blend Type 设置可定义混合树将使用多少参数来选择要播放的动画。你希望 Blend Tree 使用两个参数来控制水平和垂直方向的更改,因此请将 Blend Type 设置为 2D Simple Directional
例如,当机器人的水平移动为 0 且垂直移动为 -1 时,Blend Tree 应选择 RobotDown 动画,而机器人的水平移动为 1 且垂直移动为 0 时,Blend Tree 应选择 RobotRight 动画。
Blend Type 设置下,你现在将有两个 Parameters 设置。Controller 使用这些值来选择要播放的动画。
目前,Controller 使用了自动创建的 Blend 参数,但是我们将用名为 Move XMove Y(分别表示水平和垂直移动量)的 2 个参数来替换该参数。

8.参数:Move X 和 Move Y

要创建新参数,请执行以下操作:
1.直接找到 Animator 窗口左侧的 Parameters 选项卡:
选择要展开的图像
输入图像描述 (可选)


你需要两个浮点型参数,因为你希望参数值的范围是 -1.01.0
2. 选择并单击 Blend 参数(浮点型),然后将该参数重命名为 Move X,从而完成重命名操作。
3.接下来,单击搜索栏旁边的 + 图标,选择 Float,并将新参数命名为 Move Y
现在,当选择 Blend Tree 时,可以在 Inspector 顶部的下拉选单中选择这两个参数,从而告诉你的 Blend Tree 应当观察这两个参数来选择要播放的动画。
选择要展开的图像
输入图像描述 (可选)

4.然后,单击 Motion 部分底部的 +,然后选择 Add Motion 字段,此操作需要执行 4 次,因为你要混合四段动画。你的 Inspector 最后应如下所示:
选择要展开的图像
输入图像描述 (可选)

5. 现在将四段动画剪辑拖放到名为 Motion四个字段中,并将各个 Pos XPos Y 值设置为相应的方向,如下所示:
  • Left:Pos X = -0.5 Pos Y = 0
  • Right:Pos X = 0.5 Pos Y = 0
  • Up:Pos X = 0 Pos Y = 0.5
  • Down:Pos X = 0 Pos Y = -0.5
你的 Inspector 应如下所示:
选择要展开的图像
输入图像描述 (可选)


该图像表示混合,其中每个蓝色菱形表示一个剪辑,红色点是由 2 个参数的值给出的位置。
6.现在,这个点位于中心,相当于 Move XMove Y 等于 0。但是你可以看到,例如,如果将 Move Y 设置为 1,则红点将移至正方形的顶部(因为 Y 是垂直位置),然后混合树将选择顶部动画(向上奔跑),因为这是最接近的动画。
你可以在 Animator 中的 Blend Tree 节点上看到针对 Move XMove Y 模拟的值:
选择要展开的图像
输入图像描述 (可选)


7.或者,也可以直接移动红点。在 Inspector 的底部,你将看到 Controller 针对给定值选择的动画的模拟情况。按 Play 按钮可查看播放的动画效果。
选择要展开的图像
输入图像描述 (可选)


恭喜!你已完成对动画混合树 (Blend Tree) 的设置!
现在,只需将数据从 EnemyController 脚本发送到 Controller,即可使动画在游戏中正常运行。

9.将参数发送到 Animator Controller

要将参数发送到 Animator Controller,请执行以下操作:
1. 打开 EnemyController 脚本
我们要进行交互的组件是 Animator,就像我们先前需要与游戏对象上的组件进行交互时一样,我们将使用 GetComponentStart 函数中获取这个组件并存储在类变量中。
Animator animator; void Start() { rigidbody2D = GetComponent<Rigidbody2D>(); timer = changeTime; animator = GetComponent<Animator>(); }
2. 现在,我们需要将参数值发送到 Animator。我们可以通过 Animator 上的 SetFloat 函数来完成此操作,因为我们使用的是浮点型参数。
3.SetFloat 函数将参数名称作为第一个参数,并将该参数的当前值作为第二个参数(此处是给定方向上的移动量),因此在 Update 函数中,你将添加:
在移动的第一部分的 if(vertical) 代码块内,添加:
animator.SetFloat("Move X", 0); animator.SetFloat("Move Y", direction);
在此部分中,如果机器人垂直移动,则会将 0 发送到 horizontal 参数,而 direction 定义机器人是向上还是向下移动。
4.else 代码块中,当机器人水平移动时,发送相反值:
animator.SetFloat("Move X", direction); animator.SetFloat("Move Y", 0);
5.现在,你可以按 Play 并检查 Animator 是否为机器人选择了正确的动画!

10.为主角设置动画

为了加快学习过程(本教程已经很长了!),你可以在 Project 中找到为你提供的用于 RubyController
1. 你只需将 Animator 添加到 Ruby 预制件,并将 Animation 文件夹中找到的 RubyController Animator 分配到 Controller 字段
2. 如果在选中预制件后打开 Animator,你将看到如下所示的 Ruby Animator Controller
选择要展开的图像
输入图像描述 (可选)

暂时忽略 Launch 状态,这是下一个关于发射飞弹的教程中要处理的小干扰!
3.现在,只需注意我们有三个状态(即三个混合树):
  • Moving:在 Ruby 奔跑时播放。
  • Idle:Ruby 站立不动时播放。
  • Hit:Ruby 与机器人或伤害区域碰撞时播放。
状态之间的白色箭头是过渡。这些箭头定义状态之间的移动。例如,HitLaunch 之间没有过渡,因为 Ruby 受伤害时无法投掷飞弹
4. 单击 MovingIdle 之间的向下箭头,然后查看 Inspector 以了解该过渡的设置:
选择要展开的图像
输入图像描述 (可选)

其中的大多数设置(例如图形上的条形)可用于 3D(可以混合动画)。在 2D 中,只有部分设置对我们有用。
注意,Has Exit Time 为取消选中状态。这表示 Moving 动画不会等到状态机进入到 Idle 动画之前结束,而是会立即发生变化。
最重要的部分是底部的 Conditions 部分。在两种情况下会发生过渡
  • 如果没有条件,则在动画结束时发生过渡。此处不属于这种情况,因为我们的动画正在循环播放,但是如果你查看从 HitIdle过渡,就属于这种情况:已勾选 Has Exit Time,并且没有设置任何条件,这意味着 Hit 动画将播放一次,然后过渡回 Idle
  • 或者,我们可以根据你的参数设置条件。在此处,如果 Ruby 的速度小于 0.1,则状态机将从 Moving 过渡到 Idle
你可以单击其他过渡来查看这些过渡,并了解过渡的运行方式。
你可能想知道,为什么 Hit 参数在 Parameters 列表中显得有所不同。这是因为该参数不是浮点参数。被击中不是移动或速度之类的“量化值”,而是一次性事件。
这种类型的参数称为“触发器”。如果将该参数用作过渡条件,则从代码激活触发器时(本例中为角色被击中时)将发生过渡

11.修改 RubyController 脚本

你现在已了解 Controller 的设置方式,接下来让我们修改 RubyController 脚本,将这些参数发送到 Controller
1.就像机器人一样,添加一个 Animator 变量,然后在 Start 函数中使用 GetComponent 来获取 Animator 并存储在该变量中。
2.你还将添加一个名为 lookDirectionVector2 变量(初始化为 (1,0)):
Animator animator; Vector2 lookDirection = new Vector2(1,0); void Start() { animator = GetComponent<Animator>();
为什么要存储观察方向?因为与机器人相比,Ruby 可以站立不动。她站立不动时,Move XY 均为 0,因此状态机不知道要使用哪个方向(除非我们指定方向)。
3.lookDirection 存储 Ruby 的观察方向,这样你就始终可以向状态机提供方向。实际上,如果你查看 Animator 参数,这个参数预期为 Look XLook Y 参数。
4. 你将发送来自 Update 函数的这些 Look 参数和 Speed。你只需更改一些代码即可。现在,代码应如下所示:
float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical");
5.让我们更改为:
float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); Vector2 move = new Vector2(horizontal, vertical); if(!Mathf.Approximately(move.x, 0.0f) || !Mathf.Approximately(move.y, 0.0f)) { lookDirection.Set(move.x, move.y); lookDirection.Normalize(); } animator.SetFloat("Look X", lookDirection.x); animator.SetFloat("Look Y", lookDirection.y); animator.SetFloat("Speed", move.magnitude);
6. 我们来看看你所做的更改:
  • Vector2 move = new Vector2(horizontal, vertical); position = position + move * speed * Time.deltaTime;
你将输入值存储在名为 moveVector2 变量中,而不是独立设置用于移动的 xy
7. 功能与你之前设置的完全相同,但只需在一行中同时处理 xy
  • if(!Mathf.Approximately(move.x, 0.0f) || !Mathf.Approximately(move.y, 0.0f))
8.检查 move.xmove.y 是否不等于 0
9. 使用 Mathf.Approximately 而不是 ==,这是因为计算机存储浮点数的方式意味着精度会有很小的损失。
所以,不应测试完美的相等性,因为最终结果应该是 0.0f 的运算可能会得出类似 0.0000000001f 的结果。Approximately 会考虑这种不精确性,如果除去这种不精确性可以认为数值相等,将返回 true。
  • lookDirection.Set(move.x, move.y);
10.如果 xy 不等于 0,则表示 Ruby 在移动,请将你的观察方向设置为你的 Move 向量,然后 Ruby 应该看向自己移动的方向。如果她停止移动(Move xy 等于 0),则不会发生这种情况,look 将保持为 Ruby 停止移动之前的值。
注意,你本可以设置 lookDirection = move,但这是另一种分配向量xy 的方式。
  • lookDirection.Normalize();
11.然后,在 lookDirection 上调用 Normalize,从而使长度等于 1。如前面所述,Vector2 类型存储位置,但也可以存储方向!
(1,0) 将位置存储为世界中心右侧的一个单位,但还存储向右方向(如果你追踪从 0,00,1 的箭头,就会得到向右的箭头)。
向量的长度定义该箭头的长度。例如,等于 (0,-2)Vector2 的长度为 2 并指向下方。如果我们归一化该向量,结果将变为等于 (0,-1),这样仍然会指向下方,但长度为 1
通常,你将会归一化用于存储方向的向量,因为这时的长度并不重要,方向才重要。
注意:千万不要归一化用于存储位置的向量,因为这种向量更改 xy 时会更改位置!
  • animator.SetFloat("Look X", lookDirection.x);
  • animator.SetFloat("Look Y", lookDirection.y);
  • animator.SetFloat("Speed", move.magnitude);
12.接下来你便有三个代码行,用于将数据发送到 Animator,表示观察方向和速度(move 向量的长度)。
如果 Ruby 不动,值将为 0,但如果她移动,则为正数。长度始终为正数,根据上文中的示例,向量 (0,-2) 的长度为 2
然后,你可以按 Play,并尝试移动 Ruby 来测试所有动画。
唯一剩下的一个操作就是触发 Hit 动画。需要通过 animator.SetTrigger (“触发器名称”) 将触发器发送到 Animator。因此,在 ChangeHealth 函数的 if(amount < 0) 代码块内,我们只需添加:
  • animator.SetTrigger("Hit");

12.检查你的脚本

你的 RubyController 脚本现在应如下所示:
public class RubyController : MonoBehaviour { public float speed = 3.0f; public int maxHealth = 5; public float timeInvincible = 2.0f; public int health { get { return currentHealth; }} int currentHealth; bool isInvincible; float invincibleTimer; Rigidbody2D rigidbody2d; float horizontal; float vertical; Animator animator; Vector2 lookDirection = new Vector2(1,0); // 在第一次帧更新之前调用 Start void Start() { rigidbody2d = GetComponent<Rigidbody2D>(); animator = GetComponent<Animator>(); currentHealth = maxHealth; } // 每帧调用一次 Update void Update() { horizontal = Input.GetAxis("Horizontal"); vertical = Input.GetAxis("Vertical"); Vector2 move = new Vector2(horizontal, vertical); if(!Mathf.Approximately(move.x, 0.0f) || !Mathf.Approximately(move.y, 0.0f)) { lookDirection.Set(move.x, move.y); lookDirection.Normalize(); } animator.SetFloat("Look X", lookDirection.x); animator.SetFloat("Look Y", lookDirection.y); animator.SetFloat("Speed", move.magnitude); if (isInvincible) { invincibleTimer -= Time.deltaTime; if (invincibleTimer < 0) isInvincible = false; } } void FixedUpdate() { Vector2 position = rigidbody2d.position; position.x = position.x + speed * horizontal * Time.deltaTime; position.y = position.y + speed * vertical * Time.deltaTime; rigidbody2d.MovePosition(position); } public void ChangeHealth(int amount) { if (amount < 0) { animator.SetTrigger("Hit"); if (isInvincible) return; isInvincible = true; invincibleTimer = timeInvincible; } currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); Debug.Log(currentHealth + "/" + maxHealth); } }
你的 EnemyController 脚本现在应如下所示:
public class EnemyController2 : MonoBehaviour { public float speed; public bool vertical; public float changeTime = 3.0f; Rigidbody2D rigidbody2D; float timer; int direction = 1; Animator animator; // 在第一次帧更新之前调用 Start void Start() { rigidbody2D = GetComponent<Rigidbody2D>(); timer = changeTime; animator = GetComponent<Animator>(); } void Update() { timer -= Time.deltaTime; if (timer < 0) { direction = -direction; timer = changeTime; } } void FixedUpdate() { Vector2 position = rigidbody2D.position; if (vertical) { position.y = position.y + Time.deltaTime * speed * direction; animator.SetFloat("Move X", 0); animator.SetFloat("Move Y", direction); } else { position.x = position.x + Time.deltaTime * speed * direction; animator.SetFloat("Move X", direction); animator.SetFloat("Move Y", 0); } rigidbody2D.MovePosition(position); } void OnCollisionEnter2D(Collision2D other) { RubyController player = other.gameObject.GetComponent<RubyController >(); if (player != null) { player.ChangeHealth(-1); } } }

13.总结

这是一个很长的教程!但现在你已经了解了动画在 Unity 中的运行方式。动画剪辑是用于存储动画数据的资源
Controller 存储的状态机定义这些动画之间的关系。Animator 播放由 Controller 分配并要求播放的动画。我们可以通过脚本中的 Animator 将数据发送到 Controller,从而根据游戏过程来选择正确的动画!
在下一教程中,我们将了解如何通过 Ruby 发射飞弹,并最终修复那些损坏的机器人!

项目:
Ruby's Adventure:2D 初学者
精灵动画
精灵动画
一般教程讨论
0
0
1. Animator
0
0
2. 创建新的 Controller
0
0
3. 动画
0
0
4. 更改精灵
28
4
5. 创建动画
0
0
6. 构建 Controller
0
0
7. 使用混合树
0
0
8. 参数:Move X 和 Move Y
0
0
9. 将参数发送到 Animator Controller
5
5
10. 为主角设置动画
1
1
11. 修改 RubyController 脚本
1
3
12. 检查你的脚本
0
2
13. 总结
0
0