DreamFX 是什么
它做什么、不做什么,以及几条贯穿始终的设计立场:文本是唯一真相、缺口必须显形、菜单不是第二套实现。
DreamFX 把文本编译成标准 Niagara 资产,并把标准 Niagara 资产反编译回文本。范围仅此而已 ——
它不是一个新的粒子运行时,也不是 Niagara 的替代品。生成出来的东西就是普通的
UNiagaraSystem / UNiagaraEmitter / UNiagaraScript:任何引擎加载它、任何 .dfs 引用它、
cook 它、跑它,都和手连出来的没有区别。
为什么要写成文本
Niagara 的编辑面是图和栈。图很好用,直到你需要:
- diff 一次改动 —— 谁把 spawn rate 从 20 改成 60 了?二进制资产回答不了;
- 批量改 40 个特效 —— 手点 40 遍,还是改 40 个文本文件?
- code review —— 一个
.uasset的 PR 只能看结论,看不到过程; - 让代理去做 —— 一个编码代理能写文本、跑构建、读诊断;它点不了 Slate 按钮。
文本化不是为了更"高级",是为了让版本控制、批量操作和自动化这三件事重新变得可能。
几条设计立场
文本是唯一真相,资产是构建产物
生成的资产上盖着溯源戳(源 hash + 生成器版本 + 模块版本 GUID)。手改它,下次重建铲掉;
改了源码不重建,verify 会把它抓出来。资产随时可以删掉重建,这句话必须一直是真的,
否则整个模型就塌了。
缺口必须显形,绝不静默丢
反编译一个 DreamFXLang 表达不了的东西时,它不会悄悄丢掉:那一条会写进导出文件的头部缺口注释,
并以一个 DFX8xxx 警告的形式报出来。Adopt 更严 —— 有任何缺口就直接拒绝接管。
一个静默降级的工具会让你在三个月后对着一个不对劲的特效发呆,而且没有任何线索。
菜单是入口,不是第二套实现
编辑器里每一个按钮都调用 commandlet 调用的同一份代码。Rebuild DFX 能用,dfx build -All
就能用,反之亦然。而且每个命令都能从 Python 调 —— 只有人手能点到的命令,是永远得不到回归测试的命令。
声称要能被测量
「反编译是无损的」这种话不算数,除非有个东西能把它证伪。所以有四层验证:文本逐行、镜像编译、 SimCache 运行时逐帧、以及绕开导出器的资产事实走查。第四层存在的理由很具体:前面几层比的是同一个 导出器的两份产物,导出器自己丢掉的东西,它们对称地看不见。
不做什么
| Scratch Pad | 不表达。Scratch Pad 脚本是资产内嵌的图,没有稳定名字 |
| 模块内部图 lowering | .dfm 的 body 落成一个 custom HLSL 节点,不铺成节点图(那是 DreamShader 已经有的那个约 1.3 万行的问题,重做一遍) |
| GPU/CPU 条件分支、Scalability 条件 | 未覆盖 |
| 真正的 emitter 继承 | from 是拷贝。编辑 .dfe 不会反向影响已经拷过它的系统,直到它们重建 |
| 通用表达式编译器 | 内联表达式只有算术和一张短白名单。要更多就写 hlsl { } 或 .dfm |
完整清单与 1.0.0 的已知问题在能力边界。
和 DreamShader 的关系
DreamShaderLang 是同一个作者的姊妹项目,对材质做同一件事:
.dsm / .dsf / .dsh 生成 UMaterial 与 UMaterialFunction。
两者共享设计立场(文本是源、诊断带位置、编辑器集成走同一条管线),但不共享代码, 语法也不通用。一个明显的分岔:DreamShader 有一个完整的表达式编译器,DreamFX 有意不做 —— Niagara 的模块生态已经把"计算"这件事解决了,DreamFX 的工作是把模块接起来, 而不是再发明一次算子。
名字与版本
| 插件 | DreamFX 1.0.0 |
| 语言 | DreamFXLang |
| 模块 | DreamFX(Runtime)、DreamFXEditor(Editor) |
| commandlet | -run=DreamFX |
| 驱动脚本 | .skill/dfx.ps1、.skill/ci.ps1 |
| 日志分类 | LogDreamFX |
| 作者 | TypeDreamMoon · MIT |