Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

gamelet-puerts-proxy

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

gamelet-puerts-proxy

TypeScript proxy library for remotely controlling Unity objects via Puerts through WebSocket/RPC

latest
npmnpm
Version
2.0.0
Version published
Weekly downloads
25
-83.55%
Maintainers
1
Weekly downloads
 
Created
Source

gamelet-puerts-proxy 开发者指南

gamelet-puerts-proxy 是一个 TypeScript 库,允许你在 WebView(异步 RPC)或 Puerts 内部 V8(同步直连)中,以完全相同的代码远程操作 Unity 中的 C# 对象。

它通过 Proxy 拦截本地访问,把调用打包成 ReflectPayload,再交给底层 Transport 执行:

Transport运行环境派发方式性能
AsyncTransport (mode=1)外部 WebViewexternal.sendMessage JSON RPC受 IPC 延迟影响
NativeTransport (mode=0)Puerts 同 V8直接读 globalThis.CSharp / globalThis.Puerts接近原生

业务侧 await 一次写完,两种模式都能跑。

目录

  • 核心差异:从 Puerts 到 gamelet-puerts-proxy
  • Transport 配置
  • 快速开始
  • API 参考
  • 场景用例对照表
  • 常见问题

核心差异:从 Puerts 到 gamelet-puerts-proxy

1. 全异步操作

所有与 Unity 交互的操作(方法调用、属性获取、对象创建)都是异步的,必须 await

操作原生 Puertsgamelet-puerts-proxy
方法调用go.SetActive(true)await go.SetActive(true)
获取返回值let x = tf.positionlet x = await tf.$get('position')
对象创建new Vector3(1,2,3)await (new Vector3(1,2,3)).$await()

同步模式(NativeTransport)下,await 一个已 resolve 的 Promise 仅产生纳秒级 microtask 开销,不会引入 IPC 延迟。

2. 属性访问 (Get/Set)

JavaScript Proxy 无法把同步属性读取转换成异步操作,因此属性访问有特殊语法。

  • 获取属性await proxy.$get('propertyName')
  • 设置属性
    • fire-and-forgetproxy.name = 'NewName'(不等待,无返回值,由 set trap 走 notify
    • awaitableawait proxy.$set('name', 'NewName')(等待 Unity 端执行完成)

3. 泛型方法

TypeScript 泛型在运行时会被擦除,需要显式传递类型信息。

写法含义
await go.GetComponent(PuertsProxy.$typeof('Animator'))推荐:Proxy 自动识别 args 中的 $typeof 标记,剥离为泛型参数
await go.$call('GetComponent', [], undefined, ['UnityEngine.Animator, UnityEngine.AnimationModule'])显式:用于复杂重载

Transport 配置

ts-proxy 内部抽象出一层 Transport,对外只暴露两个 API:configureTransportgetTransport。所有 Reflect 调用最终都走当前 transport,业务代码不需要关心选了哪一个。

1. 模式

模式值实现适用场景
0NativeTransportts-proxy 与 Puerts 同 V8(如 Puerts 内部脚本),直接读 globalThis.CSharp
1AsyncTransportts-proxy 在 WebView,通过 external.sendMessage 与主进程 Puerts 通信(默认

2. 显式配置

import { configureTransport, getTransport } from 'gamelet-puerts-proxy';

// 入口处显式选择模式(推荐在第一次 reflect 调用前调用一次)
configureTransport({ mode: 0 });   // 同步:Puerts 同 V8
configureTransport({ mode: 1 });   // 异步:WebView RPC

// 检查当前 transport
const t = getTransport();
console.log(t.mode);  // 'sync' | 'async'

行为说明:

  • 重复调用、mode 一致 → 直接返回原 transport。
  • mode 变更 → 打 warn 后切换。
  • 从未调用 configureTransport,第一次 getTransport() 默认创建 AsyncTransport(mode=1) 并打日志提示 fallback。
  • NativeTransport 构造时若 globalThis.CSharp 不存在会抛错,请确保在 Puerts 主机环境下才传 mode: 0

3. ITransport 接口(一般业务无需关心)

interface ITransport {
    readonly mode: 'sync' | 'async';

    /** 发起 RPC 调用,等待返回值。同步模式下用 Promise.resolve 包装。 */
    call(payload: ReflectPayload): Promise<any>;

    /** 单向通知,fire-and-forget。 */
    notify(payload: ReflectPayload): void;

    /** 注册 JS 回调,返回 funcId(异步模式传给后端,同步模式本地映射)。 */
    registerCallback(fn: (...args: any[]) => any): string;
}

ReflectPayload 字段(双模式共用):

字段说明
opget / set / has / invoke / create / release / addListener / removeListener
type完整类型名 Type, Assembly(静态调用 / 创建对象时必填)
member成员名
target实例引用 { $ref, $type }
args已经过 packArg 的参数列表
genericTypes泛型类型参数(AssemblyQualifiedName 数组)
paramTypes重载消歧用的参数类型
isStatic是否静态调用

快速开始

1. 初始化

import {
    UnityEffectProxy,
    PuertsProxy,
    configureTransport,
} from 'gamelet-puerts-proxy';

// (可选)显式选择 transport:mode 0 = 同步,1 = 异步
configureTransport({ mode: 1 });

// 获取当前上下文绑定的 GameObject
const go = await UnityEffectProxy.getMountingGameObject(windowId, handle);

2. 常用类型

UnityEffectProxy 提供了常用类型的快捷访问,这些都是完整类型代理,可直接调静态方法、new

const GameObject = UnityEffectProxy.GameObject;
const Vector3 = UnityEffectProxy.Vector3;
const Transform = UnityEffectProxy.Transform;
// 其他:Vector2 / Quaternion / Color

// 不在快捷列表里的类型用 type() 获取
const Debug = UnityEffectProxy.type('UnityEngine.Debug, UnityEngine.CoreModule');

3. 完整示例

// 1. 静态方法
await Debug.Log('Hello from Proxy');

// 2. 创建对象(注意 $await)
const vec = await (new UnityEffectProxy.Vector3(0, 1, 0)).$await();

// 3. 实例方法
const transform = await go.GetComponent(PuertsProxy.$typeof('Transform'));
await transform.Translate(vec);

// 4. 泛型方法(args 中的 $typeof 标记会被自动识别为泛型参数)
const animator = await go.GetComponent(PuertsProxy.$typeof('Animator'));

// 5. 属性操作
const pos = await transform.$get('position');   // get:必须用 $get
go.name = 'RenamedObject';                       // set:fire-and-forget
await go.$set('name', 'RenamedObject');          // set:等待完成

API 参考

UnityEffectProxy

核心代理类,用于创建和管理代理对象。所有方法均为静态。

方法 / 属性说明
getMountingGameObject(windowId, handle)获取当前上下文挂载的 GameObject 代理(带缓存)
type(assemblyQualifiedName)通过完整 Type, Assembly 创建完整类型代理(支持静态方法、new$get / $set 静态成员)
namespace(ns, assembly)创建命名空间代理,按需 lazy 创建子类型代理(不缓存)
logProxy(label, proxy)安全打印代理元数据(ref / type / windowId / handle),不会触发远程调用
clearCache()清除所有代理缓存(GameObject 缓存 + 远程对象缓存),调试 / 切场景时使用
call(payload)直接走当前 transport 发送原始 ReflectPayload(调试 / 绕过 Proxy)
GameObject / Transform / Vector3 / Vector2 / Quaternion / Color常用类型的快捷代理(getter,每次返回新代理实例)

获取类型时,优先使用 UnityEffectProxy.type() 或快捷属性。只有它创建的代理才支持静态方法调用

代理对象实例方法

所有由 getMountingGameObject / 方法返回值 / $get 等产生的实例代理都拥有以下成员:

方法 / 属性说明
$get(member)异步获取属性值
$set(member, value)异步设置属性值(等待完成)
$has(member)异步检查成员是否存在
$call(member, args, paramTypes?, genericTypes?)显式调用方法(用于重载消歧 / 复杂泛型)
$addListener(event, callback)添加事件监听
$removeListener(event, callback)移除事件监听(必须用同一个函数引用)
$release()清除该代理的本地缓存并通知 Unity 释放远程引用(只清自己,不递归
$ref (属性)远程对象 ID
$type (属性)远程对象类型名
$raw (属性)原始 remote 元数据 { id, type, windowId, handle }
$inspect()返回完整调试信息对象

泛型自动识别:常规方法调用 proxy.Foo(arg1, arg2, $typeof('T')) 时,参数列表中的 $typeof 标记会被自动剥离为 genericTypes。需要更精细的控制时改用 $call

PuertsProxy

类型辅助类,主要用于泛型参数传递和类型简写注册

方法说明
$typeof(shortNameOrFullName)创建类型标记(仅用于泛型参数传递)。支持简写 'Animator' 或完整名 'UnityEngine.Animator'。结果带缓存。
registerType(shortName, fullTypeName)注册单个简写映射,fullTypeName 必须带 Assembly
registerTypes(typeMap)批量注册简写映射
hasType(typeName)该简写是否已注册
getFullTypeName(typeName)解析简写到 AssemblyQualifiedName;含逗号视作已是完整名直接返回;未注册会 fallback 到 , UnityEngine.CoreModule 并 warn
getTypeMap()返回当前类型映射表副本(调试用)
isTypeProxy(obj)检查对象是否为 $typeof 产生的类型标记
getTypeName(typeProxy)从类型标记中提取类型名(不是标记返回 undefined

$typeof() vs type() —— 不要混用:

PuertsProxy.$typeof()UnityEffectProxy.type()
产物类型标记(普通 object,仅带一个 Symbol)完整类型代理(Proxy 函数对象)
用途仅用于泛型参数传递静态方法 / new / 静态属性
静态方法❌ 不支持✅ 支持
// ❌ 错误:$typeof 不支持静态方法
await PuertsProxy.$typeof('GameObject').Find('Player');

// ✅ 正确:用 UnityEffectProxy 的快捷属性 / type()
await UnityEffectProxy.GameObject.Find('Player');

// ✅ 正确:$typeof 用于泛型参数
const tr = await go.GetComponent(PuertsProxy.$typeof('Transform'));

Transport 模块

方法 / 类型说明
configureTransport(cfg)显式选择 transport,cfg.mode0(同步)或 1(异步)
getTransport()获取当前 transport;未配置时默认 AsyncTransport(mode=1)
ITransportTransport 接口,含 mode / call / notify / registerCallback
AsyncTransport / NativeTransport两种内置实现
ReflectPayload通用 reflect 操作 payload 类型
TransportModeValue字面类型 `0
ProxyTransportConfigconfigureTransport 入参类型

工具函数

函数说明
enablePuertsLog(enable)开关内部 Logger.logwarn / error 始终输出,默认关闭普通日志)
import { enablePuertsLog } from 'gamelet-puerts-proxy';
enablePuertsLog(true);

场景用例对照表

对象创建与构造函数

场景原生 Puertsgamelet-puerts-proxy
创建 Vector3new Vector3(1, 2, 3)await (new UnityEffectProxy.Vector3(1, 2, 3)).$await()
创建 GameObjectnew GameObject('Name')await (new UnityEffectProxy.GameObject('Name')).$await()
创建自定义类new MyClass()await (new MyType()).$await()MyType = UnityEffectProxy.type('Ns.MyClass, Asm')
const pos = await (new UnityEffectProxy.Vector3(0, 1, 0)).$await();
const go  = await (new UnityEffectProxy.GameObject('MyObject')).$await();

属性操作

场景原生 Puertsgamelet-puerts-proxy
获取属性go.nameawait go.$get('name')
设置属性go.name = 'New'go.name = 'New' (fire-and-forget)
设置属性(等待)go.name = 'New'await go.$set('name', 'New')
检查属性存在'transform' in goawait go.$has('transform')
链式属性获取go.transform.position.x必须分步 $get
const name = await go.$get('name');
go.name = 'NewName';                              // 不等待
await go.$set('name', 'NewName');                 // 等待完成
const hasTransform = await go.$has('transform');

// 链式获取(每层都要 await + $get)
const transform = await go.$get('transform');
const position  = await transform.$get('position');
const x         = await position.$get('x');

方法调用

场景原生 Puertsgamelet-puerts-proxy
实例方法go.SetActive(true)await go.SetActive(true)
静态方法GameObject.Find('Player')await UnityEffectProxy.GameObject.Find('Player')
泛型方法go.GetComponent<Transform>()await go.GetComponent(PuertsProxy.$typeof('Transform'))
重载消歧go.SendMessage('Foo', 123)await go.$call('SendMessage', ['Foo', 123], ['System.String', 'System.Int32'])
// 实例方法
await go.SetActive(true);

// 静态方法
const player = await UnityEffectProxy.GameObject.Find('Player');

// 泛型方法 —— args 中的 $typeof 标记会被自动识别为泛型参数
const animator  = await go.GetComponent(PuertsProxy.$typeof('Animator'));
const transform = await go.GetComponent(PuertsProxy.$typeof('Transform'));

// 显式调用(用于 property getter / setter / 重载)
const forward = await transform.$call('get_forward');
await transform.$call('set_localPosition', [newPos]);

// 带 paramTypes 的重载消歧
await go.$call('SendMessage', ['MyMethod', 42], ['System.String', 'System.Int32']);

组件操作

// 获取组件
const transform = await go.GetComponent(PuertsProxy.$typeof('Transform'));
const animator  = await go.GetComponent(PuertsProxy.$typeof('Animator'));

// 添加组件(未注册简写时,传完整 'Type, Assembly')
const button = await go.AddComponent(PuertsProxy.$typeof('Button'));

// 获取所有组件 + 遍历
const components = await go.GetComponents(PuertsProxy.$typeof('Component'));
const count      = await components.$get('Length');
for (let i = 0; i < count; i++) {
    const comp = await components.GetValue(i);
    const t    = await comp.GetType();
    const name = await t.$get('Name');
    console.log(`Component[${i}]: ${name}`);
}

事件监听

场景原生 Puertsgamelet-puerts-proxy
添加监听button.onClick.AddListener(cb)await button.$addListener('onClick', cb)
移除监听button.onClick.RemoveListener(cb)await button.$removeListener('onClick', cb)
const button = await go.GetComponent(PuertsProxy.$typeof('Button'));

// 必须保留同一个引用才能 remove
const onClick = async () => console.log('clicked!');

await button.$addListener('onClick', onClick);
await button.$removeListener('onClick', onClick);

对象销毁

const UnityObject = UnityEffectProxy.type('UnityEngine.Object, UnityEngine.CoreModule');

await UnityObject.Destroy(go);            // 异步销毁
await UnityObject.Destroy(component);     // 销毁组件
await UnityObject.DestroyImmediate(go);   // 立即销毁

子对象操作

const tr       = await go.GetComponent(PuertsProxy.$typeof('Transform'));
const parentTr = await parentGo.GetComponent(PuertsProxy.$typeof('Transform'));

await tr.SetParent(parentTr, false);      // false = 保持世界坐标
const childCount = await tr.$get('childCount');
const child      = await tr.GetChild(0);
const found      = await tr.Find('ChildName');

类型系统

// 1. 快捷类型(推荐)
const GameObject = UnityEffectProxy.GameObject;
const Vector3    = UnityEffectProxy.Vector3;

// 2. 完整类型代理(任意类型)
const Debug  = UnityEffectProxy.type('UnityEngine.Debug, UnityEngine.CoreModule');
const Button = UnityEffectProxy.type('UnityEngine.UI.Button, UnityEngine.UI');

// 3. 命名空间代理(按需 lazy 创建子类型,不缓存)
const UI = UnityEffectProxy.namespace('UnityEngine.UI', 'UnityEngine.UI');
const ButtonType = UI.Button;             // 等价于上面的 Button

// 4. 注册自定义类型简写(fullTypeName 必须带 Assembly)
PuertsProxy.registerType(
    'PlayerController',
    'Game.Controllers.PlayerController, Assembly-CSharp'
);
PuertsProxy.registerTypes({
    'EnemyAI':         'Game.AI.EnemyAI, Assembly-CSharp',
    'InventorySystem': 'Game.Systems.InventorySystem, Assembly-CSharp',
});

// 5. 用简写传递泛型参数
const ctrl = await go.GetComponent(PuertsProxy.$typeof('PlayerController'));

⚠️ 常见错误registerType('MyButton', 'UnityEngine.UI.Button')缺 Assembly!必须写成 'UnityEngine.UI.Button, UnityEngine.UI',否则 getFullTypeName 会 fallback 到 , UnityEngine.CoreModule 并报错。

调试技巧

// 安全打印代理元数据(不会触发远程调用)
UnityEffectProxy.logProxy('MyObject', go);
// [PuertsProxy] [MyObject] {"ref":10001,"type":"UnityEngine.GameObject, ...","windowId":1,"handle":2}

// 直接读调试属性
console.log(go.$ref, go.$type, go.$raw);
console.log(go.$inspect());                   // { ref, type, windowId, handle }

// 检查 transport 模式
import { getTransport } from 'gamelet-puerts-proxy';
console.log('transport mode:', getTransport().mode);

// 类型注册表
console.log(PuertsProxy.getTypeMap());

// 切场景 / 强制刷新时清缓存
UnityEffectProxy.clearCache();

// 释放单个远程引用(只清自己,不递归)
go.$release();

常见问题

Q: console.log(go) 看不到属性? A: 代理是空的,所有数据都在 Unity 端。用 UnityEffectProxy.logProxy('Label', go)go.$inspect() 查看元数据。

Q: 报错 Cannot read property 'then' of undefined A: 多半忘了 await,或者直接访问了 go.transform.position(链式访问),改成分步 await proxy.$get('xxx')

Q: 如何传递回调函数? A: 直接传 JS 函数即可,packArg 会自动注册并发 funcId。但要 removeListener 必须保留同一个引用:

const cb = async () => console.log('clicked');
await button.$addListener('onClick', cb);
await button.$removeListener('onClick', cb);

Q: 泛型方法怎么调用? A: 用 PuertsProxy.$typeof() 传类型:

const anim = await go.GetComponent<Animator>();                       // ❌ 泛型被擦除
const anim = await go.GetComponent(PuertsProxy.$typeof('Animator'));  // ✓

Q: 如何处理方法重载? A: 用 $call 显式指定 paramTypes

await go.$call('SendMessage', ['MyMethod', 42], ['System.String', 'System.Int32']);

Q: 属性赋值后如何确认生效? A: 用 $set 替代直接赋值:

go.name = 'New';                  // fire-and-forget,不等待
await go.$set('name', 'New');     // 等待 Unity 端执行完成

Q: 数组返回值怎么用? A: 数组也是代理,用 $get('Length') + GetValue(index)

const arr = await go.GetComponents(PuertsProxy.$typeof('Component'));
const n   = await arr.$get('Length');
for (let i = 0; i < n; i++) {
    const item = await arr.GetValue(i);
}

Q: 同 V8 怎么避免异步开销? A: 入口处显式 configureTransport({ mode: 0 }),走 NativeTransport。业务代码不用改,await 只剩纳秒级 microtask 开销。

Q: 自定义类型 Method not found 报错? A: 检查两点:

  • registerType 的 fullTypeName 必须带 Assembly(如 , Assembly-CSharp),否则 fallback 到 UnityEngine.CoreModule 必然解析失败。
  • 静态方法必须从类型代理调用(UnityEffectProxy.type() 或快捷属性),不能从实例代理调用。

Q: $release() 是递归释放整棵子树吗? A: 不是。它只清除当前代理在 remoteProxyCache 的 entry 并 notify Unity 释放对应远程引用,子节点引用不受影响。要清空整套缓存用 UnityEffectProxy.clearCache()

Q: Reflect error: Unknown reflect operation: has 报错? A: $has 需要 Unity 端 puerts-runtime 支持该 op,请升级 runtime 或改用 $get + 判 null

Keywords

puerts

FAQs

Package last updated on 28 May 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts