Files
Obsidian_Unity/Unity学习/Unity组件/碰撞器/碰撞器.md
T
2026-05-03 14:06:26 +08:00

5.1 KiB
Raw Blame History

对于碰撞器而言,基本的碰撞器形状,碰撞器的作用不必多谈,这里着重强调的是碰撞器和触发器,以及他们碰撞和触发的三种状态.

Enter Stay Exit

以上三种状态表示了物体碰撞刚刚进入的一帧,物体持续碰撞,和物体离开碰撞的一帧 他们分别对应了代码当中的 OnCollisionEnter OnCollisionStay OnCollisionExit 以及触发器的 OnTriggerEnter OnTriggerStay OnTriggerExit 他们的用法相同,区别在于方法传入的参数不同 碰撞器提供的参数类型是Collision 触发器提供的参数类型是Collider

筛选

我们需要在碰撞器和触发器内部进行许多逻辑上的处理,这里有一个关键是如何筛选物体 比如NPC和玩家同时触发开门触发器,我们只希望玩家触发时才会开门,需要进行筛选. 常见的筛选方法有2种: 签名. 层

签名

由于为我们提供了参数,我们可以获取到触发的物体,所以我们可以使用签名进行筛选 譬如:
if (collision.gameObject.tag==("Player")) {

} if (collision.gameObject.CompareTag("Tag")) {

} 二种方式均可,其中第二种方式的效率更高,因为: Unity将标签单独储存在一个数据结构当中,CompareTag方法可以直接访问该数据结构效率更高.

在Unity当中,层被储存为整数,因此使用层进行筛选的效率比Tag高. 层的筛选也需要分为筛选特定的单层,和多层 比如你需要玩家单独可以开门,那么就可以使用单层筛选 但是如过你想要多个物体都可以开门,那么就需要多层

单层检测

!Pasted image 20241219231058.png

多层检测

!Pasted image 20241219231128.png

1. 关键概念

Layer 和 LayerMask

  • Layer(层) Unity 中的每个 GameObject 都可以设置一个 Layer,用于分组、筛选或逻辑处理。每个层的编号是从 031 的整数。

  • LayerMask(层掩码) LayerMask 是一个 32 位的整型数,每一位(bit)对应一个 Layer。如果某一位是 1,则表示该层被包含在掩码中;如果是 0,则表示不包含。

    例如:

    • 如果 LayerMask 的值是 5(即二进制 00000000 00000000 00000000 00000101),表示包含第 0 层和第 2 层。
    • 具体映射:
      • 第 0 位 = 1 → 包含第 0 层
      • 第 1 位 = 0 → 不包含第 1 层
      • 第 2 位 = 1 → 包含第 2 层

2. 拆解逻辑

(1) collision.gameObject.layer

  • 获取发生碰撞的对象的层编号(一个整数,例如 012 等)。

(2) (1 << collision.gameObject.layer)

  • 位移操作 << 1 << x 表示将 1 左移 x 位。例如:

    • 1 << 000000000 00000000 00000000 00000001 (表示第 0 层)
    • 1 << 200000000 00000000 00000000 00000100 (表示第 2 层)

    结果: 这个操作生成一个位掩码,只有对应层的位置是 1,其余位置是 0

(3) targetLayers | (1 << collision.gameObject.layer)

  • 按位或操作 |targetLayers 和生成的位掩码进行按位或运算。

    • 如果 targetLayers 中已经包含对应的层(对应位置是 1),运算结果不变。
    • 如果 targetLayers 中不包含对应的层(对应位置是 0),运算结果会将该位置变为 1

    例子:

    • targetLayers = 00000000 00000000 00000000 00000101 (包含第 0 层和第 2 层)
    • collision.gameObject.layer = 1
    • 1 << 1 = 00000000 00000000 00000000 00000010 (表示第 1 层)
    • 运算结果:00000000 00000000 00000000 00000111 (包含第 0、1 和 2 层)

(4) 比较:targetLayers == ...

  • 比较 targetLayers 和运算结果是否相等。
    • 如果相等,说明碰撞对象的 Layer 已经在 targetLayers 中。
    • 如果不相等,说明碰撞对象的 Layer 不在 targetLayers 中。

3. 具体判断逻辑

代码的核心逻辑是:

  1. 通过 1 << collision.gameObject.layer 获取碰撞对象的层对应的位掩码。
  2. targetLayers | ... 检查碰撞对象的层是否在 targetLayers 中。
  3. 如果运算结果和 targetLayers 相等,说明碰撞对象的层已经被包含。

简化版本: (targetLayers | (1 << collision.gameObject.layer)) 实际上就是在“试探性”地将碰撞对象的层加入 targetLayers,然后检查是否变化。如果没有变化,说明碰撞对象的层已经在 targetLayers 中。


4. 更直观的判断方式

可以使用 LayerMask 的内置方法 LayerMask.Contains(在较新 Unity 版本中),或者稍微改写代码,提升可读性:

替代写法:

csharp

复制代码

if (((1 << collision.gameObject.layer) & targetLayers) != 0) { // 进行内部逻辑 }

逻辑解析:

  • (1 << collision.gameObject.layer) 生成碰撞对象层的位掩码。
  • &(按位与)检测目标层掩码是否包含该层。
    • 如果结果不为 0,说明目标层掩码包含碰撞对象的层。