这是用户在 2025-8-3 13:33 为 https://learn.unity.com/tutorial/shi-jue-feng-ge-yong-hu-jie-mian-tai-tou-xian-shi?uv=2020.3&project... 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
Unity Learn 主页
查看教程内容

视觉风格 - 用户界面 - 抬头显示

教程
初级
1 小时
(434)
摘要
到目前为止,你一直在使用 Debug.Log 在主角受伤时输出当前的生命值。但是玩家不会有 Console 窗口,因此无法查看最终游戏中的日志。所以,为了给玩家提供反馈,交互式应用程序使用用户界面(简称 UI)将图像和文本叠加在场景上来显示信息。这种显示方式也称为抬头显示 (HUD)
选择 Unity 版本
最后更新:2021 四月 12
2020.3
2020.2
2020.1
2019.4
2019.3
2019.2
2019.1
2018.4
2018.3
语言
中文

1.UI 画布

在本教程中,你将向你的项目添加 UI,用于显示主角的当前生命值。
选择要展开的图像
输入图像描述 (可选)

Unity 中的 UI 使用一种称为画布 (Canvas) 组件的游戏对象来渲染特定于 UI 的组件,例如图像、滑动条和按钮。画布定义了每个 UI 元素应如何在屏幕上呈现,并负责渲染所有作为子对象的 UI 组件
要创建 UI,请执行以下操作:
1.创建 UI 的第一步是创建一个画布画布的创建方式与任何其他游戏对象没有区别:在 Hierarchy右键单击一个空白位置,或使用 Hierarchy 顶部的 Create 按钮,然后选择 UI > Canvas
执行此操作后,你会发现还会向场景中添加另一个游戏对象,名为 EventSystem。这是一个带有特殊组件的游戏对象,可以处理事件以及与 UI 的交互,例如单击鼠标。这个游戏对象在这里用不到,但需要保留在场景中,否则画布会记录警告。
2.选择新创建的 Canvas 游戏对象,然后查看 Inspector
选择要展开的图像
输入图像描述 (可选)


2.Rect Transform

游戏对象的第一个区别是具有 Rect Transform 组件,而不是在其他游戏对象上看到的 Transform 组件。
Rect Transform(矩形变换)仍然是一种 Transform(变换),因此可以在脚本中用作变换,但矩形变换具有额外的 UI 数据,稍后将进行介绍。目前,只需看成是一种特殊的变换
画布定义了 UI 在游戏中的显示方式。画布可以处于以下任何一种模式:
  • Screen Space - Overlay:这是默认模式,可以让 Unity 在始终在游戏的上层绘制 UI。大多数应用程序使用此模式,因为它们希望 UI 始终位于最上层以便提供所有信息。
  • Screen Space - Camera:这种模式在与摄像机对齐的平面上绘制 UI。平面的大小确定为始终填充整个屏幕,这样你就可以四处移动摄像机,并且平面将随摄像机一起移动,从而显示与 Overlay 图形相同的形状。但是,由于平面是在世界中绘制的,而不是在屏幕上层绘制的,因此世界中的对象可以绘制在 UI 的上层。
  • World Space:这种模式可在世界中的任何位置绘制平面。例如,你可以将此平面用作游戏中的计算机屏幕,或者用作墙壁,或者放在角色的上层。这在 3D 游戏中更有用,因为 UI 会随着距离变小。
在我们这种情况下,你需要保留默认模式 Screen Space - Overlay,因为你希望一直显示 Ruby 的生命值,以便无论摄像机面向什么方向,这个生命值都不会被世界中的对象隐藏。
你可以忽略游戏的其他参数。有关更多信息,请参阅画布 (Canvas) 文档
提示:单击组件上带有问号的小书图标可以打开相应文档。

3.Canvas Scaler

Canvas 画布具有 Canvas Scaler 组件,用于定义 UI 在不同的屏幕大小下如何缩放。有些玩家可能以 800 x 600 的分辨率运行你的游戏,而其他玩家可能以 1920 x 1080 的分辨率运行。或者对于移动端应用程序,可能会在横向和纵向模式下使用应用程序。所有这些选项都要求具有不同的屏幕大小和比例。
可以将 Canvas Scaler 模式设置为以下选项之一:
1.Constant SizePixelPhysical):无论屏幕大小或形状如何,UI 均保持大小不变。这样可以使 UI 在任何屏幕上都能看清楚,但在较小的屏幕上可能会被 UI 占据很大的空间,而且如果屏幕太小,元素也不能重叠。
选择要展开的图像
输入图像描述 (可选)

2.Scale With Screen Size:UI 缩放取决于你设置为 Reference Resolution 的屏幕大小。
例如,如果将 Reference Resolution 设置为 800 x 600,并且屏幕宽度为 1600 像素,则 UI 会缩放为两倍大。这样,无论屏幕大小如何,UI 始终覆盖屏幕上的相同区域。
此模式的缺点在于,这可能会导致以下情况:如果你的基础大小很大,并且有玩家在较小的屏幕上玩游戏,那么 UI 会太小而看不清;如果你的 UI 是为小屏幕设计的,但玩家在大屏幕上玩游戏(放大很多),那么 UI 看上去会很模糊或像素化:
选择要展开的图像
输入图像描述 (可选)

在你这种情况下,因为你的 UI 再简单不过,也更方便学习,所以将设置保留为 Constant Pixel Size
3.最后,Canvas 画布上的最后一个组件是 Graphic Raycaster。在本教程中不会使用这个组件,但这个组件可以检测玩家是否点击了画布中的对象(比如按钮)。

4.向 UI 中添加图像

现在,你已经完成了画布的设置,接下来可以将生命值计量表添加到 UI 中。这里将包含两部分
第 1 部分:背景图像,即 Ruby 的肖像
第 2 部分:生命值计量表的字段。顶部绘制的蓝色计量表将根据角色当前生命值动态变化。
要添加图像,请执行以下操作:
1.画布中单击右键,或者选择画布后Hierarchy 中单击 Create,然后选择 UI > Image。此时将创建一个 Image 游戏对象作为 Canvas 的子对象,因为需要在该画布上渲染这个图像。
如果打开 Game 窗口,你会发现屏幕中间有一个白色正方形。你还没有为图像分配精灵,所以图像现在呈现为白色正方形:
选择要展开的图像
输入图像描述 (可选)

但如果在 Scene 视图中查看,则看不到白色正方形。这是因为 Scene 视图Game 视图UI 位置和大小的处理方式略有不同。
到目前为止,所有对象都是有“单位”的,因此沿一个轴将一个游戏对象移动 10 会将这个游戏对象在世界中移动 10 个单位,此处我们的 UI 元素Rect Transform 值以像素为单位。
因此,如果你的屏幕宽度为 800 像素,并且图像位于位置 400 处,那么这个图像就会位于 UI 的屏幕中间,但在 Scene 视图中位于 400 个单位处对我们来说太远(也太大)了,看不见。

5.在编辑器中编辑 UI:

1.要在编辑器中编辑 UI,请在 Hierarchy 中选择 Canvas,然后可以执行下列操作之一:
  • 双击
  • 将鼠标悬停在 Scene 视图上并按 F 键
2.你现在应该可以看到白色正方形和带有四个蓝色圆点的矩形(作为画布的边界)。在左下角,可以看到非常小的游戏世界。
选择要展开的图像
输入图像描述 (可选)

3.让我们将白色矩形更改为所需的图像,方法如下:在 Art > Sprites > UI 中,将名为 UIHealthFrame 的精灵拖入 Health 游戏对象(也就是 Canvas 的子对象)的 Source Image 设置中。
选择要展开的图像
输入图像描述 (可选)

图像被完全压缩,因为要保持矩形的大小。
4.要将 Rect Transform 值更改为图像的大小,请在 Image Inspector 中单击 Set Native Size 按钮。目前来说,图像对于屏幕而言应该太大了:
选择要展开的图像
输入图像描述 (可选)

这是因为图像宽度为 1336 像素,而你的屏幕小于该像素值。但接下来你要将图像缩放到适当的大小。

6.调整图像大小

1.选择图像,然后确保在工具栏中选择矩形工具快捷键:T)。
2.拖动图像的角或边以调整图像大小。如果在拖动角时按住 Shift 键,则会在调整大小时保持正确的比例,并且图像将均匀缩放。
3.也可以单击并拖动图像,这样图像会“贴靠”到画布的一个角。让我们将图像放在左上角
选择要展开的图像
输入图像描述 (可选)

可以随时打开 Game 视图来检查在游戏中的显示效果。放置图像只是我们工作的第一部分。
现在,如果调整屏幕大小(例如,通过调整 Game 视图的大小),元素的位置将发生变化:
选择要展开的图像
输入图像描述 (可选)


7.什么是锚点?

你的图像锚定在屏幕中央,但什么是锚点呢?在选择图像后,观察 Scene 视图
选择要展开的图像
输入图像描述 (可选)

屏幕中央的十字就是图像的锚点。这是计算对象位置时的起点(由绿色箭头显示,从图像的锚点到图像的轴心(即小蓝色圆圈))。
因此,在调整屏幕大小时,该位置将保持不变,并且图像不会随着屏幕边框移动。
要解决此问题,你需要将图像锚定到一个角。这样,如果由于调整了屏幕大小而移动了该角,则图像将保持在相同的距离内并随之移动。
要将图像锚定,请执行以下操作:
1.你可以使用 Rect Transform 直接移动锚点,但是 RectTransform 允许我们直接将锚点锚定到一个角上。
2.单击 Rect Transform 左上角的正方形,然后选择左上角的选项。
选择要展开的图像
输入图像描述 (可选)

你可以在 Scene 视图中看到锚点已经移动到这个角。
还要注意,Rect TransformPos XPos Y 已更改:现在是从画布的角(而不再是中心)开始计算的!
确保图像在这个角的位置,现在调整屏幕大小时,UI 会保持在这个角的位置!
还可以将游戏对象从 Image 重命名为 Health,这样我们就知道这是生命值的 UI。使用 Inspector 顶部包含“Image”的文本框,并将内容更改为 Health
选择要展开的图像
输入图像描述 (可选)


8.添加肖像

现在,你需要添加 Ruby 的肖像以及蓝色条来完善我们的 Health UI
要添加肖像,请执行以下操作:
1.Art > Sprites > UI 中找到肖像,名为 CharacterPortrait
2.创建一个新图像作为 Health Image 的子对象,将找到的肖像分配给这个子对象,单击 Set Native Size 并调整其大小(同时按住 Shift 键可保持相同的比例,不会压扁!)。尝试调整为不同大小,并移动到生命值条背景的蓝色圆圈中,直到对结果满意为止。
选择要展开的图像
输入图像描述 (可选)

你这里实际上并不需要更改锚点,因为已经锚定在父级(在我们这种情况下为 Health 游戏对象)的中心。所以,在通过调整屏幕大小来移动 Health 游戏对象时,中心也会移动,并且肖像也将随之移动。
但是,如果水平调整生命值条对象的大小,中心位置将会更改,因此会移动肖像的位置,但不会调整肖像的大小:
选择要展开的图像
输入图像描述 (可选)

这是因为锚点不仅定义位置,还定义大小。如果按下 Rect Transform 组件中的锚点按钮,然后选择右下角的展开蓝色箭头,则会看到作为锚点的 4 个箭头移动到父图像的 4 个角
现在图像大小不是绝对值,而是相对于这些锚点之间的距离的值。因此,如果图像大小在左右锚点之间为 25%,若将锚点移近(例如通过调整大小),则图像将调整大小以保持在该比例之内:
选择要展开的图像
输入图像描述 (可选)

3.要正确调整生命值条的大小,请更改 Rect Transform 以拉伸和移动锚点。为此,可以单击并拖动锚点,让锚点围住肖像。
选择要展开的图像
输入图像描述 (可选)


9.遮盖生命值条

现在,你需要添加蓝色的生命值条,并且需要添加一种方法让角色在受伤时缩短生命值条
你可以在 Ruby 的生命值满血时简单地将生命值条标定为 1,然后逐渐减少生命值条中的生命值,直到为空时减小为 0。但这种方法会将生命值条压得很扁:
选择要展开的图像
输入图像描述 (可选)

因而,你需要改用遮罩技术。遮罩UI 系统中的一种技术,利用这种技术可以将一张图像用作其他图像的“遮罩”。我们可以看成是第一张图像充当一个模板。第二张图像中与第一张图像重叠的部分是可见的,但另一部分则是隐藏的。

10.如何创建生命值条遮罩

要创建生命值条遮罩,请执行以下操作:
1.首先创建 Health 游戏对象Image 游戏对象子对象,并将这个子对象命名为 Mask
2.调整这个对象的大小并移动其锚点以适应 UI 中放置生命值条的空白位置:
选择要展开的图像
输入图像描述 (可选)

3. 最后一步是移动轴心,也就是左侧的蓝色空圆圈。为什么呢?因为在稍后使用代码调整大小时,你将根据轴心调整大小。
注意:如果将轴心保留在中间,并减小 20% 的大小(此数字是举例),则会在每一侧调整 10% 的大小。通过将轴心放在左侧,可以确保仅在右侧完成 20% 的大小调整。
选择要展开的图像
输入图像描述 (可选)

不需要指定精灵,因为你是要用作模板,默认的白色矩形可以完美满足你的需求。
4.创建该遮罩的新图像子对象,并为这个对象分配 Art > Sprites > UI 文件夹中的 UIHealthBar 精灵。然后,不要像以前那样单击 Set Native Size,而是通过单击图像的 Rect Transform 中的相应图标来打开锚点菜单:
选择要展开的图像
输入图像描述 (可选)

5.现在,按住 Alt 键的同时,在出现的弹出窗口中单击右下角的图标:
选择要展开的图像
输入图像描述 (可选)

此操作将同时设置锚点和新图像的大小以填充其父级,因此生命值条会自动设置为正确的大小。完成上述操作后,重新打开这个弹出窗口,这一次像之前一样选择左上方的图标,但不要按 Alt 键,仅将生命值条的锚点设置在左上角,而不调整大小。
6. 现在,再次单击 Mask,选择 Add Component 并搜索 Mask。添加此组件,然后取消选中 Show Mask Graphic 以隐藏白色正方形。
7.调整遮罩大小,但请注意,这是你在 Hierarchy 中选择的 Mask 遮罩,而不是生命值条!
选择要展开的图像
输入图像描述 (可选)

此时会隐藏生命值条。因为矩形是模板,所以在你缩小矩形时,生命值条上与较小遮罩不重叠的所有部分都将被隐藏。这也是为什么将生命值条的锚点设置在一个角处,而不是根据遮罩来缩放。如果生命值条根据重新缩放,无法达到目的。

11.编写生命值条的脚本

现在已经完成了生命值条的可视部分,接下来我们看一下如何通过脚本处理生命值条的变化。
1.我们来创建一个名为 UIHealthBar 的新脚本,其中包含以下代码行:
public class UIHealthBar : MonoBehaviour { public Image mask; float originalSize; void Start() { originalSize = mask.rectTransform.rect.width; } public void SetValue(float value) { mask.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, originalSize * value); } }
2. 除了使用 rect.width 获取屏幕上的大小和使用 SetSizeWithCurrentAnchors 从代码中设置大小和锚点之外,这里没有其他新内容。
3.生命值更改为介于 01 之间的值(1 表示生命值为满血,0.5 表示一半,以此类推)时,你的代码将调用 SetValue,这将更改我们的遮罩大小,进而隐藏生命值条的右侧部分。
4. 现在,如果返回 Unity 编辑器来编译脚本,你会收到一条错误消息,提示“The type or namespace name 'Image' could not be found”。这是因为 Image 不是 UnityEngine 主代码“命名空间”的一部分(命名空间可将所有类似的内容分组在一起),而是在一个名为 UnityEngine.UI 的子类别中。
我们可以通过键入 UnityEngine.UI.Image 来解决此问题,但是 GameObjectUnityEngine 命名空间的一部分,并且到目前为止,我们还不必编写 UnityEngine.GameObject,为什么会这样呢?
实际上,在脚本的顶部,还记得在第一个教程中提到的带有“using”关键字的代码行吗?当时我们略过了这一个话题。这些关键字可以将命名空间中的所有内容“导入”到脚本中。
代码行 using UnityEngine; 让你无需在 UnityEngine 中的所有类(如 GameObject)前面键入 UnityEngine。所以,如果你在 using.UnityEngine; 代码行下面直接添加 using UnityEngine.UI; 代码行,你的代码将会正确编译,因为现在可以在该脚本内直接使用 Image 了。
为了更改生命值条,要做的最后一件事是告知脚本,生命值已经变了。你需要从 RubyController 脚本的 ChangeHealth 函数中调用 SetValue 来提供新的生命值。
可以像到先前一样,在 RubyController 脚本中公开 UIHealthBar 类型的公共变量,然后在 Inspector 中手动分配这个变量。但是你需要在创建的每个场景中执行此操作。

12.引用

让我们研究一种引用对象的新方法:使用静态成员。到现在为止,当你在脚本中创建了成员(比如我们的 Enemy 脚本中的 public float speed)时,使用该成员的每个对象的每个实例都有一个该成员的副本。如果有十个敌人,则可以设置十个不同的速度,因为每个敌人都有自己的速度变量。
静态成员由该类型的所有对象共享。因此,如果在敌人脚本中将速度设为静态成员(通过在前面添加关键字 static),然后对一个敌人更改速度,则会更改所有敌人的速度。这是因为静态成员访问内存中的相同空间,而不是各自有自己的空间。静态成员还允许我们使用类名代替引用来访问该变量(因此使用 Enemy.speed 代替 myEnemy.speed,其中 myEnemy 是一个变量,包含对敌人的引用)。
你在前面使用过一个静态成员:还记得 Time.deltaTime 吗?是的,这就是 Time 类的静态成员,所以你不需要编写下面的代码:
Time myTime = gameObject.GetComponent<Time>(); myTime.deltaTime;
因为 deltaTime 是静态的,所以只需键入 Time.deltaTime
当然,静态成员也可以是函数,并且你也一直在使用这样的一个静态成员函数:Debug.Log。同样,你不需要获取对 Debug 对象的引用,只需直接在类名上调用该函数即可。
在你这种情况下,你希望不必使用引用就直接能够从任何其他脚本访问 UIHealthBar 脚本。下面是对 UIHealthBar 脚本的修改:
public class UIHealthBar : MonoBehaviour { public static UIHealthBar instance { get; private set; } public Image mask; float originalSize; void Awake() { instance = this; } void Start() { originalSize = mask.rectTransform.rect.width; } public void SetValue(float value) { mask.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, originalSize * value); } }
让我们看一下修改内容:
  • static UIHealthBar instance 属性表示是静态的公共属性,因此可以在任何脚本中编写 UIHealthBar.instance,都会调用该 get 属性。set 属性是私有属性,因为我们不希望其他人能够从脚本外部进行更改。
  • 然后在你的 Awake 函数中(注意,一旦创建了对象,在我们这种情况下就是指游戏开始后,便会调用此函数),你在静态实例中存储了 this,这是一个特殊的 C# 关键字,表示“当前运行该函数的对象”。
现在,游戏开始后,生命值条脚本将获取其 Awake 函数调用,并将自身存储在名为“instance”的静态成员中。因此,如果在任何其他脚本中调用 UIHealthBar.instance,则返回给该脚本的值就是我们的场景中的生命值条
现在,你可以直接引用场景中的生命值条脚本,不必在 Inspector 中手动分配。为什么以前不对其他所有内容都这样做?在前面可以看到,静态成员在脚本的所有实例之间共享,因此,在附加了相应脚本的所有游戏对象中,静态成员的值都是相同的。
如果场景中有两个生命值条,则第二个生命值条也会将自身存储在静态成员中,并替换第一个生命值条。因此,UIHealthBar.instance 将始终返回第二个生命值条,而始终不返回第一个。这就是为什么将这种特定设置称为单例,因为只能存在一个该类型的对象。这正是你这种情况下想要的结果:只有一个生命值条

13.更新生命值条

现在,让我们在游戏过程中动态更新生命值条。只需打开 RubyController 脚本,然后在 ChangeHealth 函数中将 Debug.Log 代码行替换为: UIHealthBar.instance.SetValue(currentHealth / (float)maxHealth);
你现在将 currentHealth 相对于 maxHealth 的比值提供给 UIHealthBar SetValue 函数。maxHealth 前面的 (float) 让 C#maxHealth 视为一个浮点值。
currentHealthmaxHealth 都是整数,将两个整数相除会被 C# 视为一个整数除法,因此 2/4 不会得到 0.5,而是 0。将其中一个数字强制为浮点数会将这个除法变为 2/4.0,这样就会得出的浮点结果等于 0.5
现在,你可以将 UIHealthBar 脚本添加到生命值条游戏对象,将遮罩拖入 Inspector 中的 Mask 属性中,然后进入运行模式。如果你让 Ruby 受到敌人或伤害区域的伤害,她的生命值条便会相应更新。

14.检查你的脚本

你的 RubyController 脚本现在应如下所示:
public class RubyController : MonoBehaviour { public float speed = 3.0f; public int maxHealth = 5; public GameObject projectilePrefab; public int health { get { return currentHealth; }} int currentHealth; public float timeInvincible = 2.0f; 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; } if(Input.GetKeyDown(KeyCode.C)) { Launch(); } } 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) { if (isInvincible) return; isInvincible = true; invincibleTimer = timeInvincible; } currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); UIHealthBar.instance.SetValue(currentHealth / (float)maxHealth); } void Launch() { GameObject projectileObject = Instantiate(projectilePrefab, rigidbody2d.position + Vector2.up * 0.5f, Quaternion.identity); Projectile projectile = projectileObject.GetComponent<Projectile>(); projectile.Launch(lookDirection, 300); animator.SetTrigger("Launch"); } }
你的 UIHealthBar 脚本现在应该如下所示:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class UIHealthBar : MonoBehaviour { public static UIHealthBar instance { get; private set; } public Image mask; float originalSize; void Awake() { instance = this; } void Start() { originalSize = mask.rectTransform.rect.width; } public void SetValue(float value) { mask.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, originalSize * value); } }

15.总结

在本教程中,你了解了 Unity 如何渲染 UI,以及如何在编辑器中使用矩形工具放置元素并调整元素大小,从而确保在多种比例下都能正常显示。
你还学习了如何在脚本和单例中使用静态成员,使 UIHealthBar 脚本可从任何位置被访问。
在下一教程中,你将进一步强化你的 UI 以添加一个对话的角色,并将介绍视频游戏制作的重要概念:射线投射 (Raycasting)

项目:
Ruby's Adventure:2D 初学者
视觉风格 - 用户界面 - 抬头显示
视觉风格 - 用户界面 - 抬头显示
一般教程讨论
0
0
1. UI 画布
0
0
2. Rect Transform
0
0
3. Canvas Scaler
0
0
4. 向 UI 中添加图像
0
0
5. 在编辑器中编辑 UI:
0
0
6. 调整图像大小
0
0
7. 什么是锚点?
0
0
8. 添加肖像
0
0
9. 遮盖生命值条
0
0
10. 如何创建生命值条遮罩
18
5
11. 编写生命值条的脚本
0
0
12. 引用
0
0
13. 更新生命值条
0
2
14. 检查你的脚本
0
0
15. 总结
3
2