新增 WallHangSpeed = 1f:正常抓墙(低于等于 wallGrabY,可蹬墙跳区间)的缓慢下滑速度 WallSlideSpeed 语义调整为:受限模式(高于 wallGrabY)的较快下滑速度 PlayerMovement.cs ApplyWallSlide() 改为 ApplyWallSlide(float speed),由调用方传入对应速度 WallSlideState.cs OnStateFixedUpdate:正常模式用 WallHangSpeed,受限模式用 WallSlideSpeed(两档清晰分离) 恢复反方向键脱离:脱离时同样调用 StartWallCoyote,wall coyote 窗口内仍能触发蹬墙跳 更新类头注释完整描述脱离方式和下滑速度档位Two wall slide improvements:
236 lines
12 KiB
Markdown
236 lines
12 KiB
Markdown
# Unity Layer 命名与用途规范
|
||
|
||
> **版本**:1.0
|
||
> **创建日期**:2026-05
|
||
> **适用范围**:项目所有 GameObject 的 Layer 分配
|
||
> **关联文档**:`Standards/AssetFolderSpec.md`、`Editor/Tools/Physics2DLayerReport.cs`
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [概述](#1-概述)
|
||
2. [Layer 清单](#2-layer-清单)
|
||
3. [Physics 2D 碰撞矩阵](#3-physics-2d-碰撞矩阵)
|
||
4. [使用规范](#4-使用规范)
|
||
5. [检查与修复工具](#5-检查与修复工具)
|
||
|
||
---
|
||
|
||
## 1. 概述
|
||
|
||
本项目使用固定的 Layer 名称集合来驱动 Physics 2D 碰撞矩阵、伤害判定、视线检测(LOS)、地面检测等核心系统。所有 Layer 必须在 **Edit → Project Settings → Tags and Layers** 中手动创建,名称必须与本文档一致(大小写敏感)。
|
||
|
||
> **重要**:代码中通过 `LayerMask.NameToLayer("LayerName")` 引用 Layer,若 Layer 不存在将返回 `-1` 并导致逻辑错误。创建场景对象时请优先使用 `BaseGames → Scene → Place` 菜单工具,该工具会自动校验并赋值 Layer。
|
||
|
||
---
|
||
|
||
## 2. Layer 清单
|
||
|
||
### 2.1 玩家相关
|
||
|
||
| Layer 名称 | 挂载对象 | 用途说明 |
|
||
|---|---|---|
|
||
| `Player` | 玩家根节点 GameObject | 玩家物理碰撞体(含 Rigidbody2D);地面检测、存档点、检查点触发器通过此 Layer 筛选玩家 |
|
||
| `PlayerHurtBox` | 玩家子节点 `HurtBox` | 接收伤害的碰撞体(isTrigger);`EnemyHitBox` / `EnemyProjectile` 命中此 Layer 时对玩家造成伤害 |
|
||
| `PlayerHitBox` | 玩家武器 / 技能 HitBox | 发出伤害的碰撞体;命中 `EnemyHurtBox` 时触发对敌人的伤害判定 |
|
||
| `PlayerProjectile` | 玩家发射的投射物 | 玩家子弹;不与 `PlayerHurtBox`、`EnemyProjectile` 碰撞,命中 `EnemyHurtBox` 或 `Platform` 时有效 |
|
||
|
||
### 2.2 敌人相关
|
||
|
||
| Layer 名称 | 挂载对象 | 用途说明 |
|
||
|---|---|---|
|
||
| `Enemy` | 敌人根节点 GameObject | 敌人物理碰撞体(含 Rigidbody2D);与 `Platform` 碰撞实现站立 |
|
||
| `EnemyHurtBox` | 敌人子节点 `HurtBox` | 接收伤害的碰撞体(isTrigger);`PlayerHitBox` / `PlayerProjectile` 命中此 Layer 时对敌人造成伤害;`EnemyHitBox` 也可命中此 Layer(敌人互伤,HitBox 运行时排除自身根节点) |
|
||
| `EnemyHitBox` | 敌人武器 HitBox / `LethalTrap` 主体 | 发出伤害的碰撞体;命中 `PlayerHurtBox` 时触发对玩家的伤害判定 |
|
||
| `EnemyProjectile` | 敌人发射的投射物 | 敌人子弹;不与 `EnemyHurtBox` 碰撞,命中 `PlayerHurtBox` 或 `Platform` 时有效 |
|
||
|
||
### 2.3 地形相关
|
||
|
||
| Layer 名称 | 挂载对象 | 用途说明 |
|
||
|---|---|---|
|
||
| `Platform` | 静态地面、移动平台、Tilemap 地形、障碍物 | 玩家与敌人站立的实体平台;`PlayerMovement` 的 `_groundLayer`、`PlayerWallDetector` 的 `_wallLayer`(默认)均包含此 Layer;投射物命中后销毁 |
|
||
| `OneWayPlatform` | 单向平台(静态) | 玩家可从下方穿过、从上方站立的平台;挂载 `PhantomPlate`(`PlatformEffector2D` + `IDropThrough`);`_groundLayer` 必须包含此 Layer 才能检测 `OnOneWayPlatform` 状态 |
|
||
| `MovingOneWayPlatform` | 单向平台(移动) | 会移动的单向平台;行为同 `OneWayPlatform`,独立层便于移动脚本、AI 路径查询按层筛选 |
|
||
| `MidHeightOneWayPlatform` | 单向平台(半高) | 半腰高度的单向平台;角色可从侧方穿越;行为同 `OneWayPlatform` |
|
||
| `Wall` | 墙壁碰撞体(垂直面) | 玩家攀墙检测(`PlayerWallDetector` 默认掩码包含 `Wall` 和 `Platform`);若地形使用统一的 Tilemap 则墙壁可合并到 `Platform` Layer |
|
||
|
||
### 2.4 触发区域
|
||
|
||
| Layer 名称 | 挂载对象 | 用途说明 |
|
||
|---|---|---|
|
||
| `TriggerZone` | 存档点(`CheckpointMarker`)、存档台(`SavePoint`)、传送站(`TeleportStation`)、房间过渡(`RoomTransition` / `DoorTransition`)、相机区域(`CameraArea`) | 纯触发碰撞体(isTrigger),不参与物理阻挡;统一使用此 Layer 方便在碰撞矩阵中集中管理 |
|
||
|
||
### 2.5 环境危险
|
||
|
||
| Layer 名称 | 挂载对象 | 用途说明 |
|
||
|---|---|---|
|
||
| `HazardHitBox` | 对双方均造成伤害的环境危险(落石、熔岩、毒区、AOE 爆炸范围等) | 同时与 `PlayerHurtBox` 和 `EnemyHurtBox` 碰撞;区别于 `EnemyHitBox`(只对玩家),此 Layer 用于阵营中立的环境伤害源 |
|
||
|
||
### 2.6 特殊用途
|
||
|
||
| Layer 名称 | 挂载对象 | 用途说明 |
|
||
|---|---|---|
|
||
| `PhantomBody` | 幽灵/幻象角色的实体碰撞体 | `PhantomInteractable` 通过 `other.gameObject.layer == PhantomBody` 判断是否为幻象角色触发交互 |
|
||
|
||
---
|
||
|
||
## 3. Physics 2D 碰撞矩阵
|
||
|
||
以下为项目**期望的碰撞配置**,由 `Physics2DLayerReport` 工具维护与校验。
|
||
|
||
| Layer A | Layer B | 应碰撞 | 说明 |
|
||
|---|---|:---:|---|
|
||
| `PlayerHitBox` | `EnemyHurtBox` | ✅ | 玩家攻击伤害敌人 |
|
||
| `EnemyHitBox` | `PlayerHurtBox` | ✅ | 敌人攻击伤害玩家 |
|
||
| `EnemyHitBox` | `EnemyHurtBox` | ✅ | 敌人可互相伤害(HitBox 运行时排除自身根节点) |
|
||
| `Player` | `Platform` | ✅ | 玩家站在实体平台上 |
|
||
| `Player` | `OneWayPlatform` | ✅ | 玩家站在单向平台上(PlatformEffector2D 控制单向穿透) |
|
||
| `Player` | `MovingOneWayPlatform` | ✅ | 玩家站在移动单向平台上 |
|
||
| `Player` | `MidHeightOneWayPlatform` | ✅ | 玩家站在半高单向平台上 |
|
||
| `Enemy` | `Platform` | ✅ | 敌人站在实体平台上 |
|
||
| `Enemy` | `OneWayPlatform` | ✅ | 敌人站在单向平台上 |
|
||
| `Enemy` | `MovingOneWayPlatform` | ✅ | 敌人站在移动单向平台上 |
|
||
| `Enemy` | `MidHeightOneWayPlatform` | ✅ | 敌人站在半高单向平台上 |
|
||
| `PlayerProjectile` | `EnemyHurtBox` | ✅ | 玩家投射物伤害敌人 |
|
||
| `PlayerProjectile` | `PlayerHurtBox` | ❌ | 玩家投射物不自伤 |
|
||
| `PlayerProjectile` | `Platform` | ✅ | 玩家投射物命中地形 |
|
||
| `EnemyProjectile` | `PlayerHurtBox` | ✅ | 敌人投射物伤害玩家 |
|
||
| `EnemyProjectile` | `EnemyHurtBox` | ❌ | 敌人投射物不自伤 |
|
||
| `EnemyProjectile` | `Platform` | ✅ | 敌人投射物命中地形 |
|
||
| `PlayerHitBox` | `PlayerHurtBox` | ❌ | 玩家不自伤 |
|
||
| `PlayerProjectile` | `EnemyProjectile` | ❌ | 子弹不互相碰撞(Clash 系统单独处理) |
|
||
| `HazardHitBox` | `PlayerHurtBox` | ✅ | 环境危险伤害玩家 |
|
||
| `HazardHitBox` | `EnemyHurtBox` | ✅ | 环境危险伤害敌人(中立伤害) |
|
||
| `HazardHitBox` | `PlayerHitBox` | ❌ | 环境不触发拼刀 |
|
||
| `HazardHitBox` | `EnemyHitBox` | ❌ | 环境不触发拼刀 |
|
||
|
||
> 未在上表中列出的 Layer 对默认继承 Unity 全局设置(默认全部碰撞)。
|
||
|
||
---
|
||
|
||
## 4. 使用规范
|
||
|
||
### 4.1 Layer 赋值方式
|
||
|
||
- **优先使用 `SceneObjectPlacerTool`**(菜单 `BaseGames → Scene → Place → …`)自动赋值,避免遗漏。
|
||
- 手动创建 GameObject 时,在 Inspector 顶部 **Layer** 下拉菜单中选择对应 Layer。
|
||
- 代码中引用时,通过 `LayerMask.NameToLayer("LayerName")` 获取整型索引,或通过 `LayerMask.GetMask("LayerA", "LayerB")` 获取掩码值。
|
||
|
||
```csharp
|
||
// 推荐:缓存到字段,避免每帧调用
|
||
private int _playerLayer;
|
||
|
||
void Awake()
|
||
{
|
||
_playerLayer = LayerMask.NameToLayer("Player");
|
||
}
|
||
```
|
||
|
||
### 4.2 子节点继承规则
|
||
|
||
- 玩家 / 敌人根节点与其 HurtBox、HitBox **必须分别挂不同 Layer**,不可统一使用根节点 Layer。
|
||
- `LethalTrap`(陷阱):主体用 `EnemyHitBox`,可弹起的子 `HurtBox` 用 `EnemyHurtBox`。
|
||
- 触发区域对象(存档点等)统一用 `TriggerZone`,不需要额外子节点分层。
|
||
|
||
### 4.3 禁止事项
|
||
|
||
- 禁止在代码中硬编码 Layer 整型编号(如 `layer == 8`),必须使用名称转换。
|
||
- 禁止直接修改 `ProjectSettings/TagManager.asset` 中的 Layer 顺序(会导致所有已赋值对象 Layer 错乱)。
|
||
- 禁止将 `TriggerZone` 对象的 Collider 设置为非 trigger(`isTrigger` 必须为 `true`)。
|
||
|
||
---
|
||
|
||
## 6. 复杂场景处理模式
|
||
|
||
### 6.1 弹反投射物(EnemyProjectile → 伤害敌人)
|
||
|
||
**问题**:`EnemyProjectile ↔ EnemyHurtBox = 不碰撞`,弹反后的投射物无法对敌人造成伤害。
|
||
|
||
**正确方案:运行时 Layer 切换**(不新增 Layer)
|
||
|
||
弹反成功时,将投射物的 Layer 从 `EnemyProjectile` 切换为 `PlayerProjectile`,同时反转飞行方向。`Projectile` 已有 `SetFactionLayer(int ownerLayer)` 方法,弹反逻辑只需调用它:
|
||
|
||
```csharp
|
||
// HurtBox.ReceiveDamage() 弹反成功后(步骤2)
|
||
if (_parrySystem.ConsumeParry())
|
||
{
|
||
// 若攻击来源是投射物,翻转其阵营 Layer
|
||
if (info.SourceProjectile != null)
|
||
{
|
||
info.SourceProjectile.ReflectAsPlayerProjectile();
|
||
// ReflectAsPlayerProjectile() 内部:
|
||
// gameObject.layer = LayerMask.NameToLayer("PlayerProjectile");
|
||
// rb.velocity = -rb.velocity; // 反向
|
||
}
|
||
return;
|
||
}
|
||
```
|
||
|
||
> **注意**:当前 `DamageInfo` 尚未携带 `SourceProjectile` 字段,实现此功能需要:
|
||
> 1. 在 `DamageInfo` 中添加 `Projectile SourceProjectile` 字段
|
||
> 2. `HitBox.OnTriggerEnter2D` 通过 `_attackerTransform.GetComponent<Projectile>()` 填入
|
||
> 3. `Projectile` 实现 `ReflectAsPlayerProjectile()` 方法
|
||
|
||
**为什么不新建 `ParriedProjectile` 层?**
|
||
`PlayerProjectile` 的语义本就是"对敌人有效、对玩家无效"的攻击来源,弹反后的投射物完全符合此语义,无需新层。
|
||
|
||
---
|
||
|
||
### 6.2 环境伤害(同时对玩家与敌人有效)
|
||
|
||
`LethalTrap`(陷阱)当前使用 `EnemyHitBox` 层,只对玩家生效,是**有意为之**的设计(跑酷陷阱不伤敌人)。
|
||
|
||
对于确实需要**同时伤害双方**的环境机关(落石、熔岩、AOE 区域等),使用 `HazardHitBox` 层并配合 `HazardHitBoxTrigger` 组件(待实现),收到碰撞时向 `PlayerHurtBox` 和 `EnemyHurtBox` 均发送伤害:
|
||
|
||
```
|
||
HazardHitBox ↔ PlayerHurtBox → 碰撞
|
||
HazardHitBox ↔ EnemyHurtBox → 碰撞
|
||
```
|
||
|
||
---
|
||
|
||
### 6.3 敌人之间互伤(友伤/AOE)
|
||
|
||
`EnemyHitBox ↔ EnemyHurtBox = 碰撞` 已在碰撞矩阵中开启。
|
||
自伤防护由 `HitBox.OnTriggerEnter2D` 的根节点比较负责:
|
||
|
||
```csharp
|
||
if (other.transform.root == _attackerTransform.root) return; // 排除自身
|
||
```
|
||
|
||
这意味着:
|
||
- 同一 GameObject 树的 EnemyHitBox 不会命中自身的 EnemyHurtBox ✅
|
||
- 敌人 A 的 HitBox 可以命中敌人 B 的 HurtBox ✅(天然支持友伤)
|
||
- Boss 的 AOE 技能可以对场景中所有其他敌人造成伤害 ✅
|
||
|
||
---
|
||
|
||
### 6.4 不需要细分的场景
|
||
|
||
以下场景**不需要新增 Layer**,通过 `DamageInfo` 字段在逻辑层区分即可:
|
||
|
||
| 场景 | 处理方式 |
|
||
|---|---|
|
||
| 区分攻击来源(玩家技能 vs 玩家普攻) | `DamageInfo.SkillId` / `DamageInfo.SourceId` |
|
||
| 不同类型伤害(物理 vs 魔法) | `DamageInfo.Type`(`DamageType` 枚举) |
|
||
| Boss 阶段专属伤害规则 | `DamageInfo.Tags`(`DamageTags` 标记位) |
|
||
| 不可弹反的攻击 | `DamageInfo.Flags` 不含 `CanBeParried` |
|
||
| 穿透无敌帧的伤害 | `DamageInfo.Flags` 含 `IgnoreIFrame` |
|
||
|
||
|
||
项目提供了 **Physics2DLayerReport** 编辑器工具,位于菜单:
|
||
|
||
```
|
||
BaseGames → Tools → Physics2D Layer Matrix → Check
|
||
BaseGames → Tools → Physics2D Layer Matrix → Auto Fix
|
||
```
|
||
|
||
- **Check**:打印当前碰撞矩阵与期望配置的对比报告。
|
||
- ✅ 正常
|
||
- ❌ 当前配置与期望不符(需修复)
|
||
- ⚠ Layer 不存在(需先在 Tags and Layers 中创建)
|
||
- **Auto Fix**:自动将所有不符合期望的碰撞对修正为正确值,并持久化到 `ProjectSettings`。
|
||
|
||
> 建议在以下时机运行 **Check**:新人入职初始化环境、合并来自其他分支的 `ProjectSettings` 变更后、新增 Layer 之后。
|