Skip to main content

设计概览

谁该读本文

选型者、新接入开发者、需要理解「为什么这样设计」的读者。 日常 API 用法请直接看 使用指南;实现细节见 规范文档

ZTS 把 JavaScript 当作另一种 Native:类比 P/Invoke,用声明式 API 统一双向互操作;Il2Cpp 侧生成 C++ stub不是 海量托管 Wrap。语义与 ZLua 对齐,引擎与语法面换成 QuickJS / TypeScript·JavaScript

P/Invoke 与 ZTS 对照

C# 互操作职责ZTS 对应
P/InvokeC# 调用 native 函数GetFunction<T> — C# 调用 JS(ES module 导出)
MonoPInvokeCallbacknative 回调 C#委托 / 回调桥(见 FUNCTION
MarshalAs覆盖默认 Marshal[TsMarshalAs] — C# ↔ JS Marshal 覆盖
flowchart LR
subgraph CSharp["C# 游戏代码"]
GF["GetFunction → Invoke"]
APP["业务类 public API"]
end

subgraph Bridge["自动生成桥接"]
MonoB["Editor: C# MethodBridge Emit"]
Il2B["Player: C++ 直桥"]
DelB["Delegate 桥(C#→JS)"]
end

subgraph JS["QuickJS / ES module"]
MOD["export function …"]
CS["CSharp / csharp: 类型访问"]
end

GF --> DelB
DelB --> MOD
CS --> MonoB
CS --> Il2B
MonoB --> APP
Il2B --> APP

核心原则

原则说明
统一双向调用C#→JS:TsAppDomain.GetFunction<T>;JS→C#:CSharp 懒注册或 import from "csharp:…",语法贴近 C#
自动生成(JS→C#)Editor Emit / Il2Cpp Generate C++ stub;C#→JS 无 per-call codegen
深度集成TsAppDomain.Initialize 一次完成 CLR + QuickJS(JSRuntime + 主 JSContext)+ zts 库;热更清空走 Reset
C++ 直桥Player 字段 offset 直读、方法经 methodPointer,无海量 C# Wrap
零 Wrapper 膨胀相同签名共享桥接函数,而非每成员一个 Wrap
strict miss未注册成员 throw Error,不回退反射、不返回 undefined

自动生成流水线(JS→C#)

flowchart TB
A[开发者编写 C# + JS/TS] --> B{Unity 构建阶段}
B -->|Editor 程序集编译| C[首次 CSharp / csharp: 访问 EnsureBinding]
C --> D[Expression Emit MethodBridge]
B -->|Il2Cpp Player 构建| E[扫描类型绑定 + ReducedType]
E --> F[生成 C++ MethodBridge / DelegateBridge 模板]
F --> G[libil2cpp/zts 链接进 Player]
D --> H[Mono 运行时: 反射 + Expression 编译缓存]
G --> I[Il2Cpp 运行时: C++ 直调 QuickJS API]
阶段Mono (Editor)Il2Cpp (Player)
C#→JSGetFunction + Delegate 桥同左(native 路径)
JS→C# 成员首次访问 EnsureBinding + EmitEnsureBinding + C++ stub(Generate)
开发者感知无 C# Wrap无 C# Wrap;须 Generate stub

Host API 一瞥

TsAppDomain.Initialize(moduleLoader);
var onTick = TsAppDomain.GetFunction<Action<float>>("game/logic", "onTick");
// onTick(deltaTime);
  • jsModuleES module specifier(canonical 不含 .js / .ts
  • jsExportName 为该模块的 named export
  • 热路径请缓存 Delegate;Reset 生效后旧委托作废,须重新 GetFunction

JS 侧访问 C#:

// 推荐:csharp: 虚拟模块
import { Demo } from "csharp:Assembly-CSharp";

// 权威低层:CSharp 根对象
const Demo2 = CSharp["Assembly-CSharp"].Demo;

实例方法使用 点号调用 obj.Method(args) Lua : 语法)。

与 Puerts / 自管 QuickJS 的路径差异(摘要)

维度Puerts 常见路径自管 QuickJSZTS
类型暴露生成 / 导出配置手写绑定CSharp + csharp: + Exotic 三表
C#→JS视方案(常见 DoString / 路径拼装)手写GetFunction<T> + Invoke
Player 性能成熟路径因版本而异自建C++ 直桥 + 签名复用
与 ZLua不同同构语义

详见 选型对比Il2Cpp 实现

何时读哪份文档

你的问题推荐阅读
怎么从 C# 调 JS?C# 调用 JS
JS 怎么访问 C# 类型?JS 调用 C#
参数怎么传递?Marshal 模型概览
Editor 与 Player 差别?双运行时
TypeScript 工程?TypeScript 工作流
完整设计语义?设计规范

相关文档