设计取向
最初的方案是「重组」—— 零件丢进机器里碾碎,按元素比例重新生成新物品。 做了一版之后推翻了,理由很直接:重组把玩家捡到的东西变成了数字。 一个阀门和一块铁皮在重组系统里没有区别,那玩家为什么要去找阀门?
于是改成直接从垃圾里拆零件:
你捡到一个坏掉的压缩机,拆开它,得到一个阀门、一段铜管、两颗螺栓。 这三样东西能拼成一台手动水泵。
零件有身份,玩家的每一次拆解都是在做选择。
抓取
抓取是这套系统里最容易出问题的一环。当前的实现:
bool UAC_AssemblySystem::TryGrab(const FHitResult& Hit)
{
AActor* Target = Hit.GetActor();
if (!IsValid(Target)) { return false; }
// 已经被人拿住的零件不能抢
if (GrabbedParts.Contains(Target)) { return false; }
// 抓住的必须是"零件"语义的物体
if (!Target->Implements<UAssemblyPart>()) { return false; }
GrabbedParts.Add(Target);
Target->AttachToComponent(HandSocket, ...);
return true;
}
踩过的坑
这里出过一个相当难查的运行时错误:某个 BP_123 actor 在被抓住后突然失效,
后续对它调用的每一条指令都崩。
根因是所有权:那个 actor 在生成时没有被任何持久对象引用,
当它被从原本的父级 detach 出来、又被垃圾回收扫描到的那一刻,就被回收了。
AttachTo 并不会建立 GC 意义上的引用 —— 它只改变换层级。
修法是同时持有软引用和硬引用:
/** 强引用,防止零件在手上被 GC 掉 */
UPROPERTY(Transient)
TObjectPtr<AActor> HeldPart;
/** 弱引用,用来在零件被别人销毁后安全地感知到 */
UPROPERTY(Transient)
TWeakObjectPtr<AActor> HeldPartWeak;
每次抓取后把两个都设上。凡是从容器里取物件做逻辑的地方,
第一步先 IsValid(),而不是直接解引用 —— 这条现在是这个模块的硬约定。
配方与匹配
配方在 DT_AssemblyRecipes 里,一行一条:
| 列 | 含义 |
|---|---|
RecipeId |
配方标识 |
RequiredParts |
需要的零件(TMap<FName,int32>,无序) |
RequiredTools |
需要工具 |
ResultItem |
产物 |
bConsumeParts |
零件是否消耗掉 |
匹配算法刻意写得很笨:先排序再逐项比对。 零件种类少(十几种),玩家一次最多摆五六个,性能完全不是问题; 而排序之后比对是确定性的 —— 同样的零件组合永远得到同样的判定, 不会因为玩家摆放顺序不同而时灵时不灵。
目前的进展
-
DT_AssemblyRecipes表结构 -
AC_AssemblySystem组件(挂到工作台与手持模式) -
TryGrab/TryRelease与吸附点 - GC 引用失效崩溃修复
- 抓取时的物理预览(零件之间的碰撞与导向)
- 多步合成(中间体参与后续配方)