摄像机区域的架构改动

This commit is contained in:
2026-05-15 14:47:24 +08:00
parent 1b37297585
commit f264329751
3591 changed files with 1687228 additions and 446503 deletions

View File

@@ -0,0 +1,472 @@
# 手动测试 12 · 区域相机系统
> **测试类型**Unity Editor 手动测试Play Mode
> **覆盖模块**`CameraArea`、`CameraTriggerZone`、`CameraStateController`、`CinemachineConfiner2D`、`CameraBlendProfileSO`
> **前置文档**`Phase1_Verification_Guide.md` §1验证前准备
---
## 快速工具
| 工具 | 用途 | 菜单路径 |
|------|------|----------|
| **Camera Area Setup窗口** | 扫描场景中所有 CameraArea/TriggerZone/Controller显示绑定状态提供一键修复 | `BaseGames → Camera → Camera Area Setup` |
| **Place Camera Area** | 生成 CameraArea 节点(含 PolygonCollider2D 限位边界) | `BaseGames → Scene → Place → Camera Area` |
| **Place Camera Trigger Zone** | 生成 CameraTriggerZone + BoxCollider2D Trigger | `BaseGames → Scene → Place → Camera Trigger Zone` |
**典型工作流**
1. 在 Persistent 场景中放置两台全局虚拟相机(`VCamA` / `VCamB`),绑定到 `CameraStateController._vcamA/_vcamB`
2. 在关卡场景中使用 **Place Camera Area** 为每个相机区域放置 `CameraArea` 数据节点(一个房间可放多个)。
3. 选中 `CameraArea`,在 Scene 视图中拖拽**黄色可视区域**的边 Handle 调整可见范围,然后点击 Inspector 底部 **「从可视区域更新限位区域(透视)」** 自动换算限位多边形。
4. 使用 **Place Camera Trigger Zone** 在区域入口放置触发器,并将目标 `CameraArea` 拖入 `_targetArea`
5. 打开 **Camera Area Setup** 窗口,点击 **为全局 VCam 赋值 Follow 目标**(会自动在 Player 下创建或复用 `CameraFollowTarget` 子节点并绑定)。
6. 所有条目显示绿色 ● → 进入 Play Mode 验证。
---
## 目录
1. [系统架构说明](#1-系统架构说明)
2. [Persistent 场景配置CameraStateController](#2-persistent-场景配置cameraStatecontroller)
3. [关卡场景配置CameraArea + CameraTriggerZone](#3-关卡场景配置cameraarea--cameratriggerzone)
4. [ScriptableObject 资产说明](#4-scriptableobject-资产说明)
5. [Camera Area Setup 工具详解](#5-camera-area-setup-工具详解)
6. [验收测试用例](#6-验收测试用例)
7. [常见问题排查](#7-常见问题排查)
---
## 1. 系统架构说明
```
Persistent.unity
└── [Camera]
└── CameraStateController ExecutionOrder = -100
组件: CameraStateController
│ _vcamA → VCamA全局虚拟相机 A
│ _vcamB → VCamB全局虚拟相机 B
组件: CinemachineBrain ← 实际渲染相机(随 Main Camera 放置)
组件: CinemachineImpulseSource ← 屏幕抖动信号源
├── VCamA
│ 组件: CinemachineCamera Follow = Player/CameraFollowTarget
│ 组件: CinemachineConfiner2D ← 由 CameraStateController 动态更新 BoundingShape2D
└── VCamB
组件: CinemachineCamera Follow = Player/CameraFollowTarget
组件: CinemachineConfiner2D ← 由 CameraStateController 动态更新 BoundingShape2D
Level_01.unity
├── [CameraAreas]
│ ├── CameraArea_A 区域 A可在同一房间内放置多个
│ │ 组件: CameraArea _confinerCollider → PolygonCollider2D
│ │ │ _visibleBounds可视矩形
│ │ │ _blendProfile → CameraBlendProfileSO可选
│ │ │ _dedicatedCamera专有 VCam可选priority > 全局)
│ │ └── PolygonCollider2D 定义该区域的相机限位边界
│ │
│ └── CameraArea_B 区域 B
│ ...(同上)
└── [Triggers]
├── CameraTriggerZone_AB 区域 A→B 入口
│ 组件: CameraTriggerZone _targetArea = CameraArea_B
│ 组件: BoxCollider2D isTrigger = true
└── CameraTriggerZone_BA 区域 B→A 入口
组件: CameraTriggerZone _targetArea = CameraArea_A
组件: BoxCollider2D isTrigger = true
```
**核心流程**
1. 玩家进入 `CameraTriggerZone``OnTriggerEnter2D` 调用 `ICameraService.SwitchArea(targetArea)`
2. `CameraStateController.SwitchArea` → 应用 `BlendProfile``CinemachineBrain.DefaultBlend`
- **无专有 VCam**:配置非活跃全局 VCam 的 `CinemachineConfiner2D.BoundingShape2D` → 提升其优先级至 10 → 降低旧 VCam 优先级至 0ping-pongCinemachine Brain 自动触发混合过渡
- **有专有 VCam**:提升 `_dedicatedCamera` 优先级至 `_dedicatedPriority`(默认 20高于全局 VCamCinemachine 自动切换
---
## 2. Persistent 场景配置CameraStateController
### 2.1 组件放置
在 Persistent 场景的 `[Camera]` 下建立以下节点结构:
| GameObject | 挂载组件 | 说明 |
|-----------|---------|------|
| `[CameraController]` | `CameraStateController``CinemachineBrain``CinemachineImpulseSource` | ExecutionOrder = -100 |
| `VCamA` | `CinemachineCamera``CinemachineConfiner2D` | 全局虚拟相机 A拖入 `CameraStateController._vcamA` |
| `VCamB` | `CinemachineCamera``CinemachineConfiner2D` | 全局虚拟相机 B拖入 `CameraStateController._vcamB` |
> **注意**`CinemachineBrain` 须挂在附有 `Camera` 组件Main Camera的 GameObject 上,
> 否则 Cinemachine 无法驱动视口渲染。两台全局 VCam 初始优先级均为 0由 `CameraStateController` 在运行时动态管理。
### 2.2 字段绑定清单
打开 **Camera Area Setup** 窗口(`BaseGames → Camera → Camera Area Setup`
**CameraStateController** 区域确认以下项目全为绿色 ●:
| 字段 | 期望状态 |
|------|---------|
| `_vcamA` (CinemachineCamera) | ● 已绑定 |
| `_vcamB` (CinemachineCamera) | ● 已绑定 |
| `_brain` (CinemachineBrain) | ● 已绑定 |
| `_impulseSource` (CinemachineImpulseSource) | ◌ 可选;用于屏幕抖动 |
| `_defaultBlendProfile` (CameraBlendProfileSO) | ◌ 可选;未设置则无混合过渡 |
---
## 3. 关卡场景配置CameraArea + CameraTriggerZone
### 3.1 添加 CameraArea
**方式 A使用快速放置工具**(推荐)
1. 菜单 `BaseGames → Scene → Place → Camera Area`
2. 工具自动创建以下节点结构:
```
CameraArea
├── CameraArea组件_confinerCollider 已绑定)
└── PolygonCollider2D默认矩形 24×12isTrigger = true定义限位区域
```
3. 打开 **Camera Area Setup** 窗口,点击 **为全局 VCam 赋值 Follow 目标**
(工具会自动在 Player 下查找或创建 `CameraFollowTarget` 子节点,绑定到两台全局 VCam
4. 手动调整子节点 `PolygonCollider2D` 顶点定义限位范围。
**方式 B手动创建**
1. 新建空 GameObject命名如 `CameraArea_A`
2. 挂载 `CameraArea`BaseGames.Camera
3. 在同一 GameObject 或子对象上创建 `PolygonCollider2D`
4. 将 `PolygonCollider2D` 拖入 `CameraArea._confinerCollider`
5. (可选)如需专有相机参数,新建独立 VCam GameObject挂载 `CinemachineCamera`,拖入 `CameraArea._dedicatedCamera`
> **一个房间可放置多个 `CameraArea`**,如大厅区域与 Boss 区域分别使用不同的限位和混合配置。
### 3.2 调整限位区域PolygonCollider2D
`CameraArea` 上(或子节点)的 `PolygonCollider2D` 定义了相机在该区域内的移动边界。
- **编辑顶点**:选中 `CameraArea` 节点 → Inspector 中 `PolygonCollider2D` → 点击 **Edit Collider** 图标,在 Scene 视图拖动顶点
- **自动修复**:打开 **Camera Area Setup** 窗口,对应条目点击 **修复:绑定子节点 PolygonCollider2D**
> **最佳实践**:限位区域应比实际可见范围大一格以上(约 1 unit避免相机卡在边缘。
### 3.3 添加 CameraTriggerZone
**使用快速放置工具**(推荐)
1. 菜单 `BaseGames → Scene → Place → Camera Trigger Zone`
2. 工具生成:
```
CameraTriggerZone
├── CameraTriggerZone组件_playerTag = "Player"
└── BoxCollider2DisTrigger = true默认 2×2
```
3. 在 Inspector 中将目标 `CameraArea` 拖入 `CameraTriggerZone._targetArea`
4. 调整 `BoxCollider2D` 大小至覆盖整个区域过渡走廊宽度(通常 2×3 或 2×4
**典型布局**
```
[区域 A] ‖ [走廊] ‖ [区域 B]
←← TriggerZone_A→B (_targetArea = CameraArea_B)
TriggerZone_B→A (_targetArea = CameraArea_A) →→
```
> 双向过渡需要两个 TriggerZone 分别放置在走廊两端,各自指向对应区域的 `CameraArea`。
### 3.4 全局 VCam Follow 绑定
Persistent 场景中两台全局 VCam 的 `CinemachineCamera.Follow` 须指向 **Player 下的 `CameraFollowTarget` 子节点**,而非 Player 根节点本身。
使用 `Place Player` 工具放置 Player 时,`CameraFollowTarget` 子节点会被自动创建(`localPosition = 0`)。
**方法**
- **自动**:打开 **Camera Area Setup** 窗口 → CameraStateController 区域 → 点击 **为全局 VCam 赋值 Follow 目标**(场景中必须已有 tag=Player 对象)
- **手动**:分别选中 `VCamA` / `VCamB` → CinemachineCamera 组件 → `Follow` 字段拖入 `Player/CameraFollowTarget` Transform
---
### 3.5 编辑可视区域(透视相机)
`CameraArea` 支持在 Scene 视图中直接定义摄像机的最大可视范围,并自动换算为限位 `PolygonCollider2D` 的顶点坐标。
**Inspector 字段**
| 字段 | 说明 |
|------|------|
| `_visibleBounds` | 摄像机应显示的最大可视矩形世界坐标Scene 视图选中时显示为**黄色矩形** |
| `_cameraDepth` | 摄像机到场景平面Z = 0的垂直距离留 `0` 则自动读取 `\|transform.position.z\|` |
**Scene 视图拖拽编辑**
选中 `CameraArea` GameObject 后Scene 视图出现:
- **黄色矩形**:可视区域(玩家在此区域内的最大可见范围)
- **蓝色多边形**:当前 `PolygonCollider2D` 限位边界(参考用)
矩形四条边各有一个滑动 Handle拖拽即可调整
- 左 / 右边 Handle沿 X 轴滑动
- 上 / 下边 Handle沿 Y 轴滑动
**同步到限位区域**
调整好可视区域后,在 Inspector 底部点击 **「从可视区域更新限位区域(透视)」**,工具根据以下公式换算限位多边形:
```
halfH = depth × tan(vFOV / 2)
halfW = halfH × aspectRatio
confiner = visibleBounds 向内收缩 (halfW, halfH)
```
> **含义**相机视口边缘恰好与可视区域边框对齐。若区域小于单屏inset 后为负),限位收缩为中心点,相机固定居中。
Inspector 参数预览区实时显示 FOV来源专有 VCam → 全局 VCamA → Camera.main → 60°、深度、视口半宽 / 半高的计算值。
### 3.6 专有 VCam特殊区域
需要独特相机参数(如 Boss 区域特写 FOV的区域可在 `CameraArea._dedicatedCamera` 中指定一台独立的 `CinemachineCamera`
1. 在关卡场景中新建空 GameObject挂载 `CinemachineCamera`(设置好 Lens、Follow、Noise 等参数)
2. 将其拖入该 `CameraArea._dedicatedCamera`
3. `_dedicatedPriority`(默认 20须高于全局 VCam 的激活优先级10
进入该区域时,`CameraStateController` 自动提升专有 VCam 优先级Cinemachine 混合切换;离开时优先级归零,全局 VCam 重新接管。
---
## 4. ScriptableObject 资产说明
### 4.1 CameraBlendProfileSO
**创建路径**`Assets → Create → BaseGames → Camera → BlendProfile`
| 字段 | 说明 | 典型值 |
|------|------|--------|
| `Style` | 混合曲线类型EaseInOut / Linear / Cut / Custom | `EaseInOut` |
| `BlendTime` | 混合持续时间(秒) | `0.5` |
| `CustomCurve` | 仅 `Style = Custom` 时使用 | — |
**使用**
- 全局默认:拖入 `CameraStateController._defaultBlendProfile`
- 单独区域:拖入对应 `CameraArea._blendProfile`(覆盖全局默认)
### 4.2 CameraConfigSO
**创建路径**`Assets → Create → BaseGames → Camera → CameraConfig`
| 字段 | 说明 | 典型值 |
|------|------|--------|
| `FollowDamping` | 跟随阻尼(越大越迟钝) | `0.15` |
| `LookAheadTime` | 朝向预见时间(秒) | `0.3` |
| `DeadZoneSize` | 死区尺寸(玩家在此范围内移动相机不动) | `(1, 0.5)` |
| `SoftZoneSize` | 软区尺寸(慢速追赶) | `(2.5, 2)` |
| `LookDownOffset` | 俯视偏移(负值向下) | `-1.5` |
| `LookUpOffset` | 仰视偏移(正值向上) | `1.5` |
| `DefaultImpulseStrength` | 默认震屏强度 | `0.3` |
> `CameraConfigSO` 的配置值须由运行时的 `CameraStateController` 或相机系统读取并写入 Cinemachine 组件,具体写入逻辑取决于 `CameraStateController.ApplyConfig()` 的实现(如有扩展)。
---
## 5. Camera Area Setup 工具详解
菜单:`BaseGames → Camera → Camera Area Setup`
### 5.1 界面区域说明
**工具栏**
- `↻ 刷新`:手动重新扫描当前已加载场景
- `Place Camera Area`:快捷调用 `BaseGames → Scene → Place → Camera Area`
- `Place Trigger Zone`:快捷调用 `BaseGames → Scene → Place → Camera Trigger Zone`
**CameraStateController 区域**
显示控制器组件绑定状态。若显示"未找到"提示,说明 Persistent 场景未加载(属正常)。
| 图标 | 含义 |
|------|------|
| ``(绿) | 项目已正确配置 |
| ``(红) | 缺失必填项 |
| ``(黄) | 可选项未设置 |
检查项:`_vcamA`、`_vcamB`(必填)、`_brain`(必填)、`_impulseSource`(可选)、`_defaultBlendProfile`(可选)
底部按钮:**为全局 VCam 赋值 Follow 目标** → 查找 Player/CameraFollowTarget 并写入两台 VCam 的 Follow 字段。
**Camera Areas 区域**
为每个 `CameraArea` 显示一行,检查项:
| 字段 | 状态 |
|------|------|
| `_confinerCollider` (PolygonCollider2D) | 必填 |
| `_dedicatedCamera`(专有 VCam | 可选 |
| `_blendProfile` | 可选 |
每行可点击 **修复:绑定子节点 PolygonCollider2D** 自动修复 `_confinerCollider` 未绑定的情况。
**Camera Trigger Zones 区域**
列出所有 `CameraTriggerZone`,高亮显示 `_targetArea` 未绑定的项目(红色 ✗)。
### 5.2 典型使用流程
```
1. 打开窗口 BaseGames → Camera → Camera Area Setup
2. (仅首次)加载 Persistent 场景,确认 _vcamA/_vcamB/_brain 全绿
3. 在关卡场景中使用 Place Camera Area × N一个房间可放多个
4. 选中每个 CameraArea在 Scene 视图拖拽黄色可视区域边 Handle点击 [从可视区域更新限位区域(透视)]
5. 点击 [为全局 VCam 赋值 Follow 目标](自动创建 Player/CameraFollowTarget 并绑定)
6. 使用 Place Trigger Zone 添加 N 个触发器,手动绑定 _targetArea
7. 所有条目绿色 ● → 进入 Play Mode 验证
```
---
## 6. 验收测试用例
### 测试前检查清单
| # | 检查项 | 操作 |
|---|--------|------|
| 1 | Console 无红色 Error | `Window → General → Console` |
| 2 | **Camera Area Setup** 窗口所有必填项为绿色 ● | `BaseGames → Camera → Camera Area Setup` |
| 3 | Player 已在场景中tag = Player | Hierarchy |
| 4 | Physics2D Layer 矩阵已配置 | `BaseGames → Tools → Validate Physics2D Layer Matrix` |
---
### MT-CAM-01全局 VCam 正常跟随
**目的**:验证全局 VCam 激活后 `CinemachineCamera` 跟随玩家移动。
**步骤:**
1. Persistent 场景中放置 VCamA/VCamB`Follow = Player/CameraFollowTarget`
2. 关卡场景中放置一个 `CameraArea`,通过 `CameraTriggerZone` 或 `RoomController` 触发 `SwitchArea`
3. 按 **Play**,在 Scene 视图和 Game 视图同时观察
4. 用 WASD/方向键移动 Player
**预期结果:**
| 检查点 | 期望 | ✓ |
|--------|------|---|
| Game 视图相机跟随 Player 移动 | 玩家靠近边缘时相机平滑跟进 | ☐ |
| 相机不会越出 CameraArea 的限位范围 | 玩家走到边角时相机贴边停止 | ☐ |
| 无跳变(平滑)跟随 | 无抖动、跳帧 | ☐ |
---
### MT-CAM-02区域相机切换CameraTriggerZone
**目的**:验证玩家穿越触发器后全局 VCam ping-pong 平滑过渡到目标区域。
**步骤:**
1. 场景中放置两个 `CameraArea`A、B各有独立 `PolygonCollider2D` 限位
2. 在两区域之间放置两个 `CameraTriggerZone`(各自 `_targetArea` 互指)
3. 按 **Play**,引导 Player 穿越触发区进入区域 B
**预期结果:**
| 检查点 | 期望 | ✓ |
|--------|------|---|
| 穿越触发器后 Game 视图开始混合过渡 | 相机平滑从 A 过渡到 B非切割 | ☐ |
| 过渡时长约等于 `CameraBlendProfileSO.BlendTime` | 与 SO 设置一致(默认 0.5s | ☐ |
| 过渡后相机限位在 CameraArea_B 的边界内 | 玩家无法把相机带出 B 的限位范围 | ☐ |
| 反向穿越触发器后相机切回 A | 同上,反向过渡 | ☐ |
---
### MT-CAM-03CinemachineConfiner2D 边界限位
**目的**:验证 `CinemachineConfiner2D` 正确将相机限制在 `CameraArea` 限位范围内。
**步骤:**
1. 打开关卡场景,确认 `CameraArea._confinerCollider` 已绑定,且 `CameraStateController` 已调用 `SwitchArea`
2. 按 **Play**,将 Player 移动到房间的各个角落和边缘
**预期结果:**
| 检查点 | 期望 | ✓ |
|--------|------|---|
| 相机在所有方向均不超出 `PolygonCollider2D` 多边形范围 | 无越界 | ☐ |
| 小房间(相机视口 > 房间)时相机居中,不晃动 | 稳定居中 | ☐ |
**常见失败原因**
- `CameraArea._confinerCollider` 未绑定 → 打开 Camera Area Setup 点击修复
- PolygonCollider2D 顶点数量少于 3 → 确认 `_confinerCollider` 路径顶点完整
---
### MT-CAM-04屏幕抖动CinemachineImpulseSource
**目的**:验证调用 `ICameraService.TriggerImpulse` 时 Game 视图画面抖动。
**步骤:**
1. 确认 `CameraStateController._impulseSource` 已绑定
2. 按 **Play**
3. 在 Console 执行(或通过游戏内事件触发):
```csharp
ServiceLocator.Get<ICameraService>().TriggerImpulse(0.5f);
```
或让玩家受到一次伤害(若伤害系统已接入抖动调用)
**预期结果:**
| 检查点 | 期望 | ✓ |
|--------|------|---|
| Game 视图画面发生轻微抖动后恢复稳定 | 抖动时长约 0.20.4s,幅度可见 | ☐ |
| 无 Console 错误 | 无 NullReferenceException | ☐ |
---
### MT-CAM-05初始场景无 CameraStateController 时安全降级
**目的**:验证场景中未加载 Persistent 场景时,`CameraTriggerZone` 不崩溃。
**步骤:**
1. 单独打开关卡场景(不加载 Persistent.unity
2. 按 **Play**,移动 Player 穿越 `CameraTriggerZone`
**预期结果:**
| 检查点 | 期望 | ✓ |
|--------|------|---|
| 无 NullReferenceException | `ServiceLocator.GetOrDefault<ICameraService>()` 返回 null 时跳过 | ☐ |
| Console 可能有黄色 Warning服务未注册 | 无红色 Error | ☐ |
---
### MT-CAM-06多场景加载时相机状态正确恢复
**目的**:验证通过 `SceneLoader` 加载新场景时,`CameraStateController` 正确切换到新场景首个 `CameraArea`。
**步骤:**
1. 以 Persistent + Level_01 双场景启动
2. 按 **Play**,触发场景加载切换到 Level_02
3. Level_02 加载完成后观察相机
**预期结果:**
| 检查点 | 期望 | ✓ |
|--------|------|---|
| Level_02 的 `RoomController` 调用 `SwitchArea` 后相机切换正确 | 无黑屏、无旧场景相机残影 | ☐ |
| 旧场景 `CameraArea` 随场景卸载,全局 VCam 状态不受干扰 | 无相机混合错误 | ☐ |
> **注意**:全局 VCam 常驻 Persistent 场景,`CinemachineConfiner2D.BoundingShape2D` 在 `SwitchArea` 时动态更新,
> 旧场景卸载后引用会变为 null须确保 `SwitchArea` 在新场景 `CameraArea` 可用后再调用。
---
## 7. 常见问题排查
| 现象 | 原因 | 解决 |
|------|------|------|
| Game 视图相机不动(黑屏或固定位置) | 全局 VCam `Follow` 未绑定 | Camera Area Setup → 为全局 VCam 赋值 Follow 目标 |
| 相机追赶卡顿/震颤 | `CinemachineConfiner2D.BoundingShape2D` 未绑定或碰撞体顶点有误 | 确认 `CameraArea._confinerCollider` 已绑定PolygonCollider2D 顶点数 ≥ 3 |
| 进入区域后限位未更新(仍在旧区域限位内) | `CameraArea._confinerCollider` 为空,`ConfigureSlot` 跳过了更新 | 打开 Camera Area Setup 修复 `_confinerCollider` 绑定 |
| 场景中有多个 `CinemachineBrain` | Persistent 场景外又添加了含 Camera 组件的对象 | 仅 Main Camera 上保留一个 Brain |
| 过渡时画面闪切而非混合 | `CameraBlendProfileSO.BlendTime = 0` 或 Style = Cut | 检查 BlendProfile 并将 BlendTime 设置为 > 0 |
| `CameraStateController` 未找到Console 错误) | Persistent 场景未加载 | 确认 Build Settings 中 Persistent.unity 第一位;开发测试用 `SceneManager.LoadScene("Persistent", Additive)` |
| 触发器无响应(玩家穿越后相机不切) | `CameraTriggerZone._targetArea` 未绑定,或 `_playerTag` 不匹配 | 检查 `_targetArea` 是否已拖入 `CameraArea`;确认 Player Tag = "Player" |
| `Camera Area Setup` 窗口列表为空 | 场景未保存或 DomainReload 后未刷新 | 点击窗口内 `↻ 刷新` 按钮 |
| 专有 VCam 不切换 | `_dedicatedPriority` ≤ 全局激活优先级(默认 10 | 将 `_dedicatedPriority` 设置为 > 10默认 20 已满足) |