物理属性:一次变更必须原子提交到 Body 与接触过滤

自动关联目录:物理属性:一次变更必须原子提交到 Body 与接触过滤

核心判断:密度、质量、摩擦、弹性、Anchored、碰撞响应和接触事件不是互不相关的 Setter。UE 中若某一步触发 RecreatePhysicsState 或 UpdateMassProperties,之前写入的 shape filter、Contact Modify 标记和速度都可能失效,因此一次属性修改必须经过校验、计划、提交、复核四阶段。

一、业务冲突:创作者只改一个字段,引擎却可能重建整个物理状态

创作者期望 Anchored=false、CanCollide=false 或修改 Density 立即生效,且编辑态与 Play 态一致。玩法还要求“可穿透但仍触发接触”“可被查询但不参与刚体解算”“命名材质和自定义材质互斥”,客户端所有权切换后又不能让本地物理与远端插值同时写同一 Body。

UE 的实际代价远比属性表复杂。SetSimulatePhysics 可能重建 Body;RecreatePhysicsState 会创建新 shape;SetPhysMaterialOverride 与 UpdateMassProperties 会更新质量和惯量,某些路径也会使 filter data 重新生成。工程曾出现 Anchored 从 true 变 false 后,Block 接触和 Overlap 两条事件路径同时消失:新 shape 没继承 Contact Modify 能力、通道响应和自定义 marker。另一个竞态是客户端先收到 Unanchor 属性、抢先本地模拟,稍后才收到远端物理接管,于是物体先动、停住、再被同步拉走。

这些问题说明,属性写入的正确单位不是一个 Key,而是“这个 Body 在本次提交后应处于怎样的完整状态”。

二、实际实现:缓存真相,按模式写穿,并严格安排重建顺序

实现用行为组件保存 Anchored、碰撞、触碰、查询、碰撞组和物理材质字段。Edit 模式只更新缓存;进入 Play 或运行时属性改变时才写入 UPrimitiveComponent。命名材质解析为密度、摩擦、弹性与两种混合权重,自定义模式直接使用缓存值;理论质量按密度与当前几何体积计算,而不是依赖“当前是否正在模拟”的 GetMass()。

碰撞语义被拆成独立通道:CanCollide=false && CanTouch=true 使用 Overlap,查询开关控制 Query Channel,触碰事件同时支持阻挡接触与非阻挡 Overlap。两个受管物体接触时,Contact Modify 按双方权重混合摩擦和弹性;与普通 UE 物体接触时则走引擎 Physical Material Combine Mode。物理线程只读 World 级注册表,发现 shape marker 丢失时入无锁队列,由 Game Thread 在 PostPhysics 阶段修复。

完整应用顺序是关键:先配置 Overlap 与各通道响应,再在 Body 创建前启用 Contact Modification;随后决定是否模拟、必要时重建并 Wake;再应用 Physical Material、Mass Override 与惯量更新;最后重写碰撞组和 marker。纯客户端是否模拟不只看 Anchored,还看本地/远端所有权,避免属性包先于首个物理快照到达时抢跑。对重复的 Unanchor,如果动态 Body 仍有效则避免重建,以免把刚收到的速度清空。

flowchart LR
    P[属性候选值] --> V[范围/单位/权限校验]
    V --> C[更新规范缓存]
    C --> F[通道与事件过滤]
    F --> B[模拟状态/必要重建]
    B --> M[材质/质量/惯量]
    M --> G[碰撞组与 Marker]
    G --> O[读取 Body 事实并发布事件]

三、值得保留:把 UE 副作用当成显式时序,而不是偶然细节

CanCollide、CanTouch 和 CanQuery 独立建模是业务语义上的正确选择。只用 UE 的 CollisionEnabled 四态无法完整表达“穿透但有 Touched”和独立查询;显式 Channel 映射让每种组合都能被测试。Edit 只改缓存、Play 统一 Apply,则避免编辑预览中的 Body 写入污染运行态初始化。

质量用理论值计算也很重要。Anchored、远端接管或 Capsule 可能让当前 Body 返回零或历史值,密度换算又有明确单位;从规范几何和有效 Density 推导,才能让脚本读取、属性面板和真正 Body 使用同一口径。Scale 变化主动触发质量重算,补上了“材质没变但体积变了”的依赖边。

更成熟的是对 UE 重建副作用的认识:Contact Modification 必须在 shape 创建前打开,自定义 marker 必须在材质和质量更新后最后写入,物理线程不能直接修改 const shape,因此用事件队列回到 Game Thread 自愈。代码与测试记录了这些先后关系,说明修复已经从补 Setter 进化到维护提交协议。

四、结构性缺陷:事务仍由分支手工拼装,容易再次漏步骤

当前多个属性分支各自决定调用局部 Setter、完整 Apply 或窄化重建流程。这样能快速修回归,却导致提交顺序以注释和复制代码维持:未来新增 Gravity、Damping、CCD 或新的过滤字段时,很容易只更新缓存,忘记 Wake、质量重算、网络所有权或 marker 恢复。

从当前属性链还能看到一个明确边界:密度、摩擦、弹性、锚定和碰撞开关已经进入规范缓存与运行时写穿,逐物体 Linear/Angular Damping 和 Gravity Policy 尚未进入同一事务。它们目前更多依赖 UE Body 默认值或世界重力,不能被描述成已完成的创作能力。若后续开放,Damping 应校验非负范围并决定是否 Wake,Gravity 应区分世界重力倍率、自定义重力源和完全禁用,同时进入 Authority Snapshot;直接再加两个 Setter 会重复现有问题。

非法值处理也不够强。文本解析失败若静默降为 false 或零,会把格式错误变成合法但危险的物理状态,例如 Density 归零、Weight 异常或误解锚;正确策略应区分 Parse Error、Range Error 与业务允许的零值,并保持旧状态不变。命名材质缺失时退回默认材质虽然保证可运行,却需要结构化告警,否则创作错误只表现为“手感不对”。

接触混合的双轨语义也有认知成本:受管对象之间走自定义加权,与普通 UE 物体之间走全局 Combine Mode。若调试工具不显示本次 Contact 使用了哪条路径、双方材质身份与最终系数,问题会被误判成 Solver 不稳定。当前注册表优化了查询成本,但物理属性的规范快照、提交结果和接触观测尚未成为统一诊断对象。

五、目标设计:以 PhysicalState Transaction 统一属性提交

每次修改先构造不可变 PhysicalStateCandidate,包含单位归一后的材质、质量策略、模拟角色、碰撞矩阵、事件开关和网络 Authority。Planner 比较 Committed State 与 Candidate,生成有序操作:仅 Filter Patch、Mass Update、Wake,或 Full Body Rebuild。Executor 一次执行;任何失败都保留旧 Committed State,只有读取 Body 后验证成功才推进 Generation 并派发变更事件。

ApplyResult ApplyPhysicalChange(Body& body, const ChangeSet& input)
{
    Candidate next = ValidateAndNormalize(body.committed, input);
    Plan plan = BuildPlan(body.committed, next, body.authority);

    ConfigureCollisionAndContact(body, next);   // 必须先于潜在重建
    ExecuteSimulationTransition(body, plan);    // Patch 或 Recreate
    ApplyMaterialMassInertia(body, next);
    RestoreFilterIdentity(body, next);          // 最后写 marker/group

    Observation live = ObserveLiveBody(body);
    if (!Equivalent(live, next)) return KeepPreviousAndReport(live);
    return Commit(next, live, ++body.generation);
}

网络层只复制规范状态或权威变更,而不复制 UE 调用序列。客户端根据当前角色把 Candidate 物化为本地模拟、Kinematic Observer 或无 Body 的降级表示。属性事件应在提交成功后发出,不能在缓存写入后、Body 尚未一致时就通知脚本。

六、决策规则:属性更新的完成标准是 Body 事实一致

变更最小执行计划失败或竞态防护
Friction/Restitution更新材质,必要时更新接触缓存显示实际混合路径与最终系数
Density/Scale重算理论质量、惯量并按需 Wake单位和范围校验;不读失真的当前 Mass
Damping/Gravity当前未统一开放;新增时进入同一 Candidate 与 Authority 契约非负范围、Wake、世界/局部重力来源与跨端一致性
Anchored true -> false权威判定、预设过滤、启用接触修改、必要时重建、Wake重复包不重建有效动态 Body;客户端非 Owner 不抢跑
CanCollide/CanTouch原子更新 Block、Overlap、事件与 marker覆盖“穿透但触碰”和“阻挡且触碰”
CanQuery只改查询通道,但仍恢复 shape 身份查询结果与模拟结果分开验证
命名材质不存在保持旧值或显式默认,并记录结构化错误不把拼写错误伪装成正常手感
远端所有权接管禁止本地模拟,保留所需查询与触碰只有一个 Transform 写者,首包前不抢跑

工程中的最佳经验是承认 RecreatePhysicsState 不是实现细节,而是会改变事件、过滤、速度、质量和线程可见性的事务边界。下一步不应继续给每个新属性补一段调用顺序,而应让 Planner、Executor 和 Observation 成为统一框架,使任何字段都必须回答:谁有权改、是否重建、哪些状态会失效、何时才算提交成功。