这是用户在 2025-8-3 13:25 为 https://learn.unity.com/tutorial/shi-jie-jiao-hu-ke-shou-ji-dui-xiang?uv=2020.3&projectId=5facf921ed... 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
Unity Learn 主页
查看教程内容

世界交互 - 可收集对象

教程
初级
1 小时
(919)
摘要
你现在已完成装饰世界的工作,并使世界与你的角色之间有更好的交互,接下来便可以添加更多游戏玩法了。
在本教程中,你将学习游戏的另一个重要元素:触发器,它允许角色收集对象(以及其他物品)。
选择 Unity 版本
最后更新:2021 四月 12
2020.3
2020.2
2020.1
2019.4
2019.3
2019.2
2019.1
2018.4
2018.3
语言
中文

1.向 Ruby 添加生命值统计功能

可补充角色生命值的生命值包是演示可收集对象的一个良好示例。但是你需要首先向 Ruby 提供一些生命值,然后才可以补充 Ruby 的生命值。
让我们来修改你的角色脚本,向角色添加生命值:
1.打开 RubyController 脚本。
2. 如下所示修改脚本:
public class RubyController : MonoBehaviour { public int maxHealth = 5; int currentHealth; Rigidbody2D rigidbody2d; float horizontal; float vertical; // 在第一次帧更新之前调用 Start void Start() { rigidbody2d = GetComponent<Rigidbody2D>(); currentHealth = maxHealth; } // 每帧调用一次 Update void Update() { horizontal = Input.GetAxis("Horizontal"); vertical = Input.GetAxis("Vertical"); } void FixedUpdate() { Vector2 position = rigidbody2d.position; position.x = position.x + 3.0f* horizontal * Time.deltaTime; position.y = position.y + 3.0f * vertical * Time.deltaTime; rigidbody2d.MovePosition(position); } void ChangeHealth(int amount) { currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); Debug.Log(currentHealth + "/" + maxHealth); } }
让我们来详细查看这些代码更改。

2.创建新变量

首先,你创建了两个新变量:
public int maxHealth = 5;
maxHealth 变量存储 Ruby 可以拥有的最大生命值。让我们对其进行分解说明:
  • 前缀为 public。你将在本教程的后面部分中找到有关此内容的更多信息。
  • 类型为 int,这是你以前没遇到过的新类型。这种类型告诉计算机你要存储一个整数,即不带小数点的整数。Ruby 不能具有 4.325 生命值或 2.97412 生命值,在此示例中,角色的最大生命值为 5。

int currentHealth;
currentHealth 变量存储 Ruby 的当前生命值(也是整数)。此变量没有 public 前缀。

3.游戏开始时设置满血生命值

然后,在 Start 函数中,当前生命值初始化为最大生命值,因此在游戏开始时,角色处于满血生命值状态:
currentHealth = maxHealth;

4.添加函数来更改生命值

代码的最后一个部分是全新的内容。这是一个可更改角色生命值的函数:
void ChangeHealth(int amount) { currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); Debug.Log(currentHealth + "/" + maxHealth); }
到目前为止,你已经在 Input.GetAxis 或 rigidbody.MovePosition 中使用了函数,或者在默认的 Start 和 Update 函数中编写了代码,但从未编写过你自己的函数。我们来快速了解一下这个函数。

函数声明

第一行被称为函数声明,看起来很像 Start 和 Update 函数(函数声明告诉编译器如何编译该函数):
void ChangeHealth(int amount)
让我们对其进行分解说明:
  • 首先是 void,这是函数的返回类型。此处的 void 表示“无”,因为该函数不会返回任何值,只更改生命值。
  • 然后,你将函数命名为 ChangeHealth。
  • 与 Start 和 Update 函数不同,此函数的括号内有一个变量,这是一个参数。该参数是对生命值的更改量,因此你可以向函数赋值 2 或 -1(或任何其他整数)。
注意:你已经在 rigidbody.MovePosition 中使用过参数(在 4 -“世界交互 - 阻止移动”中),当时将新位置放在括号中。之所以使用括号是因为要调用 Unity 提供的 MovePosition 函数。完整的 MovePosition 函数声明是 void MovePosition (Vector2 newPosition)。

ChangeHealth 函数

现在让我们来看看 ChangeHealth 函数的主体,即花括号内的代码:
{ currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); Debug.Log(currentHealth + "/" + maxHealth); }
这里使用了另一个名为 Mathf.Clamp 的内置函数来设置当前生命值。这是因为,在 Ruby 处于生命值满血状态时,如果你尝试将生命值增大 2,生命值便会超过最大值。
同样,如果 Ruby 剩下的生命值为 1,而你尝试减少 2,则 Ruby 的生命值将为负。 钳制功能 (Clamping) 可确保第一个参数(此处为 currentHealth + amount)绝不会小于第二个参数(此处为 0),也绝不会大于第三个参数 (maxHealth)。因此,Ruby 的生命值将始终保持在 0 与 maxHealth 之间。

在 Console 窗口中显示生命值

最后,你通过添加相关代码,借助 Debug.Log 在 Console 窗口中显示当前生命值。每次生命值变化时,此脚本都会更新控制台。
在我们的游戏中没有任何图形或文本可以显示 Ruby 的生命值,因此你可以检查控制台来查看一切是否正常。
为了更容易在控制台中读取生命值,你使用了 + 将字符串合并为一个方便阅读的短语。在此示例中,你在 currentHealth 和 maxHealth 之间插入了一个斜杠 (/)。 现在让我们回到 Unity 编辑器,看看 Unity 如何编译对脚本所做的更改,并查看你的角色。

5.在 Unity 编辑器中检查你的更改

现在,在查看 Inspector 中的 Ruby Controller 组件时,你应该会看到一个新属性:
选择要展开的图像
输入图像描述 (可选)

你的脚本公开了一个 Max Health 属性。这是因为你在 maxHealth 变量前面添加了“public”。 public 意味着你可以从脚本外部访问变量,因此 Unity 会将变量显示在 Inspector 中。此变量当前设置为 5,因为这是你在脚本中定义的默认值。
如果你在此处输入 10,它就变为新值。然后,执行 Start 函数时,代码行 currentHealth = maxHealth; 会将当前生命值设置为 10。

6.练习:公开另一个变量

在此练习中,请尝试公开一个新变量,以便能够更改角色的速度,而不是使用在上一教程的代码中直接定义的值 (3.0f)。

答案

下面是练习的答案:
public class RubyController : MonoBehaviour { public float speed = 3.0f; public int maxHealth = 5; int currentHealth; Rigidbody2D rigidbody2d; float horizontal; float vertical; // 在第一次帧更新之前调用 Start void Start() { rigidbody2d = GetComponent<Rigidbody2D>(); currentHealth = maxHealth; } // 每帧调用一次 Update void Update() { horizontal = Input.GetAxis("Horizontal"); vertical = Input.GetAxis("Vertical"); } 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); } void ChangeHealth(int amount) { currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); Debug.Log(currentHealth + "/" + maxHealth); } }

7.什么是触发器?

你现在已经为主角添加生命值,接下来让我们添加一种获取生命值的方法。
为此,你将使用触发器。触发器是一种特殊类型的碰撞体。触发器不会阻止移动,但是物理系统仍会检查角色是否会与触发器碰撞。当你的角色进入触发器时,你将收到一条消息,以便你可以处理该事件。

8.创建可收集的生命值游戏对象

让我们先创建一个可收集的生命值游戏对象:
1.首先,重复你在上一教程中遵循的所有步骤以制作金属箱:
  • 在 Project 窗口中,选择 Assets > Art > Sprites > VFX,并找到 CollectibleHealth。
  • 将这个游戏对象导入到场景中,并调整 PPU 值以设置为适当大小。
  • 将 Box Collider 2D 组件添加到新游戏对象,调整碰撞体大小,以便更好地适应精灵。
2.现在,如果你单击 Play,就像 Ruby 会与箱子碰撞一样,她也会与生命值可收集对象碰撞。但是,这不是你需要的效果。 3.退出运行模式。 4.在 Inspector 中,找到 Box Collider 2D 组件。启用 Is Trigger 属性复选框。
选择要展开的图像
输入图像描述 (可选)


现在,当你测试游戏时,角色将穿过生命值可收集对象。物理系统会记录此碰撞,但由于还没有代码可处理碰撞,因此我们的游戏对碰撞没有反应。

9.创建可收集对象脚本

现在,你可以添加代码来处理碰撞: 1. 在 Project 窗口中,选择 Assets > Scripts。 2.右键单击,然后选择 Create > C# script。 3. 将新脚本命名为 HealthCollectible。 4. 在 Hierarchy 中,选择 CollectibleHealth 游戏对象。将 HealthCollectible 脚本从 Project 窗口拖放到 Inspector,从而将这个脚本作为组件添加到游戏对象。 5.双击脚本文件以便在脚本编辑器中打开此文件。 6.删除 Start 和 Update 函数,因为你不想在游戏开始时或在每一帧处理任何事务。 7.你希望脚本检测 Ruby 与可收集的生命值游戏对象发生碰撞的情况,并向她提供一些生命值。为此,请使用以下特殊函数名称:
void OnTriggerEnter2D(Collider2D other)
提示:确保函数名称和参数类型的拼写正确,因为 Unity 在需要调用函数时会使用这些名称在脚本中“查找”函数。
与 Unity 每帧调用 Update 函数的方式相同,当检测到新的刚体进入触发器时,Unity 将在第一帧调用此 OnTriggerEnter2D 函数。名为 other 的参数将包含刚进入触发器的碰撞体。
8.向该函数添加一个简单的 Debug.Log 以检查是什么进入了触发器,如下所示:
public class HealthCollectible : MonoBehaviour { void OnTriggerEnter2D(Collider2D other) { Debug.Log("Object that entered the trigger : " + other); } }
9.返回到 Unity 编辑器并单击 Play。现在,当 Ruby 接触到可收集对象时,控制台中会显示一条消息,告诉你 Ruby 已进入触发器。

10.为 Ruby 提供生命值

要调整代码来为 Ruby 提供生命值,请执行以下操作:
  • 将脚本中的 Debug.Log 更改为:

void OnTriggerEnter2D(Collider2D other) { RubyController controller = other.GetComponent<RubyController>(); if (controller != null) { controller.ChangeHealth(1); Destroy(gameObject); } }

11.审查你的代码

让我们来审查你的新代码:

访问 RubyController 组件
在第一行代码中,你访问进入触发器的碰撞体的游戏对象上的 RubyController 组件。

If 语句

然后,你引入了一个新脚本概念,即 if 语句。if 语句在你的代码中引入决策功能。该语句将条件添加在两个括号之间;如果该条件成立,Unity 将执行随后的两个花括号之间的代码。
否则会跳过这些代码行,然后在右花括号后继续执行(在此示例中不执行任何其他操作,因为该函数在这个位置结束)。
你在此处使用的条件是:
controller != null
!= 表示“不等于”(与表示“等于”的 == 相反)。因此,我们在这个条件中测试存储在控制器中的值是否不等于 null。null 是一个特殊值,表示“无/空”。
那么,为什么要测试控制器变量是否为空?如果你添加另一个移动的游戏对象(例如敌人),则进入触发器便会调用该函数。但是,随后 GetComponent 在该游戏对象上找不到 RubyController(因为该游戏对象是敌人,而不是玩家角色)。
由于 GetComponent 无法为你提供 RubyController,因此该函数将返回 null,并且你无法在空变量上调用该函数。进行此测试可确保你仅向 Ruby 提供生命值,而不会产生错误(尝试对其他对象执行此操作)。

更改 Ruby 的生命值

在 if 代码块内(if 条件行后花括号内的代码),你使用了先前编写的函数将 RubyController 的生命值更改 1。然后,你调用了 Destroy (gameObject)。 Destroy 是 Unity 的一个内置函数,可销毁你作为参数传递给这个函数的任何对象;在此示例中为 gameObject。这是将脚本附加到的游戏对象(可收集的生命值包)。

12.调整 RubyController 脚本

让我们来查看你的代码:
1. 返回到 Unity 编辑器。 2.你会在控制台上发现错误:“RubyController.ChangeHealth(int)' is inaccessible due to its protection level”。 3.打开 RubyController 脚本。
4.还记得你在 maxHealth 变量之前添加“public”来使这个变量显示在 Inspector 中吗?对于函数,也是一样。 默认情况下,你只能从同一脚本内访问函数。你可以在 RubyController 脚本的 Update 函数中调用 ChangeHealth,但是 HealthCollectible 脚本无法访问该函数。 要允许 HealthCollectible 访问该函数,请找到 RubyController 脚本并执行以下操作:
  • 在 ChangeHealth 函数声明中的“void”之前添加“public”。
  • 在 Start 函数中,将 currentHealth 设置为 1,使你的角色最初具有较小的生命值。
void Start() { rigidbody2d = GetComponent<Rigidbody2D>(); currentHealth = maxHealth; currentHealth = 1; } // 每帧调用一次 Update void Update() { horizontal = Input.GetAxis("Horizontal"); vertical = Input.GetAxis("Vertical"); } 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) { currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); Debug.Log(currentHealth + "/" + maxHealth); }
5.现在,如果你返回到 Unity,刚才的错误应该会从控制台中消失,并且你可以运行场景。
让 Ruby 走到可收集的生命值包并收集这个包!当 Ruby 拾取生命值包时,请在 Console 窗口中检查 Ruby 生命值是否增大并且绝不会超过 maxHealth。
6.保存所做的更改。
7.务必为你的 HealthCollectible 游戏对象创建一个预制件,然后你就可以使用该预制件在场景中放置多个可收集对象。

13.检查 Ruby 是否需要生命值

在完成本教程之前,让我们查看更高级的 C# 功能:属性。
现在,如果你在 Ruby 的生命值满血时拾取一个生命值可收集对象,脚本仍将销毁该可收集对象。这是因为根据你的设置,当角色进入触发器时,无论如何都要删除可收集对象。
为了避免这种情况,你可以添加一项检查以便在销毁生命值可收集对象之前先查看 Ruby 是否需要生命值: 1. 打开 HealthCollectible 脚本。 2.如下所示更改脚本:
void OnTriggerEnter2D(Collider2D other) { RubyController controller = other.GetComponent<RubyController>(); if (controller != null) { if(controller.currentHealth < controller.maxHealth) { controller.ChangeHealth(1); Destroy(gameObject); } } }
这个脚本中添加了另一项测试以查看 Ruby 的生命值是否小于最大生命值。如果相等,该测试的结果将为 false,并且脚本将跳过 if 语句(因为无需更改生命值或销毁可收集对象)。
但在切换回 Unity 之前请先等等!你可能已经猜到在控制台中会有一个错误,这是正常的;currentHealth 是私有变量,因此你无法从外部访问这个变量。
你可以像以前一样将这个变量设置为公共 (public) 变量。但是,将所有内容都公开会导致错误,尤其是对于某些内容,你可能并不希望从任何位置都可以访问和更改它们。 例如,如果将 currentHealth 设置为 public,则可以在另一个脚本中将这个变量更改为 10,即使 maxHealth 仅为 5 的情况下也可以这样修改。为避免这种情况,最好只允许通过函数进行更改。这样做便于执行检查,就像 ChangeHealth 函数一样,可以使用钳制功能确保生命值绝不会小于 0,也不会大于 maxHealth。
因此,你不想将这个变量公开(以防止其他脚本进行更改),但是仍然希望允许其他脚本读取 currentHealth 的值(但这些脚本无法写入值)。为此,你可以使用属性。

14.在 RubyController 中定义一个属性

首先,让我们在 RubyController 脚本中定义一个属性: 1. 打开 RubyController 脚本。 2.添加以下代码行以在脚本中定义一个属性:
public class RubyController : MonoBehaviour { public float speed = 3.0f; public int maxHealth = 5; public int health { get { return currentHealth; }} int currentHealth; Rigidbody2D rigidbody2d; // 在第一次帧更新之前调用 Start void Start() {

你通过以下要素开始了像变量一样的属性定义:
  • 访问级别 (public)
  • 类型 (int)
  • 名称 (health)
但是,此处添加了两个 { } 代码块,而未使用 ; 来结束代码行。 在第一个代码块中,你使用了 get 关键字来获取第二个代码块中的任何内容。
第二个代码块就像普通函数一样,因此只需返回 currentHealth 值。
编译器完全像函数一样处理此代码行,因此你可以在 get 代码块内的函数中编写所需的任何内容(例如声明变量、执行计算和调用其他函数)。

15.使用 HealthCollectible 中的属性

你已定义了一个属性,那么现在如何使用这个属性? 让我们回到 if 语句:
1.打开 HealthCollectible 脚本。 2.在代码中找到 if 语句:
if(controller.health < controller.maxHealth)
属性的用法很像变量,而不是像函数。此处,health 向你提供 currentHealth。但是,只有你读取 health 时,才有这样的作用。 如果你要更改为如下所示:
controller.health = 10;
Unity 的控制台中会显示一个错误。这是因为你只为 health 指定了“get”操作,因此它是只读的。
注意:set 关键字的原理与 get 完全相同,但作用是使属性变为可写。如果提供 set 而没有 get,则只能写入属性,而无法读取属性。你可以在线查找 C# set 属性的信息来了解如何使用这些属性。

16.检查你的脚本

你的 RubyController 脚本现在应如下所示:
public class RubyController : MonoBehaviour { public float speed = 3.0f; public int maxHealth = 5; public int health { get { return currentHealth; }} int currentHealth; Rigidbody2D rigidbody2d; float horizontal; float vertical; // 在第一次帧更新之前调用 Start void Start() { rigidbody2d = GetComponent<Rigidbody2D>(); currentHealth = maxHealth; } // 每帧调用一次 Update void Update() { horizontal = Input.GetAxis("Horizontal"); vertical = Input.GetAxis("Vertical"); } 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) { currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); Debug.Log(currentHealth + "/" + maxHealth); } }
你的 HealthCollectible 脚本现在应如下所示:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class HealthCollectible : MonoBehaviour { void OnTriggerEnter2D(Collider2D other) { RubyController controller = other.GetComponent<RubyController>(); if (controller != null) { if(controller.health < controller.maxHealth) { controller.ChangeHealth(1); Destroy(gameObject); } } } }

17.总结

在涉及大量代码的本教程中,你已经:
  • 了解触发器的工作原理,以及如何检测刚体进入触发器以触发各种操作!
  • 用 C# 编写了你自己的函数
  • 使用私有和公共访问级别
  • 编写了一条 if 语句,仅在测试结果为 true 时才执行某个操作
在下一教程中,你将执行与你在本课中的操作相反的操作:添加游戏对象来伤害 Ruby。到时候,这些生命值可收集对象将变得更加有用!

项目:
Ruby's Adventure:2D 初学者
世界交互 - 可收集对象
世界交互 - 可收集对象
一般教程讨论
0
1
1. 向 Ruby 添加生命值统计功能
0
0
2. 创建新变量
0
0
3. 游戏开始时设置满血生命值
0
0
4. 添加函数来更改生命值
0
0
5. 在 Unity 编辑器中检查你的更改
0
1
6. 练习:公开另一个变量
0
0
7. 什么是触发器?
0
0
8. 创建可收集的生命值游戏对象
0
0
9. 创建可收集对象脚本
0
2
10. 为 Ruby 提供生命值
0
0
11. 审查你的代码
0
0
12. 调整 RubyController 脚本
0
0
13. 检查 Ruby 是否需要生命值
0
0
14. 在 RubyController 中定义一个属性
0
0
15. 使用 HealthCollectible 中的属性
0
0
16. 检查你的脚本
0
0
17. 总结
0
0