相机身份:逻辑 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 ModelCameraId、模式、参数内容/会话序列化往返不变
SubjectSubjectId、Generation实体生命周期换 Pawn 后可重绑
Rig Solver期望与求解 Pose每帧/事件限制、插值可复现
UE Actor弱引用 + World当前 World销毁后可重建
ViewTargetPlayerController 实际指针本地视口始终指向活对象
Preset版本化 DataAsset构建制品缺失与重复可诊断

相机系统最关键的验收不是 ActiveId 正确,而是 Actor、ViewTarget、Subject 与 Model 的代际一致;任何一层换代,绑定都必须重新证明。