相机身份:逻辑 Camera 不等于 ViewTarget Actor
自动关联目录:相机身份:逻辑 Camera 不等于 ViewTarget Actor
核心判断:逻辑相机保存创作与脚本语义,
ACameraActor是当前 World 的可替换表示,PlayerController::ViewTarget才是最终画面事实。三者必须显式绑定,不能共享一根裸指针。
一、业务冲突:脚本相机要稳定,UE 表示会反复重生
创作者需要透视/正交、默认预设、跟随、环绕、主体、遮挡和脚本控制;编辑预览与运行态又会切换 World。逻辑 CameraId 可以长期存在,但它对应的 Actor、PlayerController 和 ViewTarget 都可能因 Run-Stop-Run、关卡切换或 Pawn 替换而改变。
如果“当前相机 ID 没变”就跳过绑定,新生成 Actor 不会成为 ViewTarget;如果 Camera Actor 缓存逻辑对象裸指针,对象树重建后就可能写入已释放内存。
二、实际实现:稳定 ID 查活对象,并以 ViewTarget 验收
工程把 Camera 数据与每个运行上下文中的 Camera Actor 分开。Actor 不长期缓存逻辑对象地址,而是在使用时用 ObjectId 到注册表解析活实例;这直接规避了重建后陈旧指针导致的越界写风险。
设置 Active Camera 时,即使逻辑 ID 未变也会重新绑定 ViewTarget,因为底层 Actor 可能已换代。相机 Actor 销毁时,不只比较 ActiveId,还检查它是否正是 PlayerController 的实际 ViewTarget,再按 Active、Reserved、其他可用相机、默认相机的顺序修复。
flowchart LR
L[逻辑 CameraId + 配置] --> R[运行时注册表]
R --> A[当前 World 的 ACameraActor]
A --> V[PlayerController ViewTarget]
S[SubjectId] --> R
D[默认/预设 DataAsset] --> L
V --> O[最终画面]三、值得保留:默认相机、预设和脚本边界都已显式化
保留相机不可随意删除,默认相机会在缺失时按 World 状态幂等创建,避免空 ViewTarget。只有本地控制器设置画面,DS 和非本地代理不物化本地视图,这是正确的 UE 边界。
预设由 DataAsset 注册,缺失时有 C++/CDO 回退;Custom 模式由框架处理,Scriptable 模式则不再被 C++ 每帧干预。把“框架控制还是脚本控制”作为模式,而不是双方争写 Transform,方向正确。
四、结构性缺陷:字段协议和控制算法仍未真正收敛
当前旋转字段的文档轴含义与实际读写顺序不一致,这是比公式错误更危险的协议债务:编辑器、脚本和运行时都能各自“看起来正确”,序列化往返却改变姿态。默认参数同时存在表格与 CDO 两个回退源,也会让缺失配置在不同构建中得到不同结果。
跟随、环绕、遮挡、碰撞与插值如果都直接修改 Camera Actor Transform,就会再次形成多写者。平台手势若直接输出旋转增量,也绕过了俯仰限制、遮挡恢复速度和主体换代规则。
五、目标设计:Camera Model、Rig Solver、UE Bridge 三层
Model 只保存 CameraId、Projection、Mode、SubjectId、期望 Pose 与参数;Rig Solver 根据跟随/环绕规则、输入 Action、限制与碰撞查询生成 SolvedPose;UE Bridge 负责生成 Actor、设置投影并提交 ViewTarget。脚本写 Model 或提供 Rig,不直接持有 Actor。
旋转协议使用明确命名的 Pitch/Yaw/Roll 或 Quaternion,并带 SchemaVersion;预设只保留一个权威源。Subject 通过稳定 ID 解析,换代后重新绑定;遮挡用 Sweep 得到目标臂长,进入快、恢复慢,并把原始期望与碰撞后结果分别诊断。
void PresentCamera(CameraId id, float dt)
{
CameraModel m = modelStore.Resolve(id);
SubjectPose subject = subjects.Resolve(m.subjectId);
SolvedPose p = rigs[m.mode].Solve(m, subject, inputActions, dt);
p = collision.Resolve(p, m.occlusionPolicy);
bridge.EnsureActor(id).Apply(p, m.projection);
bridge.EnsureViewTarget(id); // 不能因 id 未变而跳过
}六、决策与验证:最终以画面绑定事实为准
| 层 | 身份/状态 | 生命周期 | 验证点 |
|---|---|---|---|
| Camera Model | CameraId、模式、参数 | 内容/会话 | 序列化往返不变 |
| Subject | SubjectId、Generation | 实体生命周期 | 换 Pawn 后可重绑 |
| Rig Solver | 期望与求解 Pose | 每帧/事件 | 限制、插值可复现 |
| UE Actor | 弱引用 + World | 当前 World | 销毁后可重建 |
| ViewTarget | PlayerController 实际指针 | 本地视口 | 始终指向活对象 |
| Preset | 版本化 DataAsset | 构建制品 | 缺失与重复可诊断 |
相机系统最关键的验收不是 ActiveId 正确,而是 Actor、ViewTarget、Subject 与 Model 的代际一致;任何一层换代,绑定都必须重新证明。