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

129 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
对于碰撞器而言,基本的碰撞器形状,碰撞器的作用不必多谈,这里着重强调的是碰撞器和触发器,以及他们碰撞和触发的三种状态.
### 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`,用于分组、筛选或逻辑处理。每个层的编号是从 `0``31` 的整数。
- **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`**
- 获取发生碰撞的对象的层编号(一个整数,例如 `0``1``2` 等)。
#### **(2) `(1 << collision.gameObject.layer)`**
- **位移操作 `<<`** `1 << x` 表示将 `1` 左移 `x` 位。例如:
- `1 << 0``00000000 00000000 00000000 00000001` (表示第 0 层)
- `1 << 2``00000000 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`,说明目标层掩码包含碰撞对象的层。