为什么选择 ZTS
Puerts、自管 QuickJS 已经证明「在 Unity 里用 JS/TS」可行。ZTS 要解决的是下一层问题:把 JS↔C# 做成真正现代、完备、且在 Il2Cpp 上足够快、足够省的互操作——而不是再堆一套导出配置、白名单和手写绑定。
设计与 ZLua 同构(门面 / Marshal / 生命周期):会用 ZLua 即可很快上手 ZTS;Lua 与 TS 产品可并存。
七个理由(30 秒)
| 一句话 | |
|---|---|
| 更易用 | 设计贴近 C#;零 per-type Wrap 白名单;类型懒绑定 |
| 更完备 | 重载、ref/out、struct ByVal/ByObj、Nullable、委托、数组、指针、[TsMarshalAs] 等 |
| 更统一 | 与 ZLua 同一套语义契约;Host / Marshal / Exotic 心智对齐 |
| 更高效 | Player Il2Cpp 热路径为 C++ 桥接;签名复用 stub |
| 更少 GC | 引用类型与 struct 默认 Registry / ByVal;另有 Opaque 等策略 |
| 双运行时 | Editor Mono + 发布 Il2Cpp Player;JS 可见语义一致 |
| TS 一等公民 | TsProject、csharp: 声明、进 Play 闸门;运行时只跑 emit 后的 JS |
1. 更易用:现代、简单、零配置
传统方案的心智负担往往是:
- 维护导出列表 / 生成配置
- 改 API 就要重新 Generate 海量 Wrap
- C#→JS 走命令式
DoString/ 临时eval/ 路径字符串拼装
ZTS 把互操作做成接近 P/Invoke 的声明式模型:
| 你要做的事 | ZTS |
|---|---|
| C# 调 JS | TsAppDomain.GetFunction<T>(module, exportName) 取得 Delegate 后 Invoke |
| 覆盖 Marshal | [TsMarshalAs] |
| JS 访问 C# | CSharp 根对象懒加载,或 import { T } from "csharp:…" |
// 须在 Initialize 之后(例如 Awake),勿用 static 字段初始化器
var AppAdd = TsAppDomain.GetFunction<Func<int, int, int>>("app", "add");
// AppAdd(10, 20);
import { Demo } from "csharp:Assembly-CSharp";
console.log(Demo.Add(3, 5));
const d = new Demo();
d.Run(10); // 实例方法:obj.Method(args),无 Lua 冒号语法
零配置指:不需要 per-type C# Wrap 白名单与成员级 Wrap 工程。Editor 开箱即用;发 Il2Cpp Player 时执行一次 Generate(生成 C++ stub,不是托管 Wrap 海)。
模块 specifier canonical 不含 .js / .ts(如 "app"、"game/logic")。
2. 更完备:几乎能调到的 C# 都能调
目标不是「导出几个热路径 API」,而是 标准和完备的 C#↔JS 交互,包括但不限于:
| 类别 | 能力 |
|---|---|
| 类型 | class / struct / interface / enum / nullable |
| 成员 | 静态与实例:字段、属性、方法 |
| 高级 | 泛型类、泛型方法、delegate、数组(含多维) |
| 语言细节 | 方法重载、ref / out / in、Event(add_ / remove_) |
| 属性 | [TsAlias]、[TsExtension]、[TsMarshalAs] |
语义以 规范 为契约;双端(Mono Editor / Il2Cpp Player)JS 可见行为一致。
成员 miss 时 throw Error('zts: member not found: …'),不会静默返回 undefined。
3. 更高效:方法论对齐 ZLua,公开数字待补齐
Il2Cpp 上的性能目标与 ZLua 相同:去掉托管 Wrap 折返,在 C++ 里一次完成 marshal 与 methodPointer 调用;相同 ReducedType 签名 复用 stub。
互调再快,也要先 profiling。若脚本边界只占帧时间 2%,五倍互调也只省约 1.6%。ZTS 适合 战斗公式、UI、每帧大量小调用 这类边界热点。
4. 更少更快的 GC
默认策略面向热路径:
| 策略 | 含义 |
|---|---|
| 引用类型 | 默认走 ObjectRegistry / exotic object,避免无意义装箱与临时 object[] |
| struct | 默认可走 ByVal / ByObj 等路径(见规范);热路径面向少分配 |
| OpaqueValue | 临时句柄:同步调用链内更灵活的低分配策略 |
| enum | 默认 number,不强制 boxed 对象 |
需要写回时用 Opaque / ByVal exotic;裸 number 不回写(与 C# ref 语义对齐,见 ref/out/in)。
→ 少 GC Marshal · 生命周期规范
5. 极小的桥接:签名复用,而非每成员一 Wrap
| 方案 | 典型体积模型 |
|---|---|
| Puerts / 自管 QuickJS(手写) | 常随导出成员 / 绑定代码膨胀,或靠生成物维护 |
| ZTS(Il2Cpp) | 合并同签名 桥接函数,直接生成高效 C++ stub(ReducedType 复用) |
因此在「仍能访问几乎全部 C# 类型、字段、属性、方法」的前提下:
- 桥接代码体积通常远小于「每成员独立 Wrap」模型
- Editor(Mono)用 Expression Emit,不进 Player 包;Player 体积由 C++ stub 决定
6. 支持的 Unity 与 QuickJS
| 维度 | ZTS |
|---|---|
| JS 引擎 | QuickJS(pin 见包内 ZTS~/) |
| Unity | 2021.3、2022.3、Unity 6(6000.0 / 6000.3 / 6000.5) |
| 引擎 | 团结引擎 |
| 运行时 | Editor Mono + Player Il2Cpp |
完整矩阵见 兼容性。
→ 支持的版本与平台
7. 同族产品、维护积极
ZTS 与同族方案由同一产品线演进:
| 产品 | 引擎 / 宿主 | 状态 |
|---|---|---|
| ZTS(本站) | Unity / 团结 · C# | Alpha,本站文档覆盖 |
| ZLua | Unity / 团结 · Lua | 已发布文档站 |
| zts-ue | Unreal Engine · C++ | 开发中;仓库见 GitHub |
- 规范、术语、Marshal / 生命周期心智可在产品间对齐(语法面与宿主不同)
- Bug 响应与特性迭代可共享方法论
- 适合把脚本互操作当作 长期基础设施,而不是「停更的第三方插件」
不适合选 ZTS 的情况
诚实边界同样重要:
| 情况 | 建议 |
|---|---|
| 不愿维护 libil2cpp 集成 | 插件形态的 Puerts 或自管 QuickJS 可能更轻 |
| 强依赖 Puerts 现有导出管线 / 大量资产 | 先读 迁移 与 12-MIGRATION-ADAPTORS,评估迁移成本 |
| 只要极少量手写绑定、无完备互操作需求 | 自管 QuickJS 可能更直接 |
| 团队只写 Lua、不需要 JS/TS | 优先 ZLua |
| 宿主是 Unreal Engine | 关注 zts-ue(开发中);本站为 Unity 文档 |
下一步
延伸阅读
| 文档 | 内容 |
|---|---|
| 设计概览 | GetFunction 与双向桥接 |
| 双运行时 | Mono / Il2Cpp 分工 |
| 术语表 | Opaque / ByVal / stub 等 |
| Il2Cpp 实现 | Player 模块图 |