这是用户在 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.GetAxisrigidbody.MovePosition 中使用了函数,或者在默认的 StartUpdate 函数中编写了代码,但从未编写过你自己的函数。我们来快速了解一下这个函数。

函数声明

第一行被称为函数声明,看起来很像 StartUpdate 函数(函数声明告诉编译器如何编译该函数):
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 的生命值将始终保持在 0maxHealth 之间。

在 Console 窗口中显示生命值

最后,你通过添加相关代码,借助 Debug.LogConsole 窗口中显示当前生命值。每次生命值变化时,此脚本都会更新控制台
在我们的游戏中没有任何图形或文本可以显示 Ruby 的生命值,因此你可以检查控制台来查看一切是否正常。
为了更容易在控制台中读取生命值,你使用了 + 将字符串合并为一个方便阅读的短语。在此示例中,你在 currentHealthmaxHealth 之间插入了一个斜杠 (/)。 现在让我们回到 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 > Scripts2.右键单击,然后选择 Create > C# script3. 将新脚本命名为 HealthCollectible4.Hierarchy 中,选择 CollectibleHealth 游戏对象。将 HealthCollectible 脚本从 Project 窗口拖放到 Inspector,从而将这个脚本作为组件添加到游戏对象5.双击脚本文件以便在脚本编辑器中打开此文件。 6.删除 StartUpdate 函数,因为你不想在游戏开始时或在每一帧处理任何事务。 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)DestroyUnity 的一个内置函数,可销毁你作为参数传递给这个函数的任何对象;在此示例中为 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