物理属性:一次变更必须原子提交到 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 成为统一框架,使任何字段都必须回答:谁有权改、是否重建、哪些状态会失效、何时才算提交成功。