项目火星番茄拼装合成系统

拼装合成系统

把捡来的零件按配方拼成能用的东西。抓取、吸附、配方匹配、产物生成。

进行中C++蓝图

设计取向

最初的方案是「重组」—— 零件丢进机器里碾碎,按元素比例重新生成新物品。 做了一版之后推翻了,理由很直接:重组把玩家捡到的东西变成了数字。 一个阀门和一块铁皮在重组系统里没有区别,那玩家为什么要去找阀门?

于是改成直接从垃圾里拆零件:

你捡到一个坏掉的压缩机,拆开它,得到一个阀门、一段铜管、两颗螺栓。 这三样东西能拼成一台手动水泵。

零件有身份,玩家的每一次拆解都是在做选择。

抓取

抓取是这套系统里最容易出问题的一环。当前的实现:

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 引用失效崩溃修复
  • 抓取时的物理预览(零件之间的碰撞与导向)
  • 多步合成(中间体参与后续配方)