Files
zeling_v2/Docs/Design/02_CameraSystem.md
2026-05-08 11:04:00 +08:00

644 lines
26 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.
# 02 · 镜头系统
> **命名空间** `BaseGames.Camera`
> **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md)
> **依赖** Cinemachine 3 · `BaseGames.Core.Events`
---
## 目录
1. [设计目标](#1-设计目标)
2. [Cinemachine 3 核心组件](#2-cinemachine-3-核心组件)
3. [虚拟相机架构](#3-虚拟相机架构)
- 3.1 全局双机Global A/B
- 3.2 房间专用相机(可选)
- 3.3 特殊状态相机
4. [CameraStateController](#4-camerastatecontroller)
5. [可视区域与触发区域](#5-可视区域与触发区域)
- 5.1 可视区域RoomVisibleArea
- 5.2 切换触发区域CameraTriggerZone
6. [过渡效果CameraBlendProfileSO](#6-过渡效果camerablendprofileso)
7. [镜头震动CinemachineImpulse](#7-镜头震动cinemachineimpulse)
8. [像素对齐Pixel Perfect 集成](#8-像素对齐pixel-perfect-集成)
9. [CameraConfigSO — 配置资产](#9-cameraconfigso--配置资产)
10. [场景搭建规范](#10-场景搭建规范)
11. [编辑器友好设计](#11-编辑器友好设计)
12. [大房间相机处理](#12-大房间相机处理)
- 12.1 竖向/横向无缝滚动房间
- 12.2 子区域相机分段
- 12.3 滚动限制模式
---
## 1. 设计目标
- 镜头行为**完全由 Cinemachine 3 驱动**,不手动操作 `Camera.transform`
- **全局 A/B 双机交替复用**:切换房间时将 inactive 的全局相机预设好新边界再切过去,彻底消除 Confiner 跳变
- **房间专用相机(可选)**:每个房间可配置专属 `VCam_Room`,存在时自动优先使用;不配置则回退到全局双机
- **触发区域驱动切换**`CameraTriggerZone` Trigger Collider 控制镜头切换时机,可在 Scene 视图中可视化编辑
- **可视区域独立编辑**`RoomVisibleArea``PolygonCollider2D` 在 Scene 视图可直接拖拽顶点Gizmo 实时预览视野
- **过渡效果可配置**`CameraBlendProfileSO` 资产,可逐触发区域覆盖全局默认混合风格与时长
- 镜头震动通过 `CinemachineImpulseSource` 发布,与 Feel 的 `MMF_CinemachineImpulse` 集成
- 像素艺术风格保持亚像素锁定,防止渲染模糊
---
## 2. Cinemachine 3 核心组件
| 组件 | 挂载位置 | 说明 |
|------|---------|------|
| `CinemachineBrain` | Main Camera | 统一调度所有虚拟相机,同一时刻激活优先级最高者 |
| `CinemachineCamera` | 虚拟相机 GO | Cinemachine 3 的虚拟相机基础组件(取代旧版 `CinemachineVirtualCamera`|
| `CinemachinePositionComposer` | 虚拟相机组件 | 控制跟随目标偏移与阻尼(探索镜头用)|
| `CinemachineOrbitalFollow` | 虚拟相机组件 | Boss 战镜头围绕目标(可选)|
| `CinemachineConfiner2D` | 虚拟相机组件 | 房间边界约束(每个房间设置独立 `Collider2D`|
| `CinemachineImpulseSource` | 虚拟相机 GO | 发布震动冲量(与 Feel 集成)|
| `CinemachineImpulseListener` | 虚拟相机组件 | 接收并响应冲量(震屏强度系数)|
| `CinemachinePixelPerfect` | 虚拟相机组件 | 与 `PixelPerfectCamera` 协同,消除亚像素抖动 |
---
## 3. 虚拟相机架构
### 3.1 全局双机Global A/B
Persistent 场景中预置两台全局虚拟相机,同一时刻仅一台处于"热"优先级。切换房间时将 inactive 机预配置好新 Confiner再升高其优先级触发 BlendBlend 结束后互换角色。
| 虚拟相机 | 默认 Priority | 说明 |
|---------|:---:|------|
| `VCam_Global_A` | 10 | 初始活跃,跟踪玩家 |
| `VCam_Global_B` | 9 | 待机,切换时接管 |
**房间切换时序(无房间专用相机):**
```
玩家穿过 CameraTriggerZone
CameraStateController.SwitchRoom(data)
├─ 1. 更新 inactive 机(如 VCam_B
│ · BoundingShape2D ← 新房间 RoomVisibleArea.Collider2D
│ · FollowTarget ← 玩家 Transform不变
│ · PositionOffset ← data.cameraOffset
├─ 2. 将 data.blendProfile 写入 CinemachineBrain.DefaultBlend
├─ 3. inactiveCam.Priority = activeCam.Priority + 1
│ → CinemachineBrain 自动开始 Blend
└─ 4. Blend 结束CinemachineBrain.BlendFinished 回调)
activeCam.Priority = 9
swap _activeCam / _inactiveCam 引用
```
### 3.2 房间专用相机(可选)
每个房间场景可以放置一个带 `RoomCamera` 组件的虚拟相机。若存在,则优先级高于全局双机;全局双机继续在后台保持配置,以便专用相机卸载时无缝接管。
| 情况 | 使用相机 | Priority |
|------|---------|:---:|
| 房间无专用相机 | 全局 A/B 中的 active 方 | 10 |
| 房间有 `RoomCamera` | 房间专用虚拟相机 | 15 |
```csharp
// RoomCamera.cs — 挂在房间场景的 VCam_Room_XXX 上
public class RoomCamera : MonoBehaviour
{
[SerializeField] CinemachineCamera _vcam;
[SerializeField] RoomVisibleArea _visibleArea; // 本房间可视区域
[SerializeField] CameraBlendProfileSO _enterBlend; // 进入时的过渡(可留空)
void OnEnable() => CameraStateController.Instance.RegisterRoomCamera(this);
void OnDisable() => CameraStateController.Instance.UnregisterRoomCamera(this);
public CinemachineCamera Vcam => _vcam;
public RoomVisibleArea VisibleArea => _visibleArea;
public CameraBlendProfileSO EnterBlend => _enterBlend;
}
```
> 房间专用相机注册时,`CameraStateController` 同步将全局双机的 Confiner 也设置为同一区域,确保专用相机卸载后全局机能无缝接管。
### 3.3 特殊状态相机
以下相机覆盖所有普通相机,由 `CameraStateController` 响应事件频道激活:
| 虚拟相机 | Priority | 激活条件 | 默认 Blend |
|---------|:---:|---------|---------|
| `VCam_Boss` | 30 | `OnBossFightToggled.Raise(true)` | 0.8 s EaseIn |
| `VCam_Cutscene` | 40 | `OnCutsceneStarted` | 0.0 s Cut |
| `VCam_Death` | 50 | `OnPlayerDied` | 1.0 s EaseIn |
---
## 4. CameraStateController
`CameraStateController``MonoBehaviour`,挂在 CameraRig GO 上,`DefaultExecutionOrder(-200)`)负责维护全局双机状态、响应事件频道、协调房间专用相机:
```csharp
public class CameraStateController : MonoBehaviour
{
public static CameraStateController Instance { get; private set; }
[Header("全局双机")]
[SerializeField] CinemachineCamera _globalA;
[SerializeField] CinemachineCamera _globalB;
[Header("特殊状态机")]
[SerializeField] CinemachineCamera _vcamBoss;
[SerializeField] CinemachineCamera _vcamCutscene;
[SerializeField] CinemachineCamera _vcamDeath;
[Header("默认过渡效果")]
[SerializeField] CameraBlendProfileSO _defaultRoomBlend;
[Header("事件频道")]
[SerializeField] BoolEventChannelSO _onBossFightToggled;
[SerializeField] VoidEventChannelSO _onPlayerDied;
[SerializeField] VoidEventChannelSO _onCutsceneStarted;
[SerializeField] VoidEventChannelSO _onCutsceneEnded;
CinemachineCamera _activeCam; // 当前"热"全局机
CinemachineCamera _inactiveCam; // 待机全局机
RoomCamera _currentRoomCam;
// 切换到新房间(由 CameraTriggerZone 调用)
public void SwitchRoom(CameraTriggerZone.SwitchData data) { ... }
// 房间专用相机注册/反注册(由 RoomCamera.OnEnable/OnDisable 调用)
public void RegisterRoomCamera(RoomCamera cam) { ... }
public void UnregisterRoomCamera(RoomCamera cam) { ... }
}
```
**默认 Blend 查找顺序(优先级从高到低):**
```
触发区域 CameraTriggerZone._blendOverride (最高优先)
↓ 为空时
目标房间 RoomCamera._enterBlend
↓ 为空时
CameraStateController._defaultRoomBlend (全局兜底)
```
**特殊状态 Blend固定值不受 BlendProfile 影响):**
| 切换 | 时长 | 风格 | 说明 |
|------|------|------|------|
| 任意 → Boss | 0.8 s | EaseIn | 慢推入强调 Boss 出场 |
| Boss → 探索 | 0.5 s | EaseOut | 退场恢复 |
| 任意 → Death | 1.0 s | EaseIn | 慢推营造悲剧感 |
| 任意 → Cutscene | 0.0 s | Cut | 过场硬切 |
| Cutscene → 探索 | 0.3 s | EaseOut | 过场结束柔和恢复 |
---
## 5. 可视区域与触发区域
### 5.1 可视区域RoomVisibleArea
定义该房间允许相机移动的边界,驱动 `CinemachineConfiner2D`。每个房间场景(或同房间不同区段)放置一个:
```
Scene: Room_Forest_01
└── [VisibleArea_Main] (GameObject)
├── PolygonCollider2D (IsTrigger = false) ← Confiner 要求非 Trigger
└── RoomVisibleArea.cs
├── [SerializeField] string areaId // 唯一 ID"Room_Forest_01_Main"
├── [SerializeField] Vector2 cameraOffset // 此区域相机跟随偏移
└── [SerializeField] bool hideOnPlay // 运行时隐藏 Gizmo
```
> **重要**`CinemachineConfiner2D` 要求 `PolygonCollider2D.IsTrigger = false`。如需同一位置同时做触发检测,在子 GO 上另挂一个 Trigger Collider`CameraTriggerZone` 使用独立 GO
**边界约束参数**`CameraConfigSO`
| 参数 | 推荐值 | 说明 |
|------|:------:|------|
| `ConfinerDamping` | 0.0 | 到达边界时的阻尼0 = 硬停止)|
| `SlowingDistance` | 0.5 | 靠近边界时开始减速的距离 |
一个大房间可以配置**多个 `RoomVisibleArea`**(如上下层、分支路线),由不同的 `CameraTriggerZone` 分别引用。
---
### 5.2 切换触发区域CameraTriggerZone
`CameraTriggerZone` 是驱动相机切换的入口,通过 Trigger Collider 检测玩家进入:
```
Scene: Room_Forest_01
└── [CameraTrigger_Entry] (GameObject)
├── BoxCollider2D (IsTrigger = true) ← 放在入口门洞处,可视化编辑
└── CameraTriggerZone.cs
├── [SerializeField] RoomVisibleArea _targetVisibleArea // 切换到哪个可视区域
├── [SerializeField] RoomCamera _targetRoomCamera // 可留空(使用全局双机)
├── [SerializeField] CameraBlendProfileSO _blendOverride // 可留空(使用全局默认)
├── [SerializeField] Vector2 _cameraOffset // 可覆盖 VisibleArea 的偏移
└── [SerializeField] bool _triggerOnce // true = 仅触发一次
```
```csharp
public class CameraTriggerZone : MonoBehaviour
{
public struct SwitchData
{
public RoomVisibleArea visibleArea;
public RoomCamera roomCamera; // null = 使用全局双机
public CameraBlendProfileSO blendProfile; // null = 查找上级默认
public Vector2 cameraOffset;
}
void OnTriggerEnter2D(Collider2D other)
{
if (!other.CompareTag("Player")) return;
if (_triggerOnce && _triggered) return;
_triggered = true;
CameraStateController.Instance.SwitchRoom(new SwitchData
{
visibleArea = _targetVisibleArea,
roomCamera = _targetRoomCamera,
blendProfile = _blendOverride,
cameraOffset = _cameraOffset != Vector2.zero
? _cameraOffset
: _targetVisibleArea.CameraOffset
});
}
bool _triggered;
}
```
**典型放置方式**:在房间入口门洞处放置细长 `BoxCollider2D`(宽约 0.5 单位,高覆盖整个通道),确保玩家穿越时必然触发。`_triggerOnce = false` 时,玩家来回穿越可双向切换(适合同一大房间内的区段分割线)。
---
## 6. 过渡效果CameraBlendProfileSO
`CameraBlendProfileSO` 是一个轻量配置资产,封装 `CinemachineBlendDefinition`,可在 Inspector 中独立编辑并在多个触发区域之间复用:
```csharp
[CreateAssetMenu(menuName = "Camera/BlendProfile")]
public class CameraBlendProfileSO : ScriptableObject
{
[Tooltip("过渡风格Cut / EaseInOut / EaseIn / EaseOut / HardIn / HardOut / Linear")]
public CinemachineBlendDefinition.Styles style = CinemachineBlendDefinition.Styles.EaseInOut;
[Range(0f, 3f)]
public float duration = 0.4f;
public CinemachineBlendDefinition ToBlendDefinition()
=> new CinemachineBlendDefinition(style, duration);
}
```
**资产存放路径**`Assets/ScriptableObjects/Config/Camera/Blends/`
**预置资产(建议创建):**
| 资产名 | Style | Duration | 典型用途 |
|--------|-------|:--------:|----------|
| `Blend_Room_Default.asset` | EaseInOut | 0.4 s | 普通房间切换(全局兜底)|
| `Blend_Room_Snap.asset` | Cut | 0.0 s | 重生/传送等硬切 |
| `Blend_Room_Slow.asset` | EaseIn | 0.8 s | 进入特殊区域(如 Boss 前厅)|
| `Blend_Room_Fast.asset` | Linear | 0.15 s | 快速连续的密集小房间 |
---
## 7. 镜头震动CinemachineImpulse
### 震动类型设计
| 类型 | 触发来源 | 强度 | 时长 | 说明 |
|------|---------|------|------|------|
| `Light` | 玩家攻击命中普通敌人 | 0.2 | 0.1s | 轻微点击感 |
| `Medium` | 玩家受击 | 0.5 | 0.25s | 明显震动 |
| `Heavy` | 玩家死亡 / Boss 重击 | 1.0 | 0.4s | 强烈震动 |
| `Parry` | 弹反成功 | 0.7 | 0.2s | 带方向性(从敌人方向弹开)|
| `Landing` | 高空落地 | 0.3 | 0.15s | 垂直向下震动 |
### 震动集成方式
Feel 的 `MMF_CinemachineImpulse` Feedback 内部持有 `CinemachineImpulseSource`
- 命中时由 `PlayerFeedback` / `EnemyFeedback` 中的 `MMF_Player` 触发
- `CinemachineImpulseListener`(挂在虚拟相机上)响应冲量,配置强度缩放系数
**CinemachineImpulseSource 配置**Inspector
| 参数 | 推荐值 | 说明 |
|------|--------|------|
| `ImpulseDefinition.ImpulseType` | `Uniform` | 各方向均匀(普通)/ `Directional`(带方向性震动)|
| `ImpulseDefinition.ImpulseShape` | 自定义曲线 | 快速衰减曲线(前 20% 满强度,后 80% 指数衰减)|
| `DefaultVelocity` | (0.5, -0.5, 0) | 默认震动方向(右斜下)|
---
## 8. 像素对齐Pixel Perfect 集成
像素艺术游戏要求相机位置锁定到整数像素,否则 Sprite 会出现模糊/闪烁:
### 组件配置链
```
Main Camera
├── PixelPerfectCamera (com.unity.2d.pixel-perfect)
│ ├── Assets Pixels Per Unit: 32
│ ├── Reference Resolution: 480 × 270 (16:9 基准)
│ ├── Crop Frame X/Y: 关闭(允许缩放而非裁剪)
│ └── Upscale Render Texture: 开启(低分辨率渲染再放大)
└── CinemachineBrain
└── Update Method: Fixed Update与物理帧同步消除抖动
```
### 虚拟相机配置
每个虚拟相机上添加 `CinemachinePixelPerfect` Extension
- 此 Extension 自动将相机位置对齐到像素网格
-`PixelPerfectCamera` 配合,确保最终渲染无亚像素误差
### Orthographic Size 计算
$$\text{Orthographic Size} = \frac{\text{Reference Resolution Height}}{2 \times \text{PPU}} = \frac{270}{2 \times 32} = 4.21875$$
> 使用 `PixelPerfectCamera` 时Orthographic Size 由组件自动管理,勿手动设置。
---
## 9. CameraConfigSO — 配置资产
`CameraConfigSO` 集中管理所有可调节的镜头参数,存放于:
`Assets/ScriptableObjects/Config/Camera/CameraConfigSO.asset`
### 探索镜头参数
| 参数 | 类型 | 推荐值 | 说明 |
|------|------|--------|------|
| `ExploreFollowDamping` | `Vector2` | (0.2, 0.2) | XY 轴跟随阻尼 |
| `ExploreAheadDistance` | `float` | 2.0 | 朝移动方向的前瞻偏移量 |
| `ExploreAheadSmoothing` | `float` | 0.5 | 前瞻平滑时间 |
| `ExploreLookUpOffset` | `float` | 1.5 | 长按上看时的额外 Y 偏移 |
| `ExploreLookDownOffset` | `float` | -1.0 | 长按下看时的额外 Y 偏移 |
| `ExploreFOV` | `float` | 4.22 | Orthographic Size像素对齐|
### 战斗镜头参数
| 参数 | 类型 | 推荐值 | 说明 |
|------|------|--------|------|
| `CombatZoomOut` | `float` | +0.5 | 战斗时 Orthographic Size 增量(拉远)|
| `CombatForwardOffset` | `float` | 1.0 | 朝敌人方向额外偏移 |
| `CombatFollowDamping` | `Vector2` | (0.3, 0.3) | 战斗时更高阻尼(稳定)|
### Boss 镜头参数
| 参数 | 类型 | 推荐值 | 说明 |
|------|------|--------|------|
| `BossRoomCenter` | `Vector2` | 由 Boss 房间配置 | Boss 房间固定镜头中心 |
| `BossZoomLevel` | `float` | 6.0 | Boss 战 Orthographic Size |
---
## 10. 场景搭建规范
### Persistent 场景CameraRig 层级结构
```
[CameraRig] (Prefab — DontDestroyOnLoad放入 Persistent 场景)
├── Main Camera
│ ├── PixelPerfectCamera
│ └── CinemachineBrain
│ └── DefaultBlend ← 运行时由 CameraStateController 动态写入
├── [GlobalCameras] ← 全局双机,永远存在
│ ├── VCam_Global_A (CinemachineCamera, Priority 10)
│ │ ├── CinemachinePositionComposer (Follow = Player)
│ │ ├── CinemachineConfiner2D ← BoundingShape2D 运行时设置
│ │ ├── CinemachineImpulseListener
│ │ └── CinemachinePixelPerfect
│ └── VCam_Global_B (CinemachineCamera, Priority 9)
│ ├── CinemachinePositionComposer (Follow = Player)
│ ├── CinemachineConfiner2D
│ ├── CinemachineImpulseListener
│ └── CinemachinePixelPerfect
├── [SpecialCameras] ← 特殊状态机,默认 Priority 0
│ ├── VCam_Boss (CinemachineCamera)
│ │ ├── CinemachineConfiner2D ← Boss 房间专属 Confiner运行时设置
│ │ └── CinemachinePixelPerfect
│ ├── VCam_Death (CinemachineCamera)
│ │ └── CinemachinePixelPerfect
│ └── VCam_Cutscene (CinemachineCamera)
│ └── CinemachinePixelPerfect
├── [ImpulseSources]
│ ├── ImpulseSource_Light
│ ├── ImpulseSource_Medium
│ ├── ImpulseSource_Heavy
│ ├── ImpulseSource_Parry
│ └── ImpulseSource_Landing
└── CameraStateController.cs
```
### 房间场景中的标准配置
```
Room_XXX (Scene)
├── [VisibleArea_Main] (GameObject) ← 必须,定义主可视区域
│ ├── PolygonCollider2D (IsTrigger = false)
│ └── RoomVisibleArea.cs
│ └── areaId = "Room_XXX_Main"
├── [VisibleArea_Sub] (GameObject) ← 可选,房间有多段时使用
│ ├── PolygonCollider2D (IsTrigger = false)
│ └── RoomVisibleArea.cs
├── [CameraTrigger_Entry] (GameObject) ← 房间入口触发器
│ ├── BoxCollider2D (IsTrigger = true) ← 放在入口门洞处
│ └── CameraTriggerZone.cs
│ ├── _targetVisibleArea = VisibleArea_Main
│ ├── _targetRoomCamera = (留空→使用全局双机)
│ └── _blendOverride = (留空→全局默认)
└── [VCam_Room_XXX] (GameObject) ← 可选,房间专用相机
├── CinemachineCamera (Priority 15)
│ ├── CinemachinePositionComposer
│ ├── CinemachineConfiner2D
│ └── CinemachinePixelPerfect
└── RoomCamera.cs
├── _visibleArea = VisibleArea_Main
└── _enterBlend = Blend_Room_Slow (可选)
```
---
## 11. 编辑器友好设计
### RoomVisibleArea — Scene 视图
- `PolygonCollider2D` 内置**Edit Collider** 按钮,可直接在 Scene 视图拖拽顶点编辑边界形状
- `RoomVisibleArea` 始终绘制 **青色轮廓线 + 半透明青色填充**,表示 Confiner 边界
- 叠加绘制 **白色虚线矩形** 表示当前 Orthographic Size 对应的实际视野范围,方便确认边界是否足够
- Inspector 中自动检测:若 PolygonCollider2D 包围盒小于摄像机视野,显示红色警告:
`⚠ 可视区域小于摄像机视野,镜头将无法移动!`
### CameraTriggerZone — Scene 视图
- 触发区域 Collider2D 始终绘制 **黄色轮廓**(即使未选中 GO
- 选中时:绘制从触发区域中心指向 `_targetVisibleArea` 中心的 **橙色箭头**,直观显示切换目标
-`_targetRoomCamera` 不为空,额外绘制 **紫色细线** 指向专用相机位置
- Inspector 中 `_blendOverride` 字段旁显示当前生效的 Blend 描述(含回退链说明):
```
生效 Blend: [触发区域覆盖] EaseIn 0.8s
生效 Blend: [全局兜底] EaseInOut 0.4s
```
### CameraStateController — 运行时监控
Play Mode 下 Inspector 显示:
```
┌─ CameraStateController ──────────────────────────────────────────┐
│ Active Global : VCam_Global_A (Priority 10) │
│ Inactive Global: VCam_Global_B (Priority 9) │
│ Room Camera : VCam_Room_Forest01 (Priority 15) │
│ Blend Progress : ████████████░░░░ 75% │
│ Current Area : Room_Forest_01_Main │
│ ─────────────────────────────────────────────────────────────── │
│ [触发房间切换] [触发 Boss] [触发死亡] [强制 Snap] │
└──────────────────────────────────────────────────────────────────┘
```
### Scene 视图 Gizmos 汇总
| 组件 | Gizmo 颜色 | 内容 |
|------|-----------|------|
| `RoomVisibleArea` | 青色 | Confiner 边界轮廓 + 当前视野矩形 |
| `CameraTriggerZone` | 黄色 | 触发 Collider 轮廓 |
| `CameraTriggerZone`(选中) | 橙色箭头 | 指向目标 VisibleArea |
| `RoomCamera` | 紫色 | VCam 视锥Frustum轮廓 |
| `CinemachineImpulseSource` | 红色箭头 | 震动方向,长度表示强度 |
| `LargeRoomBoundary` | 绿色粗线框 | 大房间整体边界 |
| `SubRegionTrigger`(选中) | 青绿箭头 | 指向该子区域激活的 VCam |
---
## 12. 大房间相机处理
银河恶魔城中存在若干**大型房间**Boss 房、竖井、长廊),单个 Confiner 配合全局相机会导致视野移动范围过大、失去房间感。本节说明三种处理策略。
### 12.1 竖向/横向无缝滚动房间
**适用**长廊横向滚动、竖井纵向滚动、Boss 前庭。
做法:创建宽/高尺寸匹配房间的 `RoomVisibleArea`PolygonCollider2D全局相机的 Confiner 直接限制在这块大 Confiner 里自由滑动。
```
房间宽度 60 单位、高度 12 单位(横向长廊):
RoomVisibleArea:
PolygonCollider2D 顶点: (-30,-6), (30,-6), (30,6), (-30,6)
全局相机 Confiner2D → 绑定这个 Collider
结果: 镜头可以横向自由跟随,纵向锁死在房间高度内
```
**锁轴配置**(在 `RoomVisibleArea` 上附加 `CameraAxisLock`
```csharp
public class CameraAxisLock : MonoBehaviour
{
public enum LockMode { None, LockX, LockY }
[SerializeField] LockMode _lock = LockMode.None;
[SerializeField] float _lockedValue; // LockX 时为固定 X 坐标LockY 时为固定 Y 坐标
// CameraStateController 在读取 RoomVisibleArea 时检查是否挂载了此组件
// 若存在,则在 LateUpdate 中强制修正相机位置
public void ApplyLock(CinemachineBrain brain)
{
var pos = brain.transform.position;
switch (_lock)
{
case LockMode.LockX: brain.transform.position = new Vector3(_lockedValue, pos.y, pos.z); break;
case LockMode.LockY: brain.transform.position = new Vector3(pos.x, _lockedValue, pos.z); break;
}
}
}
```
### 12.2 子区域相机分段
**适用**Boss 房(进入 Boss 房后锁定在 Boss 战视角)、多功能大厅(不同区域有不同镜头高度)。
策略:在大房间内放置若干 `SubRegionTrigger`,每个子区域激活专属 `RoomCamera`(更高 Priority
```
大房间层级:
├── LargeRoom_BossArena
│ ├── RoomVisibleArea_Global ← 整体边界(兜底 Confiner
│ ├── SubRegionTrigger_Entrance ← 入口区,激活 VCam_Entrance低焦
│ ├── SubRegionTrigger_Arena ← 战斗区,激活 VCam_Arena剧院视角
│ └── SubRegionTrigger_Exit ← 出口区,激活 VCam_Exit低焦
```
```csharp
/// <summary>
/// 挂载在子区域触发 Collider 上,玩家进入时激活指定 RoomCamera离开时还原。
/// 可与 §3.2 RoomCamera 逻辑复用SubRegionTrigger 本质是 CameraTriggerZone 的轻量别名。
/// </summary>
[RequireComponent(typeof(Collider2D))]
public class SubRegionTrigger : MonoBehaviour
{
[SerializeField] CinemachineCamera _subCamera; // 子区域专用 VCam
[SerializeField] CameraBlendProfileSO _blendIn; // 进入时的 Blend
[SerializeField] CameraBlendProfileSO _blendOut; // 离开时的 Blend可为 null用全局兜底
void OnTriggerEnter2D(Collider2D other)
{
if (!other.CompareTag("Player")) return;
CameraStateController.Instance.ActivateSubRegion(_subCamera, _blendIn);
}
void OnTriggerExit2D(Collider2D other)
{
if (!other.CompareTag("Player")) return;
CameraStateController.Instance.DeactivateSubRegion(_subCamera, _blendOut);
}
}
```
`CameraStateController` 新增方法:
```csharp
public void ActivateSubRegion(CinemachineCamera subCam, CameraBlendProfileSO blend)
{
if (blend != null)
ApplyBlendOverride(subCam, blend);
subCam.Priority = _roomCameraBasePriority + 5; // 高于普通房间相机
}
public void DeactivateSubRegion(CinemachineCamera subCam, CameraBlendProfileSO blend)
{
if (blend != null)
ApplyBlendOverride(subCam, blend);
subCam.Priority = 0;
}
```
### 12.3 滚动限制模式
针对特定大型场景需要限制某轴的相机运动:
| 模式 | 典型场景 | 配置 |
|------|---------|------|
| **横向自由 + 纵轴锁定** | 横向长廊、横向 Boss 房 | `CameraAxisLock.LockY` |
| **纵向自由 + 横轴锁定** | 竖井、电梯通道 | `CameraAxisLock.LockX` |
| **双轴自由** | 大型开放场景、探索大厅 | 无 LockConfiner 覆盖整个区域 |
| **双轴锁定(静止镜头)** | 剧场演出、特定剧情房间 | 使用固定位置 VCamPriority 最高 |
**锁轴时的边缘提示**:当房间宽度/高度接近相机视野的 1.5× 以内时,`RoomVisibleArea` 的 Gizmo 会在相应轴方向上绘制红色警告线,提示设计师考虑是否需要 `CameraAxisLock`。