npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gamelet-puerts-proxy

v1.0.0

Published

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

Readme

gamelet-puerts-proxy 开发者指南

gamelet-puerts-proxy 是一个 TypeScript 库,允许你在后台(或其他非 Unity 环境)通过 WebSocket/RPC 远程操作 Unity 中的对象。它通过 Proxy 技术拦截本地调用,将其转换为 JSON 消息发送给 Unity 端的 puerts-runtime 执行。

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

如果你熟悉直接在 Puerts 中编写代码,迁移到 gamelet-puerts-proxy 主要需要适应以下变化:

1. 全异步操作

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

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

2. 属性访问 (Get/Set)

JavaScript 的 Proxy 无法拦截同步的属性读取并将其转换为异步操作,因此属性访问有特殊语法。

  • 获取属性 (Get): 必须使用 $get('propertyName')
  • 设置属性 (Set):
    • Fire-and-forget: 直接赋值 go.name = "NewName"(不等待完成,无返回值)。
    • Awaitable: 使用 await go.$set('name', "NewName")(等待 Unity 端执行完成)。

3. 泛型方法

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

  • 原生: go.GetComponent<Animator>()
  • Proxy: await go.GetComponent(PuertsProxy.$typeof('Animator'))

快速开始

1. 初始化

通常由框架负责初始化连接,你只需要获取入口对象(如挂载点 GameObject)。

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

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

2. 常用类型

UnityEffectProxy 提供了常用类型的快捷访问:

const GameObject = UnityEffectProxy.GameObject;
const Vector3 = UnityEffectProxy.Vector3;
const Transform = UnityEffectProxy.Transform;
const Debug = UnityEffectProxy.type('UnityEngine.Debug, UnityEngine.CoreModule');

3. 完整示例

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

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

// 3. 实例方法
await go.transform.Translate(vec);

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

// 5. 属性操作
// Get: 必须用 $get
const pos = await go.transform.$get('position');
// Set: 可以直接赋值
go.name = "RenamedObject";

API 参考

UnityEffectProxy

核心代理类,用于创建和管理代理对象。

  • getMountingGameObject(windowId, handle): 获取当前挂载的 GameObject。
  • type(assemblyQualifiedName): 获取任意 C# 类型的完整类型代理(支持静态方法调用、静态属性访问)。
  • namespace(ns, assembly): 创建命名空间代理。
  • logProxy(label, proxy): 打印代理对象的调试信息(ID, 类型等)。
  • 快捷类型属性: GameObject, Transform, Vector3, Vector2, Quaternion, Color - 用于静态方法调用和对象创建。

提示: 获取类型优先使用 UnityEffectProxy.type() 或快捷属性(如 UnityEffectProxy.GameObject),支持静态方法调用。

代理对象实例方法 (Instance Methods)

所有代理对象(GameObject, Component 等)都拥有以下特殊方法:

  • $get(propName): 异步获取属性值。
  • $set(propName, value): 异步设置属性值。
  • $has(propName): 异步检查属性是否存在。
  • $call(methodName, args, paramTypes?, genericTypes?): 显式调用方法(用于处理重载或复杂泛型)。
  • $addListener(event, callback): 添加事件监听。
  • $removeListener(event, callback): 移除事件监听。
  • $release(): 释放远程对象引用(清除缓存并通知 Unity)。
  • $ref: (属性) 获取远程对象的 ID。
  • $type: (属性) 获取远程对象的类型名。
  • $raw: (属性) 获取原始远程对象数据。
  • $inspect(): 获取完整调试信息。

PuertsProxy

类型辅助类,主要用于泛型参数传递

  • $typeof(shortNameOrFullName): 创建类型标记,仅用于泛型参数传递(如 GetComponent)。
    • 支持简写: Animator
    • 支持全名: UnityEngine.Animator
  • registerType(shortName, fullName): 注册自定义类型的简写映射。
  • registerTypes(typeMap): 批量注册类型映射。
  • hasType(typeName): 检查类型是否已注册。
  • getFullTypeName(typeName): 获取类型的完整 AssemblyQualifiedName。

重要区分

  • PuertsProxy.$typeof() → 创建类型标记,仅用于泛型参数传递,不支持静态方法调用
  • UnityEffectProxy.type() → 创建完整类型代理支持静态方法调用、静态属性访问
// ❌ 错误:$typeof 不支持静态方法
await PuertsProxy.$typeof('GameObject').Find("Player");

// ✅ 正确:使用 UnityEffectProxy.type() 或快捷属性
await UnityEffectProxy.GameObject.Find("Player");
await UnityEffectProxy.type('UnityEngine.GameObject, UnityEngine.CoreModule').Find("Player");

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

工具函数

  • enablePuertsLog(enable: boolean): 开启/关闭 Puerts 日志输出(默认关闭)。

场景用例对照表

以下对照表帮助 Puerts 开发者快速理解 gamelet-puerts-proxy 的写法差异。

对象创建与构造函数

| 场景 | 原生 Puerts | gamelet-puerts-proxy | | :--- | :--- | :--- | | 创建 Vector3 | new Vector3(1, 2, 3) | await (new Vector3(1, 2, 3)).$await() | | 创建 GameObject | new GameObject("Name") | await (new GameObject("Name")).$await() | | 创建自定义类 | new MyClass() | await (new CustomType()).$await() |

// gamelet-puerts-proxy 示例
const pos = await (new UnityEffectProxy.Vector3(0, 1, 0)).$await();
const go = await (new UnityEffectProxy.GameObject("MyObject")).$await();

属性操作

| 场景 | 原生 Puerts | gamelet-puerts-proxy | | :--- | :--- | :--- | | 获取属性 | go.name | await go.$get('name') | | 设置属性 | go.name = "New" | go.name = "New" (fire-and-forget) | | 设置属性(等待) | go.name = "New" | await go.$set('name', "New") | | 检查属性存在 | 'transform' in go | await go.$has('transform') | | 链式属性获取 | go.transform.position | await (await go.$get('transform')).$get('position') |

// gamelet-puerts-proxy 示例
const name = await go.$get('name');
go.name = "NewName";                              // 不等待完成
await go.$set('name', "NewName");                 // 等待完成
const hasTransform = await go.$has('transform');  // true

// 链式获取属性
const transform = await go.$get('transform');
const position = await transform.$get('position');
const x = await position.$get('x');

方法调用

| 场景 | 原生 Puerts | gamelet-puerts-proxy | | :--- | :--- | :--- | | 实例方法 | go.SetActive(true) | await go.SetActive(true) | | 静态方法 | GameObject.Find("Player") | await GameObject.Find("Player") | | 带返回值 | go.GetComponent<Transform>() | await go.GetComponent(PuertsProxy.$typeof('Transform')) | | 显式调用 | go.SendMessage("Foo", 123) | await go.$call('SendMessage', ["Foo", 123]) |

// gamelet-puerts-proxy 示例 - 实例方法
await go.SetActive(true);

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

// 泛型方法 - 必须使用 PuertsProxy.$typeof 传递类型
const animator = await go.GetComponent(PuertsProxy.$typeof('Animator'));
const transform = await go.GetComponent(PuertsProxy.$typeof('Transform'));

// 显式方法调用(用于处理重载)
const forward = await transform.$call('get_forward');
await transform.$call('set_localPosition', [newPos]);

// 带参数类型指定的调用(用于方法重载)
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'));

// 添加组件
const button = await go.AddComponent(PuertsProxy.$typeof('UnityEngine.UI.Button, UnityEngine.UI'));

// 获取所有组件
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 type = await comp.GetType();
    const typeName = await type.$get('Name');
    console.log(`Component[${i}]: ${typeName}`);
}

事件监听

| 场景 | 原生 Puerts | gamelet-puerts-proxy | | :--- | :--- | :--- | | 添加监听 | button.onClick.AddListener(() => {}) | await button.$addListener('onClick', callback) | | 移除监听 | button.onClick.RemoveListener(cb) | await button.$removeListener('onClick', callback) |

// gamelet-puerts-proxy 示例 - 事件监听
const button = await go.GetComponent(PuertsProxy.$typeof('UnityEngine.UI.Button, UnityEngine.UI'));

// 定义回调函数(需要保存引用才能移除)
const clickCallback = async () => {
    console.log("Button clicked!");
};

// 添加监听
await button.$addListener('onClick', clickCallback);

// 移除监听(必须使用同一个函数引用)
await button.$removeListener('onClick', clickCallback);

// 注意:传统的 onClick.AddListener 写法也支持,但推荐使用 $addListener
// const onClick = await button.$get('onClick');
// await onClick.AddListener(clickCallback);

对象销毁

// 销毁 GameObject
const UnityObject = UnityEffectProxy.type('UnityEngine.Object, UnityEngine.CoreModule');
await UnityObject.Destroy(go);

// 销毁组件
await UnityObject.Destroy(component);

// 立即销毁
await UnityObject.DestroyImmediate(go);

子对象操作

// 设置父对象
const transform = await go.GetComponent(PuertsProxy.$typeof('Transform'));
const parentTransform = await parentGo.GetComponent(PuertsProxy.$typeof('Transform'));
await transform.SetParent(parentTransform, false);  // false = 保持世界坐标

// 获取子对象数量
const childCount = await transform.$get('childCount');

// 获取子对象
const child = await transform.GetChild(0);

// 查找子对象
const foundChild = await transform.Find("ChildName");

类型系统

// 预定义类型快捷访问
const GameObject = UnityEffectProxy.GameObject;
const Transform = UnityEffectProxy.Transform;
const Vector3 = UnityEffectProxy.Vector3;
const Vector2 = UnityEffectProxy.Vector2;
const Quaternion = UnityEffectProxy.Quaternion;
const Color = UnityEffectProxy.Color;

// 通过完整类型名获取
const Debug = UnityEffectProxy.type('UnityEngine.Debug, UnityEngine.CoreModule');
const Button = UnityEffectProxy.type('UnityEngine.UI.Button, UnityEngine.UI');

// 通过命名空间获取
const UnityEngineUI = UnityEffectProxy.namespace('UnityEngine.UI', 'UnityEngine.UI');
const ButtonType = UnityEngineUI.Button;

// 注册类型简写(用于泛型参数)
PuertsProxy.registerType('MyButton', 'UnityEngine.UI.Button');
const btnType = PuertsProxy.$typeof('MyButton');  // 等同于 UnityEngine.UI.Button

// 使用 $typeof 传递类型参数
const animator = await go.GetComponent(PuertsProxy.$typeof('Animator'));
const rectTransform = await go.GetComponent(PuertsProxy.$typeof('RectTransform'));

调试技巧

// 查看代理对象元数据
UnityEffectProxy.logProxy('MyObject', go);
// 输出: [MyObject] ref: 10001, type: UnityEngine.GameObject...

// 获取代理信息
const ref = go.$ref;          // 远程对象 ID
const type = go.$type;        // 类型名
const info = go.$inspect();   // 完整调试信息

console.log(`对象 ID: ${ref}, 类型: ${type}`);

API 参考

UnityEffectProxy

核心代理类,用于创建和管理代理对象。

| 方法/属性 | 说明 | | :--- | :--- | | getMountingGameObject(windowId, handle) | 获取当前挂载的 GameObject | | type(assemblyQualifiedName) | 通过完整类型名获取类型代理 | | namespace(ns, assembly) | 创建命名空间代理 | | GameObject | GameObject 类型代理(快捷访问) | | Transform | Transform 类型代理 | | Vector3 | Vector3 类型代理 | | Vector2 | Vector2 类型代理 | | Quaternion | Quaternion 类型代理 | | Color | Color 类型代理 |

代理对象实例方法

所有代理对象(GameObject, Component 等)都拥有以下特殊方法:

| 方法/属性 | 说明 | | :--- | :--- | | $get(propName) | 异步获取属性值 | | $set(propName, value) | 异步设置属性值(等待完成) | | $has(propName) | 异步检查属性是否存在 | | $call(methodName, args, paramTypes?, genericTypes?) | 显式调用方法(用于处理重载) | | $addListener(event, callback) | 添加事件监听 | | $removeListener(event, callback) | 移除事件监听 | | $ref | 获取远程对象 ID(属性) | | $type | 获取类型名(属性) | | $inspect() | 获取完整调试信息 |

PuertsProxy

类型辅助类,用于创建类型标记和管理类型映射。

重要区分

  • PuertsProxy.$typeof() 创建的是类型标记,仅用于泛型参数传递(如 GetComponent),不支持调用静态方法
  • UnityEffectProxy.type() 创建的是完整类型代理,支持静态方法调用、静态属性访问

如果需要调用静态方法,请使用 UnityEffectProxy.type() 而非 PuertsProxy.$typeof()

| 方法 | 说明 | | :--- | :--- | | $typeof(shortNameOrFullName) | 创建类型标记,仅用于泛型参数传递。支持简写 Animator 或全名 UnityEngine.Animator | | registerType(shortName, fullName) | 注册单个自定义类型的简写映射 | | registerTypes(typeMap) | 批量注册类型映射 | | hasType(typeName) | 检查类型是否已注册 | | getFullTypeName(typeName) | 获取类型的完整 AssemblyQualifiedName | | getTypeMap() | 获取所有已注册的类型映射表(用于调试) | | isTypeProxy(obj) | 检查对象是否为类型代理标记 | | getTypeName(typeProxy) | 从类型代理中提取类型名 |

// ❌ 错误:PuertsProxy.$typeof 不支持静态方法调用
const goType = PuertsProxy.$typeof('GameObject');
await goType.Find("Player");  // 报错!$typeof 创建的只是类型标记

// ✅ 正确:使用 UnityEffectProxy.type() 获取完整类型代理
const GameObject = UnityEffectProxy.type('UnityEngine.GameObject, UnityEngine.CoreModule');
await GameObject.Find("Player");  // 正常工作

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

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

// 检查类型是否已注册
if (PuertsProxy.hasType('PlayerController')) {
    const controller = await go.GetComponent(PuertsProxy.$typeof('PlayerController'));
}

// 获取完整类型名
const fullName = PuertsProxy.getFullTypeName('Animator');
// 返回: 'UnityEngine.Animator, UnityEngine.AnimationModule'

工具函数

| 函数 | 说明 | | :--- | :--- | | enablePuertsLog(enable: boolean) | 开启/关闭 Puerts 日志输出(默认关闭) |

import { enablePuertsLog } from 'gamelet-puerts-proxy';

// 开启调试日志
enablePuertsLog(true);

常见问题

Q: 为什么 console.log(go) 看不到属性? A: 代理对象是空的,所有数据都在 Unity 端。请使用 UnityEffectProxy.logProxy('Label', go) 查看代理的元数据(ID, 类型)。

Q: 报错 Cannot read property 'then' of undefined A: 可能是你忘记加 await,或者尝试直接访问属性(如 go.transform.position)而不是分步访问(await (await go.$get('transform')).$get('position'))。

Q: 如何传递回调函数? A: 直接传递 JS 函数即可,Proxy 会自动将其注册并发送 ID 给 Unity。

const clickCallback = async () => {
    console.log("Clicked!");
};
await button.$addListener('onClick', clickCallback);
// 移除时使用同一个引用
await button.$removeListener('onClick', clickCallback);

Q: 泛型方法如何调用? A: 使用 PuertsProxy.$typeof() 传递类型参数:

// 错误写法
const anim = await go.GetComponent<Animator>();  // ❌ 泛型被擦除

// 正确写法
const anim = await go.GetComponent(PuertsProxy.$typeof('Animator'));  // ✓

Q: 如何处理方法重载? A: 使用 $call 方法显式指定参数类型:

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

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

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

Q: 如何操作数组返回值? A: 数组也是代理对象,需要使用 $get('Length')GetValue(index)

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);
}

Q: Reflect error: Unknown reflect operation: has 报错? A: $has 方法需要 Unity 端支持,请确保 puerts-runtime 版本支持该操作。

Q: Method not found 报错? A: 检查方法名是否正确,注意大小写。对于静态方法,确保使用类型代理而非实例代理:

// 正确:静态方法从类型代理调用
const UnityObject = UnityEffectProxy.type('UnityEngine.Object, UnityEngine.CoreModule');
await UnityObject.Destroy(go);

// 错误:从实例调用静态方法
await go.Destroy(go);  // ❌