Files
zeling_v2/Docs/Standards/LayerSpec.md
Joywayer fcd3e2dcdd PlayerMovementConfigSO.cs
新增 WallHangSpeed = 1f:正常抓墙(低于等于 wallGrabY,可蹬墙跳区间)的缓慢下滑速度
WallSlideSpeed 语义调整为:受限模式(高于 wallGrabY)的较快下滑速度
PlayerMovement.cs

ApplyWallSlide() 改为 ApplyWallSlide(float speed),由调用方传入对应速度
WallSlideState.cs

OnStateFixedUpdate:正常模式用 WallHangSpeed,受限模式用 WallSlideSpeed(两档清晰分离)
恢复反方向键脱离:脱离时同样调用 StartWallCoyote,wall coyote 窗口内仍能触发蹬墙跳
更新类头注释完整描述脱离方式和下滑速度档位Two wall slide improvements:
2026-05-21 15:58:46 +08:00

236 lines
12 KiB
Markdown
Raw Permalink 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.
# 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 之后。