
1. 项目概述为什么我们需要Puerts如果你是一名游戏开发者无论是使用虚幻引擎UE还是Unity大概率都经历过这样的场景为了修改一个UI逻辑或者调整一个角色的行为参数你需要重新编译整个C或C#项目。等待编译的时间从几十秒到几分钟不等一天下来宝贵的开发时间就在这无尽的等待中流逝了。更别提C那令人望而生畏的语法和内存管理让快速迭代和逻辑调试变得异常沉重。这就是Puerts诞生的背景。Puerts全称“Puerto Rico Engine TypeScript”是一个开源项目它的核心目标只有一个让开发者能够使用TypeScript或JavaScript来编写游戏逻辑并与UE或Unity的底层引擎进行高效、无缝的通信。简单来说它在你熟悉的游戏引擎和灵活的动态脚本语言之间架起了一座高性能的桥梁。为什么是TypeScript相比于C/C#TypeScriptTS作为JavaScript的超集拥有动态类型、解释执行的特性修改代码后无需编译保存即可生效实现了真正的“热重载”。这对于游戏逻辑、UI、配置表等需要频繁调整的内容开发来说效率提升是指数级的。同时TS的静态类型检查又能在编码阶段规避大量低级错误兼具了动态语言的灵活和静态语言的安全。Puerts正是看中了这一点通过精心的设计让TS能够直接调用引擎的API操作游戏对象其性能损耗在绝大多数业务逻辑场景下可以忽略不计。所以这个“5分钟集成”的指南就是要帮你绕过复杂的配置和原理直接上手让你在最短的时间内体验到在UE/Unity里用TypeScript写代码的畅快感。无论你是想快速验证一个玩法原型还是希望为团队引入更高效的开发工作流Puerts都值得你投入这五分钟。2. 核心原理与架构拆解Puerts如何工作在开始动手之前花两分钟理解Puerts的工作原理能让你在后续使用和排错时更加得心应手。Puerts的架构可以概括为“两层绑定一个虚拟机”。2.1 虚拟机层JavaScript引擎的集成Puerts本身并不包含一个JavaScript/TypeScript引擎它扮演的是一个“集成者”的角色。在背后它依赖于成熟的JavaScript运行时V8Google出品的高性能JavaScript引擎广泛应用于Chrome和Node.js。Puerts在UE和Unity中主要集成V8以获得最佳的执行性能。QuickJS一个轻量级、可嵌入的JS引擎。在部分对包体大小极其敏感的平台如某些小游戏平台Puerts也支持使用QuickJS作为备选。Puerts的核心工作之一就是将这个虚拟机V8或QuickJS嵌入到UE/Unity的进程空间中。你的TypeScript代码最终会被编译成JavaScript在这个内嵌的虚拟机中解释执行。2.2 绑定层打通TS与引擎的任督二脉这是Puerts最精妙的部分。仅仅能在引擎里跑JS代码是不够的我们必须能让JS代码调用CUE或C#Unity的函数访问和修改引擎中的对象。Puerts通过两套机制实现这一点静态绑定在项目启动时Puerts会根据配置自动将引擎的大量API如UObject,Actor,Vector等类及其方法生成对应的TypeScript声明文件.d.ts和C/C#胶水代码。这意味着你在TS中可以直接new UE.Actor()或者调用this.transform.position就像在写原生代码一样并且有完整的代码提示。动态绑定对于一些需要更灵活交互的场景Puerts提供了将C/C#函数或对象动态注入到JS环境的能力。这常用于将游戏特定的业务逻辑接口暴露给脚本层。2.3 通信与生命周期管理TS对象和引擎对象生活在两个不同的世界里不同的内存空间、不同的垃圾回收机制。Puerts需要小心翼翼地管理它们之间的引用关系防止出现内存泄漏或访问违规。例如当一个TS对象持有一个UEActor的引用时Puerts会确保在该Actor被引擎销毁时TS端的引用也会被妥善置空或标记避免“野指针”问题。理解了这些你就会明白Puerts不是一个简单的“脚本系统”而是一个深思熟虑的、生产级别的桥梁方案。接下来我们就分别看看在UE和Unity中如何快速搭建这座桥。3. 5分钟快速集成UE篇我们以UE 5.x版本为例目标是创建一个空项目并集成Puerts让你能运行第一段TypeScript代码。3.1 前置环境准备在开始前请确保你的系统已安装Visual Studio 2022用于编译UE的C代码。安装时务必勾选“使用C的桌面开发”工作负载。Node.js (LTS版本)Puerts的构建工具链需要Node.js环境。安装后可以在命令行输入node -v和npm -v检查是否成功。Git用于克隆Puerts仓库。3.2 获取Puerts并集成到项目创建UE C空项目打开Epic Games启动器使用C模板创建一个新的空白项目例如命名为PuertsDemo。确保项目路径没有中文和特殊字符。克隆Puerts仓库打开命令行进入你刚创建的项目根目录即包含.uproject文件的目录。cd /path/to/your/PuertsDemo执行克隆命令git clone https://github.com/Tencent/puerts.git这会在项目根目录下创建一个puerts文件夹。运行集成脚本Puerts提供了便捷的集成脚本。进入puerts目录下的unreal子目录。双击运行setup.batWindows或setup.shmacOS/Linux。 这个脚本会自动完成以下工作将必要的Puerts源码目录链接到你的项目源码中。下载并部署预编译的V8库文件到正确位置。修改项目的.uproject文件和Build.cs文件添加对Puerts模块的依赖。生成项目文件并编译脚本运行成功后回到项目根目录右键点击PuertsDemo.uproject文件选择“Generate Visual Studio project files”。生成完成后用Visual Studio打开生成的.sln解决方案文件编译整个项目通常选择“Development Editor”配置。首次编译由于要构建V8和Puerts模块可能会花费较长时间10-30分钟。注意如果编译过程中遇到关于“未找到 V8 库”等错误请检查setup.bat运行时是否成功下载了V8。可以手动查看puerts/unreal/ThirdParty目录下是否有v8文件夹及其中的.lib、.dll等文件。网络问题可能导致下载失败必要时需要手动下载对应版本的V8库并放置到正确位置。3.3 编写并运行第一个TypeScript脚本创建脚本目录在项目根目录下与Content同级创建一个名为TypeScript的文件夹。这是Puerts默认寻找脚本的路径。初始化TS项目在TypeScript文件夹内打开命令行初始化npm并安装Puerts的类型定义。npm init -y npm install types/puerts -D这会在目录下生成package.json和node_modules。创建第一个脚本在TypeScript文件夹内创建一个文件HelloWorld.ts。import * as UE from ue import {$ref, $unref} from puerts class HelloWorld { // 定义一个旋转速度 RotationSpeed: number 180.0; // 每帧更新的函数在UE中会被自动调用 OnUpdate(DeltaTime: number): void { // 获取当前脚本所附着的Actor let actor this.GetOwner() as UE.Actor; if (actor) { // 计算本帧应旋转的角度 let rotationDelta new UE.Rotator(0, this.RotationSpeed * DeltaTime, 0); // 应用旋转 actor.AddActorLocalRotation(rotationDelta, false, undefined, false); } } } // 将此类导出以便UE蓝图或其它TS模块能够使用 export default HelloWorld;这段代码定义了一个类它会在游戏运行时让附着的Actor以每秒180度的速度绕Y轴旋转。在UE编辑器中关联脚本编译成功后打开UE编辑器。在内容浏览器中任意创建一个Actor蓝图例如BP_RotatingCube。打开这个蓝图在事件图表中右键搜索并添加一个节点“TypeScript: Create TypeScript Object”。在该节点的“Class”下拉框中你应该能看到HelloWorld这个类如果没看到尝试重启编辑器或检查TS编译错误。将创建的对象提升为变量例如MyTSObj。在事件Event Tick中拖出MyTSObj变量调用其OnUpdate方法并将Delta Seconds引脚连接上去。将这个蓝图拖放到场景中点击运行。你应该能看到这个物体开始持续旋转至此你已经在UE中成功集成并运行了Puerts。整个过程的核心就是运行setup.bat完成自动集成然后在指定目录编写TS代码即可。编辑器会自动监测TS文件变化并“热重载”修改RotationSpeed的值并保存无需编译C旋转速度会立即改变。4. 5分钟快速集成Unity篇Unity的集成流程与UE类似但更为轻量因为不需要编译引擎本身。我们以Unity 2022 LTS为例。4.1 创建项目与导入Puerts创建新的Unity项目使用3D核心模板即可命名为PuertsUnityDemo。使用Package Manager导入Puerts这是最推荐的方式。打开Unity进入Window - Package Manager。点击左上角的“”号选择“Add package from git URL...”。输入Puerts的Unity包地址https://github.com/Tencent/puerts.git#unity/upm。点击“Add”。Unity会自动克隆仓库并导入Puerts的核心文件。安装必要插件Puerts需要代码生成功能这依赖于com.unity.ide.rider或com.unity.ide.vscode等插件。确保你的Package Manager中已安装其中之一。4.2 配置Puerts并生成代码绑定运行初始化菜单导入完成后在Unity顶部菜单栏会出现Puerts菜单。点击Puerts - Generate Code。这个过程会扫描你项目中的所有C#代码并为需要暴露给TS的类生成静态绑定代码和TypeScript声明文件.d.ts。首次生成可能需要一点时间。检查生成结果生成完成后你会在项目目录下看到两个新文件夹Assets/Gen存放生成的C#绑定代码。Assets/TypeScript存放生成的.d.ts声明文件以及你后续自己编写TS代码的地方。4.3 编写并运行第一个TypeScript脚本创建TS脚本在Assets/TypeScript文件夹下或在其下新建子目录创建一个HelloWorld.ts文件。Unity的Puerts插件通常与TypeScriptToLua等插件类似会监控此目录下的.ts文件变化并自动编译。编写旋转逻辑import { UnityEngine } from csharp; class HelloWorld extends UnityEngine.MonoBehaviour { // 公开一个可在Unity Inspector中调整的速度变量 public rotationSpeed: number 180.0; // Unity的Update方法每帧调用 Update(): void { // 直接使用this.transform因为此类继承自MonoBehaviour this.transform.Rotate(UnityEngine.Vector3.up, this.rotationSpeed * UnityEngine.Time.deltaTime); } }注意这里我们直接继承了MonoBehaviour这是Puerts Unity版本提供的强大特性意味着你的TS类可以像普通的C#脚本一样被使用。在Unity中使用TS脚本在场景中创建一个Cube。你不需要手动挂载任何特殊的“脚本引擎”组件。Puerts的加载器在后台已经工作。选中Cube在Inspector面板中点击“Add Component”。在搜索框中输入HelloWorld你应该能看到这个由TypeScript定义的组件添加它。你会在Inspector中看到rotationSpeed这个公共变量。点击运行Cube就会开始旋转。同样修改rotationSpeed的值并保存TS文件效果会立即在运行中的游戏里体现无需停止运行。Unity的集成流程更加“无感”得益于其灵活的组件系统Puerts生成的TS类能够完美地融入Unity的工作流对于熟悉Unity的开发者来说几乎零学习成本。5. 深入实操核心功能与高级用法成功运行第一个脚本只是开始。要真正发挥Puerts的威力必须掌握以下几个核心功能点。5.1 TypeScript与引擎对象的交互这是日常开发中最频繁的操作。Puerts提供了几种方式创建引擎对象// UE中 let boxActor new UE.StaticMeshActor(GWorld, UE.ESPMode.WorldDynamic); let meshComp boxActor.StaticMeshComponent; meshComp.SetStaticMesh(UE.LoadObject(UE.StaticMesh, null, /Engine/BasicShapes/Cube.Cube)); // Unity中 let newCube UnityEngine.GameObject.CreatePrimitive(UnityEngine.PrimitiveType.Cube); newCube.transform.position new UnityEngine.Vector3(0, 1, 0);访问和修改属性// UE中访问Actor的变换 let actorLocation actor.GetActorLocation(); actor.SetActorLocation(new UE.Vector(actorLocation.X, actorLocation.Y, actorLocation.Z 100)); // Unity中访问GameObject的组件 let renderer gameObject.GetComponentUnityEngine.Renderer(); renderer.material.color new UnityEngine.Color(1, 0, 0, 1); // 设置为红色调用引擎方法直接调用即可参数传递遵循TypeScript规则。对于需要传递引用参数out,ref的C#方法Puerts提供了$ref和$unref辅助函数来处理。import {$ref} from puerts; // 假设有一个C#方法: bool Physics.Raycast(Vector3 origin, out RaycastHit hitInfo, float distance) let hitInfo $refUnityEngine.RaycastHit(); let isHit UnityEngine.Physics.Raycast(rayOrigin, $ref(hitInfo), 100.0); if (isHit) { let hitPoint hitInfo.point; // 使用$unref(hitInfo)来获取值但在这种简单使用中Puerts会自动处理 }5.2 异步操作与Promise支持游戏开发中充斥着异步操作如资源加载、网络请求。Puerts内置了对Promise的支持让你可以用现代、优雅的方式处理异步。// 示例在Unity中异步加载一个资源 async function loadAssetAsync(path: string): PromiseUnityEngine.Object { // Unity的Resource.LoadAsync返回的是ResourceRequest它是一个AsyncOperation let request UnityEngine.Resources.LoadAsync(path); // 等待加载完成 await Promise.resolve(request); // 或者使用Puerts提供的更直接的包装 return request.asset; } // 在Update或其他地方使用 let myPrefab await loadAssetAsync(Prefabs/MyCharacter); let instance UnityEngine.Object.Instantiate(myPrefab) as UnityEngine.GameObject;在UE中处理异步通常结合async/await和UE自身的延迟Delay或事件系统原理相通。5.3 模块化与代码组织大型项目必须考虑代码组织。Puerts支持ES Module。你可以在TypeScript目录下自由创建子文件夹如GameLogic、UI、Data。使用import和export来组织代码。注意由于TS最终是在JavaScript环境中运行要避免循环依赖。合理设计模块接口。5.4 调试TypeScript代码高效的调试是开发效率的保障。Puerts支持主流的调试方式VSCode调试这是最推荐的方式。你需要配置VSCode的launch.json。在Puerts的TS项目根目录有tsconfig.json的目录下创建.vscode/launch.json。配置调试器附加到UE/Unity的进程进程名通常是UE4Editor.exe、Unity.exe或它们的开发构建版本。在Puerts初始化代码中启用调试器监听通常有一个EnableDebugger的API或设置端口。在VSCode中打上断点启动游戏然后从VSCode启动调试即可实现源码级调试。Chrome DevTools远程调试Puerts可以启动一个WebSocket服务器允许你使用Chrome浏览器的开发者工具进行调试。在浏览器中检查Console、Sources、Profiler等体验与调试网页应用几乎一致。实操心得对于Unity项目使用VSCode调试非常顺畅。对于UE项目由于编辑器进程复杂有时附加调试器会不太稳定。一个备选方案是大量使用console.log进行输出Puerts会将日志重定向到UE的输出日志窗口或Unity的Console窗口结合puerts命名空间的日志级别控制也能进行高效的逻辑追踪。6. 性能优化与最佳实践将逻辑移到脚本层性能是首要考虑。遵循以下实践可以确保你的Puerts应用运行如飞。6.1 避免每帧频繁的“桥接”调用最昂贵的操作是在TS和C/C#之间传递复杂数据。一个典型的反面例子是在Update循环中频繁创建新的Vector3对象并传递。// 不推荐每帧都创建新的JS对象和Vector3对象 Update() { let pos this.transform.position; this.transform.position new UnityEngine.Vector3(pos.x 0.1, pos.y, pos.z); } // 推荐在类成员中缓存对象只修改其属性 private _moveDelta: UnityEngine.Vector3; Start() { this._moveDelta new UnityEngine.Vector3(0.1, 0, 0); } Update() { let pos this.transform.position; pos.Add(this._moveDelta); // 直接修改引擎对象的属性减少跨语言调用和对象创建 this.transform.position pos; }6.2 善用对象池管理脚本实例频繁创建和销毁TS对象尤其是那些关联了引擎对象的会产生垃圾回收GC压力。对于频繁生成/消失的游戏实体如子弹、特效应考虑实现简单的对象池。class BulletPool { private _pool: YourBulletClass[] []; get(): YourBulletClass { if (this._pool.length 0) { return this._pool.pop()!; } return new YourBulletClass(); // 这里是你的TS类 } release(bullet: YourBulletClass) { bullet.Reset(); // 重置对象状态 this._pool.push(bullet); } }6.3 类型安全与编译检查充分利用TypeScript的强类型优势。确保tsconfig.json中开启了严格的编译选项strict: true。这能在编码阶段捕获大量潜在的类型错误避免运行时崩溃。{ compilerOptions: { target: es2020, module: commonjs, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, outDir: ./dist, declaration: true }, include: [./src/**/*], exclude: [node_modules] }6.4 关注内存泄漏虽然Puerts和V8的GC会管理JS对象的内存但如果你在TS中持有对引擎对象的强引用而引擎对象又被销毁需要确保TS端的引用被释放。通常Puerts的绑定层会处理大部分情况。你需要留意的是自定义的事件监听器或全局缓存。在TS对象的析构函数或特定的生命周期方法如Unity的OnDestroy中记得清理这些引用。7. 常见问题与排查技巧实录即使按照指南操作你也可能会遇到一些问题。这里记录了一些高频问题和解决方法。7.1 集成与编译问题问题现象可能原因解决方案UE编译失败提示找不到Puerts模块setup.bat未成功运行或项目文件未正确生成。1. 检查puerts/unreal目录是否在项目根目录。2. 手动运行setup.bat观察有无报错。3. 删除项目目录下的Intermediate,Binaries,.vs,*.sln文件重新运行setup.bat并生成项目文件。Unity中找不到TS组件TS代码有语法错误导致编译失败。1. 查看Unity Console窗口通常会有详细的TS编译错误信息。2. 检查Assets/TypeScript目录下的代码。3. 尝试点击Puerts - Rebuild TypeScript强制重新编译。运行时提示“Class XXX not found”静态绑定未生成或生成不完整。1. 在Unity中执行Puerts - Generate Code。2. 在UE中确保已成功编译项目并检查TypeScript目录下的.d.ts文件是否包含了缺失的类声明。7.2 运行时与逻辑问题问题现象可能原因解决方案修改TS代码后游戏内未生效热重载失败或脚本未重新加载。1.UE检查编辑器是否运行中Puerts默认在编辑器模式下启用热重载。尝试手动保存所有TS文件。2.Unity确保Puerts的监视器在运行。有时需要焦点切回Unity窗口才能触发重编译。重启游戏有时是最终手段。调用引擎API时崩溃参数类型不匹配或传递了非法值如空指针。1. 仔细检查API的TypeScript声明确认参数顺序和类型。2. 对于可能为null或undefined的对象在使用前先进行判断if (obj) { ... }。3. 使用try-catch包裹可疑代码捕获异常并打印日志。性能突然下降可能存在内存泄漏或某帧内进行了大量跨语言调用。1. 使用浏览器的开发者工具远程调试中的Memory和Performance面板进行分析。2. 检查Update循环中的代码优化算法避免每帧创建大量临时对象。3. 使用对象池复用频繁创建销毁的对象。7.3 调试相关问题问题现象可能原因解决方案VSCode无法命中断点调试器未正确附加或源码映射不对。1. 确认Puerts已启用调试服务器端口通常为8080或9229。2. 检查VSCodelaunch.json中的port和address配置是否正确。3. 确保VSCode打开的工作区是TS源码的根目录且.vscode/launch.json配置正确。4. 尝试在TS代码开头加入debugger;语句强制触发调试。Chrome DevTools无法连接端口被占用或防火墙阻止。1. 确认Puerts启动调试器的端口号。2. 在浏览器输入localhost:端口号/json/list查看调试目标列表。3. 关闭可能占用端口的其他程序。7.4 关于网络热词“baseurl”的特别说明在搜索TypeScript资料时你可能会看到关于tsconfig.json中“baseUrl”选项将在TypeScript 7.0中弃用的警告。这主要影响的是使用路径映射paths的项目。Puerts生成的TS项目模板可能使用了这个配置。如果你的项目出现相关警告不必惊慌这通常不影响运行。长期解决方案是关注Puerts官方仓库的更新或者将compilerOptions中的“baseUrl”替换为“rootDir”并调整“paths”的相对路径基准。目前保持现有配置完全可正常工作。最后再分享一个我个人的小技巧在项目初期建立一个简单的“测试沙盒”场景或关卡专门用于快速验证你新写的TS模块功能。将常用的调试代码如日志输出、性能测试、对象创建封装成简单的全局函数放在这里能极大提升开发调试的效率。Puerts带来的开发流畅度一旦习惯就再也回不去了那几分钟的集成时间绝对是物超所值的投资。