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

世界交互 - 阻止移动

教程
初级
1 小时
(993)
摘要
在本教程中,你将修改角色控制器脚本,以使 Ruby 可以与世界碰撞而不会穿过对象。为此,你将使用 Unity 提供的另一大系统:物理系统 (Physics System)
选择 Unity 版本
最后更新:2021 四月 12
2020.3
2020.2
2020.1
2019.4
2019.3
2019.2
2019.1
2018.4
2018.3
语言
中文

1.什么是物理系统?

学习物理时,你会发现所有的对象移动都是作用力组合的结果。例如,如果你尝试推动箱子,作用在箱子上的力包括:
  • 重力(将箱子往下拉)
  • 作用力(你施加来推动箱子的力)
  • 地面摩擦力(阻碍推动)
如果你要模拟移动和碰撞,需要使用所有数学公式来计算对象上的接触和力。但这需要编写很多复杂的代码。由于物理定律是相同的,因此可以抽象出此代码并在所有游戏中进行共享:这就是物理系统的功能。Unity 有一个内置的物理系统,可以为你计算对象的移动和碰撞。
为避免对我们游戏中的每个对象进行成本高昂的数学运算,Unity 仅对附有 Rigidbody 2D 组件的游戏对象执行这些计算。

2.添加 Rigidbody 2D 组件

让我们首先向 Ruby 添加 Rigidbody 2D 组件:
1.Hierarchy 中,选择 Ruby 游戏对象2.Ruby 游戏对象Hierarchy 拖入 Project 窗口中的 Prefab 文件夹。 3.双击 Ruby 预制件以打开预制件模式4.Inspector 中,单击 Add Component 按钮。 5.搜索“Rigidbody 2D”,然后选择该组件。 注意:确保你选择的是 2D 组件(还有一个组件名为 Rigidbody,是用于 3D 游戏的组件)。
选择要展开的图像
输入图像描述 (可选)

6.使用 Save 按钮来保存预制件,然后返回到场景
7.单击 Play 以进入运行模式。糟糕,你的角色刚刚掉出了屏幕! 8. 再次单击 Play 以退出运行模式

3.禁用重力

Ruby 掉落的原因是刚体会将重力施加到游戏对象(默认情况下设置为沿 y 轴向下),所以游戏对象会掉落。但在此示例中,这不是你想要的效果。你的游戏具有“自上而下”视图,这意味着一切都在地面上,屏幕的底部不是你的世界的“下方”。
幸好,你可以禁用重力: 1. Hierarchy 中,选择 Ruby 游戏对象2.Inspector 中,找到 Rigidbody 2D 组件。 3.找到 Gravity Scale 属性并将其设置为 0
选择要展开的图像
输入图像描述 (可选)

现在,你可以单击 Play 以测试更改,这次 Ruby 不再掉落!

4.为 Ruby 预制件禁用重力

请注意 Gravity Scale 属性现在是如何显示的:粗体,旁边有一条小蓝线。这是因为你在场景中的 Ruby 上(而不是在预制件上)更改了这个属性!(你没有进入预制件模式)。
如果你将另一个 Ruby 添加到场景中,新 Ruby 仍然会将 Gravity Scale 设置为 1。这样的行为称为覆盖,让你可以对场景中的单个预制件实例(而不是对预制件的所有实例)进行修改。 “覆盖”还始终优先于预制件值,所以如果你编辑预制件并将 Gravity Scale 更改为 0.5,则所有现存的 Ruby 都会将自己的 Gravity Scale 设置为 0.5(但这个 Ruby 除外,因为它的覆盖已将该属性设置为 0)。
但是,在此示例中,你希望对预制件进行此修改: 1. Inspector 中,查看 Ruby 游戏对象标题。 2.单击 Overrides 下拉菜单。随即将出现一个对话窗口,其中列出与预制件相比已进行更改的组件(在本例中为 Rigidbody 2D)。
选择要展开的图像
输入图像描述 (可选)

3.单击 Apply All 以便将更改应用于预制件。 现在,如果你要将新 Ruby 添加到场景中,则该游戏对象的 Gravity Scale 会设置为 0

5.什么是碰撞体?

现在,物理系统已经识别你的游戏对象(由于刚体),接下来你需要告诉物理系统,该游戏对象的哪一部分是“实心的”。此操作是通过碰撞体完成的。
碰撞体是简单的形状(例如正方形或圆形),物理系统将这样的形状作为游戏对象的近似形状来进行碰撞计算。

6.向游戏对象添加碰撞体

让我们从 Ruby 游戏对象开始: 1. 预制件模式下打开 Ruby 预制件2.Inspector 中,单击 Add Component3.搜索“Box Collider 2D”,然后添加此组件。 注意:再次提醒,务必使用此组件的 2D 版本4.现在,你会在 Scene 视图中看到 Ruby 周围有绿色的轮廓线:
选择要展开的图像
输入图像描述 (可选)

这就是碰撞体的形状,现在物理系统会将这个形状作为 Ruby 的形状。
5.保存预制件6.现在,为 MetalBox 预制件执行同样的操作:
  • 双击预制件以将其打开。
  • 添加 Box Collider 2D 组件。
  • 保存预制件
  • 退出预制件模式
注意:你尚未将刚体添加到箱子;这是正确的。这是因为不需要通过物理来移动箱子,只需要一个碰撞体即可,无论有没有刚体游戏对象都将与箱子交互。
7.现在,按 Play,然后尝试让 Ruby 穿过箱子以及在箱子周围移动。
选择要展开的图像
输入图像描述 (可选)

嗯...还是有点问题。角色会抖动并旋转!

7.解决 Ruby 的旋转问题

首先,让我们处理旋转问题。为此,你需要告知 物理系统不要旋转游戏对象。这在“真实”的物理中也许是可行的,但在此 2D 游戏中不起作用。 幸好,Rigidbody 2D 组件具有与此相关的设置:
1.确认 Ruby 已在预制件模式下打开。

2.Inspector 中,找到 Rigidbody 2D 组件。 3.单击 Constraints 旁边的小箭头以展开该部分。 4.启用 Freeze Rotation 复选框以确保刚体不会向 Ruby 添加任何旋转。
选择要展开的图像
输入图像描述 (可选)

提示:如果你在场景中(而不是在预制件上)对 Ruby 实例进行此更改,请使用 Overrides 下拉菜单将更改应用于你的预制件。
现在,你可以解决 Ruby 的抖动问题。

8.为什么 Ruby 会抖动?

发生抖动是因为 物理系统使用的是场景的简化副本,这个副本中仅包含碰撞体
物理场景可以使物理系统的计算更简化,但是物理系统需要:
  • 每当带有刚体游戏对象场景中移动时,在物理场景中移动自己的游戏对象副本。
  • 施加作用力并计算碰撞。
  • 场景中的游戏对象移动到物理场景中计算出的新位置。
在此示例中,这会导致以下事件:
  • 你在帧更新过程中移动角色。
  • 物理系统将自己的游戏对象副本移到相应的新位置。
  • 物理系统发现角色碰撞体现在位于另一个碰撞体(此处为箱子)内,然后将角色碰撞体移回以便不再位于箱子内。
  • 物理系统Ruby 游戏对象与该新位置同步。
你不断在箱子内移动 Ruby,而物理系统则将她移回。你要求代码执行的操作与物理系统执行的操作之间的这种冲突就会导致发生抖动。

9.解决 Ruby 的抖动问题

要解决 Ruby 的抖动问题,你需要移动刚体本身而不是游戏对象变换组件,并让物理系统游戏对象位置同步到刚体位置。这样,物理系统就可以在进入箱子之前停止移动,而不必在 Ruby 已经进入箱子之后再移动 Ruby。
为此,需要修改 Ruby Controller 代码
1.双击 RubyController 脚本以将其打开。你的脚本应如下所示:
public class RubyController: MonoBehaviour { // 在第一次帧更新之前调用 Start void Start() { } // 每帧调用一次 Update void Update() { float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); Vector2 position = transform.position; position.x = position.x + 3.0f* horizontal * Time.deltaTime; position.y = position.y + 3.0f * vertical * Time.deltaTime; transform.position = position; } }
2. 因为默认情况下,所有游戏对象都具有一个 Transform 组件,所以 Unity 使 transform 变量在所有脚本中都可用。但是,必须将 Rigidbody 组件手动添加到游戏对象,因此 Unity 并没有将这个变量作为内置变量。
3.  除此之外,物理系统的更新速度与游戏不同。每次游戏计算新图像时都会调用 Update,问题是调用速度不确定。在一台速度较慢的计算机上,调用速度可能是每秒 20 张图像,而在一台非常快的计算机上,调用速度可能是每秒 3000 张图像。 为了使物理计算保持稳定,需要定期进行更新(例如,每隔 16ms)。Unity 还有另一个名为 FixedUpdate 的函数,只要你想直接影响物理组件或对象(例如刚体),就需要使用该函数。
但是,你不应该读取 Fixedupdate 函数中的输入。FixedUpdate 不会持续运行,因此有可能会错过用户输入。你需要在类中添加两个浮点变量,以便在 Update 函数内存储当前的水平和垂直输入数据。
如下所示调整 RubyController 脚本: 
public class RubyController : MonoBehaviour { Rigidbody2D rigidbody2d; float horizontal; float vertical; // 在第一次帧更新之前调用 Start void Start() { rigidbody2d = GetComponent<Rigidbody2D>(); } // 每帧调用一次 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); } }

10.审查你的更改

让我们来看看新的和调整的每行代码:
  • Rigidbody2D rigidbody2d; 此行代码将创建一个新变量(名为 rigidbody2d)来存储刚体并从脚本中的任何位置访问刚体。
  • float horizontal; float vertical; 这两行代码将创建两个新变量来存储输入数据。这些变量曾在 Update 函数中声明过,但是由于你现在需要从另一个函数 (FixedUpdate) 访问这些变量,因此在此处又声明了这些变量。
  • rigidbody2d = GetComponent<Rigidbody2D>(); 此代码位于 Start 函数内,因此在游戏开始时仅执行一次。此代码要求 Unity 向你提供与脚本附加到同一游戏对象(即你的角色)上的 Rigidbody2D。
  • Update 函数中,你删除了与移动相关的所有代码。你保留了用于读取输入的代码,这次是位于先前声明的两个变量中。
  • Vector2 position = rigidbody2d.position; 在 FixedUpdate 函数中,你添加了曾位于 Update 函数中的代码行,并调整了此代码以使用刚体位置。
  • rigidbody2d.MovePosition(position); 同样,你现在使用刚体位置,而不是使用 transform.position = position; 来设置新位置。这行代码会将刚体移动到你想要的位置,但是如果刚体在移动中与另一个碰撞体碰撞,则会中途停止刚体。
现在选择 Play时,你会看到你的角色已停止抖动!
但是 Ruby 仍然停得太早,而且看起来一点也不合理。这是因为碰撞体的大小未正确适应图像。

11.调整碰撞体的大小

我们在本教程前面探讨过,物理系统通过游戏对象的碰撞体来估算游戏对象形状。 如果在 Scene 视图中查看,则会看到箱子和 Ruby碰撞体远远超出你认为的游戏对象“实体”部分范围。
选择此箱子Ruby 后,你将看到周围出现绿色矩形。 例如,箱子的阴影在碰撞体内部,因此物理系统将此处视为实心。
选择要展开的图像
输入图像描述 (可选)

要调整碰撞体大小,请执行以下操作: 1. 双击 MetalBox 预制件(或者在 Hierarchy 中单击 MetalBox 右侧的箭头)以进入预制件模式2.Inspector 中,找到 Box Collider 组件
3. 单击 Edit Collider 按钮。
选择要展开的图像
输入图像描述 (可选)

4. 单击 Edit Collider 时,碰撞体Scene 视图中会变化为在侧面显示四个小方块。可以单击并拖动小方块来调整碰撞体的大小。
如果因误单击而导致小点消失,则只需返回到 Inspector 中并重新单击 Edit Collider 按钮。 调整碰撞体的大小,使结果如下所示:
选择要展开的图像
输入图像描述 (可选)

为什么不把碰撞体设置成箱子的大小?那是因为你的角色需要能够走到箱子“后面”,这样看起来更合理(否则箱子在地面上看起来是扁平的)。
5.保存所做的更改。 6.Ruby 游戏对象完成相同过程,调整该游戏对象的碰撞体大小,使结果如下所示:
选择要展开的图像
输入图像描述 (可选)

碰撞体仅覆盖了 Ruby 的双腿,因为角色在碰撞之前需要能够稍微移到游戏对象的上方,这有助于使游戏更真实可信。
7.保存所做的更改。 8. 单击 Play,然后尝试让 Ruby 朝着和围绕着箱子移动。现在的结果应该看起来合理,行为表现也符合你的预期。(完成后,务必再次按 Play 以退出运行模式。)
你可以尝试调整这两个游戏对象碰撞体的大小,然后找出最符合你需求的形状。
你还可以返回到先前教程中制作的所有预制件,并向这些预制件添加盒型碰撞体,使 Ruby 无法穿过这些预制件!由于是预制件,因此只要你添加碰撞体并在预制件模式下调整大小,便会向已放置在场景中的所有实例添加碰撞体。 不要过分热衷于此,像渠盖这样的游戏对象可能不需要碰撞体,因为 Ruby 应该能够在此类游戏对象上行走!

12.添加瓦片地图碰撞

现在,你的角色会与我们具有碰撞体的所有游戏对象碰撞。但是她仍然可以在水上行走,因此你应该让她与水面瓦片碰撞,使她不能在水上行走。但如何才能做到呢?
你可以在空游戏对象上添加一个碰撞体,然后调整碰撞体大小以涵盖水面。但这个过程容易出错,如果你想将水面重新绘制得更大、更小或绘成其他形状,则必须手动更改碰撞体。请注意,这种情况下通过瓦片地图可以轻松快速地更改世界。
幸好,瓦片地图也可以具有碰撞体。无论是否应该碰撞,每个瓦片都可以存储起来,并且瓦片地图碰撞体将为所有要设置以进行碰撞的瓦片创建碰撞体
要设置瓦片地图碰撞体,请执行以下操作: 1. Hierarchy 中,选择 Tilemap 游戏对象2.Inspector 中,单击 Add Component 按钮。 3. 搜索“Tilemap Collider 2D”,然后选择此组件。你会看到在 Scene 视图中为所有瓦片添加绿色碰撞体方块:
选择要展开的图像
输入图像描述 (可选)

这是因为现在所有的瓦片都已设置为进行碰撞。 4.Project 窗口中,找到 Tile 文件夹。选择所有不是水的瓦片。(你可以单击一个瓦片,然后按住 Shift 并单击列表中的最后一个瓦片来全部选中。)
选择要展开的图像
输入图像描述 (可选)

5.在 Inspector 中,找到 Collider Type 属性,然后将该属性从 Sprite(目前值)更改为 None
选择要展开的图像
输入图像描述 (可选)

现在,你选择的瓦片不再被视为碰撞体。如果在 Hierarchy 中选择 Tilemap,你将在 Scene 视图中看到只有水面瓦片具有绿色方块。
选择要展开的图像
输入图像描述 (可选)

6.保存所做的更改。 7. 单击 Play 以进入运行模式,并尝试让 Ruby 在水上行走。她现在应该会与边界发生碰撞。

13.优化瓦片地图碰撞体

最后一小步是设置瓦片地图碰撞体。目前,正如在 Scene 视图中看到的,每个瓦片都是一个单独的碰撞体。 这种方法效果良好,但会产生两个问题:
  • 物理系统的计算量更大;如果你的世界很大,可能会减慢你的游戏速度。
  • 在瓦片之间的边界上会产生小问题。由于瓦片是两个并排的碰撞体,并且两者之间存在微小间隙,因此有时计算上的微小误差也可能导致仍会发生碰撞的罕见情况。
为了解决这些问题,Unity 提供了一个名为 Composite Collider 2D 的组件。此组件可以获取对象(或对象的子对象)上的所有碰撞体,并由此创建一个大碰撞体。 让我们来添加并配置此组件: 1. Hierarchy 中,选择 Tilemap 游戏对象2.Inspector 中,单击 Add Component 按钮。 3. 搜索“Composite Collider 2D”,然后选择此组件。
你会看到自动添加 Rigidbody 2D 组件,因为复合碰撞体需要 Rigidbody 2D 才能正常运行。
4.Tilemap Collider 2D 组件中,启用 Used By Composite 复选框。 5.Rigidbody 2D 组件中,将 Rigidbody Body Type 属性设置为 Static。 将此属性设置为 Static 将阻止你的世界移动。此外还有助于物理系统优化计算,因为它现在知道刚体不能移动。
选择要展开的图像
输入图像描述 (可选)


现在,围绕水面瓦片的碰撞体是一个大矩形(你可以在瓦片地图碰撞体 (Tilemap Collider) 上启用和禁用 Used by Composite 以便在 Scene 视图中进行比较)。
如果你绘制新的水面瓦片Unity 会自动更新瓦片地图复合碰撞体 (Composite Collider) 以便包含新的碰撞体

14.检查你的脚本

你的 RubyController 脚本现在应如下所示:
public class RubyController : MonoBehaviour { Rigidbody2D rigidbody2d; float horizontal; float vertical; // 在第一次帧更新之前调用 Start void Start() { rigidbody2d = GetComponent<Rigidbody2D>(); } // 每帧调用一次 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); } }

15.总结

在本教程中,你已经:
  • 探索了 Unity 中的物理系统的基础知识
  • 添加了 Rigidbody 组件 来让物理系统处理对象
  • 添加了碰撞体以使对象碰撞在一起
在下一教程中,你将在更大范围内使用物理系统来检测角色与游戏对象(例如,可收集的生命值包)的碰撞。

项目:
Ruby's Adventure:2D 初学者
世界交互 - 阻止移动
世界交互 - 阻止移动
一般教程讨论
0
1
1. 什么是物理系统?
0
0
2. 添加 Rigidbody 2D 组件
0
0
3. 禁用重力
0
0
4. 为 Ruby 预制件禁用重力
0
0
5. 什么是碰撞体?
0
0
6. 向游戏对象添加碰撞体
0
0
7. 解决 Ruby 的旋转问题
0
0
8. 为什么 Ruby 会抖动?
0
0
9. 解决 Ruby 的抖动问题
1
1
10. 审查你的更改
0
0
11. 调整碰撞体的大小
0
0
12. 添加瓦片地图碰撞
0
0
13. 优化瓦片地图碰撞体
0
0
14. 检查你的脚本
0
0
15. 总结
0
0