Unreal引擎集成TypeScript开发指南:Puerts原理、配置与双向调用实践

1. 项目概述:为什么要在Unreal里搞TypeScript?

如果你是一个常年混迹在Unreal引擎和前端开发两个圈子的开发者,看到“Puerts”这个名字,大概率会眼前一亮。这玩意儿说白了,就是给Unreal引擎这个庞然大物,开了一个能和TypeScript(或者说JavaScript)世界无缝对话的后门。我最早接触它,是因为团队里有个项目,策划和UI同学天天追着我要改界面逻辑和数值公式,每次改个按钮响应或者伤害计算,都得重新编译C++,等个几分钟是家常便饭,效率低到令人发指。

Puerts的出现,完美地解决了这个痛点。它的核心价值,就是让Unreal引擎的逻辑层可以用TypeScript来写。这意味着什么?意味着那些频繁变动的游戏逻辑、UI交互、配置表解析,都可以用脚本语言来快速迭代。改完代码,在编辑器里点一下“刷新”,甚至热重载,效果立竿见影,再也不用经历漫长的编译等待。这不仅仅是开发效率的提升,更是团队协作模式的革新——前端同学可以更深入地参与到游戏逻辑开发中,因为语法是他们熟悉的TypeScript。

这个项目标题里的“交互调用指南”,点出了Puerts最核心也是最让人头疼的部分:桥接。C++和TypeScript,一个是编译型、强类型、手动内存管理的系统级语言,一个是解释型、动态类型、拥有垃圾回收的脚本语言,让它们俩安全、高效、正确地“握手”,里面全是细节和坑。这份指南,就是带你绕过这些坑,把Puerts这把利器用得顺手。

2. 核心原理与架构拆解:Puerts如何架起这座桥?

要玩转Puerts,不能只停留在“怎么用”的层面,还得稍微了解一下它“为什么能这么用”。知其然知其所以然,遇到诡异问题时你才知道该往哪个方向排查。

2.1 核心桥梁:V8引擎与FFI(外部函数接口)

Puerts的基石是Google的V8 JavaScript引擎,就是Chrome浏览器和Node.js里那个速度飞快的引擎。Puerts将V8引擎集成到Unreal的运行时环境中,创建了一个独立的JavaScript/TypeScript执行上下文。

但这还不够,关键是如何让TS/JS代码能调用Unreal的C++对象、函数,反之亦然。这里用到的是FFI(Foreign Function Interface)技术。Puerts在底层做了大量的绑定生成工作:

  1. 反射信息提取:它利用Unreal Engine强大的反射系统(UProperty, UFunction等),在启动时或编译后,自动分析你的C++类、蓝图函数库。
  2. 绑定代码生成:根据反射信息,生成一层“胶水代码”。这层代码知道如何将TS/JS中的函数调用,翻译成对C++函数指针的调用,并且负责在两种语言的数据类型之间进行转换(Marshal/Unmarshal)。比如,把TS的number转换成C++的floatint,把TS的Array转换成TArray,或者更复杂的UObject指针传递。
  3. 上下文注入:生成的绑定代码会在V8引擎中创建对应的JavaScript对象、函数、构造函数,将它们暴露给TS/JS执行环境。这样,你在TS里就能看到new UE.SomeActor()这样的类,也能调用UE.KismetSystemLibrary.printString这样的全局函数。

2.2 两种关键对象生命周期管理

跨语言调用,内存管理是头号难题。Puerts主要处理两种对象:

  1. UObject派生对象:这是Unreal世界的基石。Puerts采用了一种“弱引用”机制。当你在TS中持有一个UObject(比如一个AActor)时,Puerts内部实际保存的是一个指向该UObject的弱指针(TWeakObjectPtr)。同时,它会向该UObject注册一个委托,当这个UObject被垃圾回收(GC)时,通知Puerts清理TS侧的引用。这样可以防止UObject已被删除,但TS还试图访问而导致的崩溃。反过来,由TS创建的对象,其生命周期也受Unreal的GC管理,或者由开发者手动控制。

  2. 纯脚本对象:在TS中new出来的、不与C++UObject直接绑定的普通对象,其生命周期完全由V8引擎的垃圾回收器管理。这和你写Node.js程序没有区别。

理解这个区别至关重要。比如,你从一个UObject方法里返回了一个TS创建的大型数组,如果你没有在C++侧持有对其的引用,它可能很快被V8回收。而如果你错误地在TS中长期强引用了一个已销毁的UObject,访问时就会得到undefined或引发错误。

2.3 模块化与加载机制

Puerts支持符合ESM规范的import/export语法。你可以把TS代码组织成一个个模块文件。在Unreal中,你需要指定一个或多个TS脚本的根目录(TypeScriptRoot)。Puerts会在启动时扫描这些目录,编译.ts文件为.js(或直接使用.ts),并构建模块依赖图。

它的加载器是自定义的,与Unreal的Pak文件系统、Project目录结构能很好地结合。你可以把TS代码放在Content/JavaScript目录下,像管理其他游戏资产一样管理它们。热重载功能也是基于此实现:监听文件变化,重新编译并加载单个模块,替换旧的模块对象,而无需重启游戏或编辑器。

3. 环境搭建与项目配置实操

理论说再多,不如动手搭一遍。这里以Unreal Engine 5.2+版本为例,走一遍完整的搭建流程。我会把几个容易踩坑的地方重点标出来。

3.1 前置环境准备

  1. 安装Node.js与TypeScript:Puerts的绑定生成工具链依赖于Node.js。去官网下载LTS版本(如18.x)安装。安装后,在命令行执行npm install -g typescript来全局安装TypeScript编译器。建议TS版本与Puerts官方推荐保持一致(例如4.9+),避免因语法支持问题导致编译错误。
  2. 获取Unreal引擎源码:Puerts需要链接到Unreal引擎的模块。虽然也有针对预编译引擎版本的发布包,但为了获得最好的兼容性和调试体验,强烈建议使用源码版本的Unreal Engine。从Epic Games Launcher或Git仓库获取。
  3. 下载Puerts源码:从GitHub的Puerts官方仓库克隆或下载最新稳定版源码。注意分支,通常unreal分支对应Unreal引擎版本。

3.2 引擎模块集成

这是最关键的一步,目的是把Puerts编译成Unreal引擎的一个模块。

  1. 放置源码:将下载的Puerts源码文件夹(例如puerts-unreal)复制到你的Unreal引擎源码目录下的Engine/Plugins目录中。如果Plugins目录不存在,就创建一个。路径看起来像YourUnrealEnginePath/Engine/Plugins/puerts-unreal
  2. 生成项目文件:在引擎源码根目录,运行GenerateProjectFiles.bat(Windows) 或相应平台的脚本。这个步骤会让Unreal Build Tool (UBT) 识别到新加入的Puerts插件模块。
  3. 编译引擎:用你常用的IDE(如Visual Studio 2022)打开生成的.sln解决方案文件,找到UE5目标(或者你对应的引擎版本),选择Development Editor配置进行编译。这个过程会同时编译引擎和Puerts插件。第一次编译时间较长,请耐心等待。

注意:务必编译Development Editor配置,这是用于编辑器的配置。纯DevelopmentShipping配置可能不包含编辑器所需的插件代码,导致在编辑器中无法启用Puerts。

3.3 创建测试项目并启用插件

  1. 创建新项目:打开刚刚编译好的Unreal编辑器,创建一个新的空白项目或第三人称模板项目。项目类型选C++,因为后续可能需要添加一些C++代码作为桥接。
  2. 启用Puerts插件:在编辑器菜单栏,点击编辑(Edit) -> 插件(Plugins)。在插件搜索框中输入“Puerts”。你应该能看到“Puerts - Unreal.js Binding”插件。勾选其旁边的“启用(Enabled)”复选框,然后重启编辑器。
  3. 验证安装:重启后,在编辑器菜单栏如果能看到Puerts菜单项,并且其下有TypeScript Project等子菜单,说明插件启用成功。你还可以在Output Log窗口中搜索“Puerts”,查看启动日志,确认V8引擎初始化成功。

3.4 配置TypeScript项目

  1. 创建脚本目录:在你的项目根目录下(与Content目录同级),创建一个名为TypeScript的文件夹。这是Puerts默认寻找TS源码的根目录之一。你也可以在项目设置中修改这个路径。
  2. 初始化TS配置:在TypeScript文件夹内打开命令行,执行tsc --init。这会生成一个tsconfig.json文件。打开它,进行关键配置:
    { "compilerOptions": { "target": "es2020", "module": "commonjs", "lib": ["es2020"], "outDir": "../../Content/JavaScript/", "rootDir": "./", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "experimentalDecorators": true, // 如果要用装饰器,需开启 "allowUmdGlobalAccess": true }, "include": [ "**/*" ] }
    关键点outDir指向了Content/JavaScript。这是Unreal的资产目录,Puerts运行时将从这里加载编译后的.js文件。rootDir设为当前目录。
  3. 编写第一个TS文件:在TypeScript目录下创建index.ts
    import * as UE from 'ue' import { $ref, $unref } from 'puerts' console.log('Hello Puerts & Unreal Engine!'); // 尝试调用一个Unreal全局函数 UE.KismetSystemLibrary.printString(null, 'Message from TypeScript!', true, false, new UE.LinearColor(0, 1, 0, 1), 5.0);
  4. 编译与运行
    • TypeScript目录下运行tsc命令。如果成功,你会在Content/JavaScript下看到生成的index.js文件。
    • 回到Unreal编辑器,点击Puerts -> TypeScript Project -> Reload(或使用快捷键)。如果一切正常,你将在游戏视图或输出日志中看到绿色的“Message from TypeScript!”字样。

4. 双向交互调用深度解析

环境搭好了,我们来啃最硬的骨头:C++和TypeScript之间到底怎么互相调来调去?这里分几个典型场景,把每种用法的细节和潜规则讲透。

4.1 从TypeScript调用Unreal C++/蓝图

这是最常用的场景。Puerts通过自动绑定,将大量的Unreal API暴露给了TS。

1. 调用静态函数和全局函数库:

import * as UE from 'ue'; // 调用Kismet系统库的打印函数 UE.KismetSystemLibrary.printString(null, 'Hello World', true, false, new UE.LinearColor(1, 0, 0, 1), 2.0); // 调用数学库的函数 let randomNum = UE.KismetMathLibrary.randomFloatInRange(0, 100); let vector = new UE.Vector(1, 2, 3); let normalized = UE.KismetMathLibrary.normal(vector);
  • 要点UE是一个命名空间对象,包含了所有已绑定的Unreal类、结构体和枚举。通过点操作符访问。
  • 参数传递:基本类型(number, boolean, string)可以直接传递。Unreal的结构体(如Vector,Rotator,LinearColor)需要在TS侧用new关键字创建对应的包装对象。注意这些包装对象是“值类型”,在跨边界传递时会被拷贝。

2. 实例化UObject和Actor:

import * as UE from 'ue'; // 假设有一个C++类或蓝图类叫 `MyGameMode` let world = UE.GWorld; // 获取世界上下文 let gameModeClass = UE.Class.Load('/Game/Blueprints/MyGameMode.MyGameMode_C'); // 加载类资产 let myGameMode = world.SpawnActor(gameModeClass, new UE.Transform(), UE.ESpawnActorCollisionHandlingMethod.Undefined, undefined) as UE.MyGameMode; // 调用实例方法 if (myGameMode) { myGameMode.someMethod('参数'); let result = myGameMode.calculateSomething(10, 20); }
  • 要点:使用UE.Class.Load加载蓝图或C++类的引用。路径格式是{AssetPath}.{ClassName}_C。使用GWorld.SpawnActor或相应的生成函数来创建实例。返回的对象需要做类型断言(as)来获得正确的类型提示(如果你有.d.ts声明文件的话)。
  • 生命周期:记住,你在TS里拿到的是一个“弱引用”。你需要确保这个对象在Unreal侧没有被销毁。例如,一个Actor被Destroy()后,TS侧的引用就会失效。

3. 访问和修改UProperty:

// 假设 myActor 有一个 public 的 `Health` (float) 属性和一个 `WeaponList` (TArray<AWeapon*>) 属性 let currentHealth = myActor.Health; // 直接读取 myActor.Health = currentHealth - 10; // 直接设置 let weaponArray = myActor.WeaponList; // 获取TArray的包装对象 weaponArray.add(someWeapon); // 调用包装对象的方法修改容器 // 注意:对于复杂容器,直接赋值给属性可能不生效,需要通过其方法操作。
  • 要点:标记为UPROPERTY(BlueprintReadWrite)的变量,可以直接在TS中像访问普通对象属性一样读写。对于TArray,TSet,TMap等容器,Puerts提供了对应的JavaScript包装对象,并暴露了类似add,get,set,remove等方法,不要试图用JavaScript原生的pushpop或直接索引赋值(对于Map),必须使用包装对象的方法。

4.2 从C++/蓝图调用TypeScript函数

这让你能把核心逻辑写在TS里,然后由C++驱动。主要有两种方式:委托(回调)和直接调用模块导出函数。

1. 使用JavaScript函数作为委托(Callback):这是最灵活的方式。在C++端定义一个动态多播委托(DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam等),然后在TS里把JS函数绑定上去。

  • C++端 (MyTrigger.h):
    #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "MyTrigger.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerEntered, const FString&, PlayerName); UCLASS() class MYPROJECT_API AMyTrigger : public AActor { GENERATED_BODY() public: UPROPERTY(BlueprintAssignable, Category = "Trigger") FOnPlayerEntered OnPlayerEntered; // ... 其他代码,在某个时机(如Overlap事件)广播委托 void NotifyPlayerEntered(const FString& Name) { OnPlayerEntered.Broadcast(Name); } };
  • TypeScript端:
    import * as UE from 'ue'; let trigger: UE.MyTrigger = ...; // 获取Trigger实例 // 将一个JS函数绑定到委托 trigger.OnPlayerEntered.add((playerName: string) => { console.log(`Player ${playerName} entered the trigger!`); UE.KismetSystemLibrary.printString(null, `Welcome ${playerName}!`, true, false, new UE.LinearColor(0, 1, 1, 1), 3.0); }); // 注意:需要保存对JS函数的引用,防止被垃圾回收。或者使用Remove动态移除。
    • 要点:JS函数被作为委托目标添加后,Puerts会保持对其的引用,防止GC。但当这个JS函数不再需要时(例如Actor销毁),最好主动调用Delegate.RemoveClear来移除引用,避免内存泄漏。

2. 调用模块的导出函数:你可以让TS模块像静态函数库一样工作。

  • TypeScript端 (GameLogic.ts):
    export function calculateDamage(baseDamage: number, defense: number, critical: boolean): number { let damage = baseDamage * (100 / (100 + defense)); if (critical) { damage *= 1.5; } return Math.floor(damage); } export const GameConfig = { version: '1.0.2', maxPlayers: 4, };
  • C++端:需要通过Puerts的API来获取并调用这些函数。这通常需要一些样板代码。
    // 假设在某个GameInstance或Manager的初始化阶段 auto JsEnv = ...; // 获取Puerts的JavaScript执行环境指针 FString Result; // 调用模块函数 bool bSuccess = JsEnv->ExecuteModuleFunction("GameLogic", "calculateDamage", Result, 50.0, 20.0, true); if (bSuccess) { float Damage = FCString::Atof(*Result); // 使用Damage... } // 获取模块变量 FString ConfigVersion; JsEnv->GetModuleVariable("GameLogic", "GameConfig.version", ConfigVersion);
    • 要点:这种方式更适用于C++主动调用一些计算逻辑或获取配置。它比委托方式更“主动”,但调用开销稍大,且需要处理参数和返回值的序列化。

4.3 数据类型的映射与转换

这是交互调用中最容易出错的地方。必须清楚每种类型在边界两侧的表现。

C++ / Unreal 类型TypeScript 类型 (传入/传出)注意事项与关键操作
int32,float,doublenumber直接映射。注意整数范围。
boolboolean直接映射。
FString,FName,FTextstring自动转换。注意FName是不区分大小写的字符串标识。
UObject*/AActor*对应的类实例 (如UE.Actor)弱引用。使用前需判断是否存在 (if (obj) {...})。
TArray<T>特殊的Array包装对象不能使用JS原生数组方法。使用包装对象的.get(index),.set(index, value),.add(value),.remove(index),.num()等方法。
TMap<FString, T>特殊的Map包装对象不能使用JS原生Map或obj[key]直接访问。使用.get(key),.set(key, value),.remove(key),.keys(),.values()等方法。
FVector,FRotator等结构体new UE.Vector(x,y,z)创建的对象值类型,传递时拷贝。修改TS侧的对象不会影响C++侧的原值,除非通过方法传回。
枚举 (UENUM)作为UE.EnumName.EnumValue访问的数值本质是number
委托/事件特殊的对象,有.add(func),.remove(func),.clear()方法绑定JS函数作为回调。

实操心得:在处理容器类型时,我强烈建议在TS侧封装一层工具函数。比如写一个toJSArray(ueArray: UE.TArray<any>): any[]函数,内部循环调用.get(),将Unreal容器转为真正的JS数组用于复杂操作,操作完再写一个fromJSArray(jsArray: any[], ueArray: UE.TArray<any>)函数同步回去。虽然有一次拷贝开销,但避免了直接操作包装对象的认知负担和潜在错误。

5. 工程化实践:构建可维护的TypeScript项目

当脚本代码越来越多,就不能再是随便写几个.ts文件扔在目录里了。需要一些工程化的手段来提升开发体验和代码质量。

5.1 类型声明与智能提示

没有类型提示的TypeScript,就失去了大半的意义。Puerts支持通过.d.ts声明文件来获得完美的代码补全和类型检查。

  1. 生成声明文件:Puerts提供了一个命令行工具,可以基于你项目的C++类和蓝图,自动生成TypeScript声明文件。这个工具通常在插件目录的Tools文件夹下。
    # 示例命令,路径需根据实际情况调整 node path/to/puerts-unreal/Tools/generate_dts.js --output ./TypeScript/ue.d.ts --include YOUR_MODULE_NAME
    运行后,会生成一个庞大的ue.d.ts文件,里面包含了所有被反射的类、结构体、函数、枚举的类型定义。
  2. 配置tsconfig.json:在tsconfig.jsoncompilerOptions中,通过typestypeRoots指定声明文件的位置。
    { "compilerOptions": { // ... 其他配置 "typeRoots": ["./"], "types": ["ue"] // 如果声明文件名为 ue.d.ts } }
  3. 享受智能提示:配置好后,你的IDE(如VSCode)就能在输入UE.时给出所有可用的类和方法提示,并且参数类型、返回值类型都一清二楚,极大提升开发效率和减少运行时错误。

5.2 代码组织与模块设计

借鉴前端和Node.js的项目结构是个好主意。

TypeScript/ ├── src/ # 源代码 │ ├── core/ # 核心游戏逻辑(状态机、管理器、配置) │ │ ├── GameState.ts │ │ ├── EventSystem.ts │ │ └── ConfigManager.ts │ ├── gameplay/ # 玩法相关逻辑 │ │ ├── abilities/ # 技能系统 │ │ ├── items/ # 物品系统 │ │ └── quests/ # 任务系统 │ ├── ui/ # UI逻辑与控制 │ │ ├── widgets/ # 各个UI组件 │ │ └── UIManager.ts # UI总管理器 │ └── utils/ # 工具函数 │ ├── math.ts │ ├── logger.ts │ └── ue-utils.ts # 封装对UE容器等操作的帮助函数 ├── libs/ # 第三方库或自研库 ├── index.ts # 程序入口,负责初始化各模块 └── tsconfig.json
  • 入口文件index.ts是Puerts默认加载的入口。在这里,你应该按顺序初始化核心管理器,注册全局事件监听等。
  • 模块化:使用ES Module的import/export清晰地管理依赖。避免形成复杂的循环依赖。
  • 依赖注入思想:重要的管理器(如GameState、UIManager)可以在初始化后挂载到全局对象(如window.G)上,方便其他模块访问,但需谨慎使用,避免全局状态污染。

5.3 调试技巧

调试是开发闭环中不可或缺的一环。

  1. 使用console.log:最基础但最有效。Puerts将console.log重定向到了Unreal的Output LogLogTemp类别)。你可以方便地打印变量、跟踪执行流。
  2. 利用Source Map:在tsconfig.json中设置"sourceMap": true。这样,当在Unreal中运行出错时,错误堆栈信息会指向原始的.ts文件行号,而不是编译后的.js文件,定位问题快得多。
  3. 配合Chrome DevTools (VSCode) 远程调试:这是高阶玩法。Puerts支持V8 Inspector协议。你可以在启动游戏时添加命令行参数-puerts-debug,然后通过Chrome浏览器访问chrome://inspect,或者配置VSCode的launch.json,附加到游戏进程进行断点调试、单步执行、查看变量。这对于复杂逻辑调试来说是无价之宝。
  4. 善用Unreal编辑器的“输出日志”窗口:过滤LogPuertsLogJavascript可以查看Puerts插件自身的运行日志和错误信息。

6. 性能优化与常见陷阱

用脚本语言,性能是永远的考量。以下是几个关键点和容易踩的坑。

6.1 性能优化要点

  1. 减少跨语言调用:每一次从TS调用C++函数,或C++回调TS函数,都有一定的开销。避免在每帧的循环(如Tick)中进行大量的、细粒度的跨语言调用。比如,不要在TS的每帧里循环读取100个Actor的位置;而是应该在C++侧批量计算好,通过一个结构体数组一次性传给TS。
  2. 批量化数据传递:如上例,对于需要频繁交换的数据,设计成批量传递的模式。例如,将一帧内所有需要更新的UI数据打包成一个大的JSON对象或一个大的TArray<FMyData>,一次传递。
  3. 警惕闭包与内存泄漏:将JS函数绑定到Unreal委托(如事件、定时器)时,Puerts会持有该函数的引用以防止被GC。但如果这个闭包函数引用了外部的大对象(如一个大的纹理数据引用),会导致这些对象也无法被释放。务必在不需要时(如Actor的EndPlay或组件销毁时)主动移除事件监听。
    class MyComponent { private onDamageHandler: any; constructor(actor: UE.Actor) { this.onDamageHandler = (amount: number) => { this.onDamage(amount); }; actor.OnTakeDamage.add(this.onDamageHandler); } destroy() { // 必须!在销毁时移除监听 if (this.actor) { this.actor.OnTakeDamage.remove(this.onDamageHandler); } this.onDamageHandler = null; } private onDamage(amount: number) { /* ... */ } }
  4. 对象池:对于频繁创建和销毁的、由TS逻辑管理的轻量级对象(如子弹轨迹计算点、特效句柄),考虑实现对象池,避免频繁的JS对象创建和GC压力。

6.2 常见陷阱与排查

  1. “undefined is not an object” 错误:这几乎总是因为你尝试访问一个已经被Unreal垃圾回收了的UObject。在访问任何来自Unreal的对象属性或方法前,先进行空值判断if (myActor) { ... }
  2. TS函数被意外回收,导致委托调用失败:你绑定了一个匿名函数到委托,但没有保存引用,一段时间后该函数被GC,委托触发时就会出错。始终将需要绑定的函数保存为类成员或模块级变量
  3. 循环引用导致内存泄漏:TS对象持有UObject的引用(虽然是弱引用,但TS对象本身被UObject通过委托强引用),UObject又通过委托强引用着TS对象内的函数,这就形成了跨语言的循环引用。需要仔细设计生命周期,在适当的时候手动断开引用。
  4. 热重载后状态丢失:热重载会重新执行模块代码,但不会重新创建之前已经存在的对象。如果你的模块级变量保存了重要的游戏状态,热重载后这些状态会重置。对于需要持久化的状态,应该将其保存到Unreal侧的GameInstance或SaveGame对象中,TS只作为操作接口。
  5. 异步操作与Promise:Puerts环境支持Promise。但要注意,Unreal的Tick是单线程的,标准的setTimeoutfetch可能无法直接使用或行为不一致。对于需要延迟执行的操作,最好使用Unreal的定时器管理器(UKismetSystemLibrary.setTimer)或在TS侧自己实现一个基于游戏时间的调度器。

7. 进阶应用场景探讨

掌握了基础,可以看看Puerts能玩出什么花来。

  1. 完整的UI系统:结合Unreal的UMG,你可以用TS完全控制UI的逻辑。将UMG Widget Blueprint中的交互事件(如OnClicked)暴露给TS,在TS里处理业务逻辑、更新数据模型,然后驱动UI刷新。可以实现非常动态和复杂的UI流程。
  2. 配置表驱动:将游戏数值、任务文本、技能效果等配置放在JSON或Excel中,用TS在运行时加载、解析并应用到游戏对象上。策划改表,无需编译,重启游戏甚至热重载即可生效。
  3. 网络消息处理:在C++层接收到网络数据包后,反序列化出基本结构,然后将业务逻辑的处理派发给TS层。TS层处理完后再通知C++层进行响应或广播。这样网络协议的核心解析在C++,多变的业务逻辑在TS,非常灵活。
  4. AI行为树节点:可以创建自定义的AI行为树节点,其“执行逻辑”由TS函数实现。这样AI设计师可以在行为树里使用高度定制化的条件和服务,而无需程序员每次都用C++实现。
  5. 编辑器工具开发:Puerts也可以用在Unreal编辑器扩展中。用TS来写一些自动化工具、批量处理资产、自定义编辑器界面等,比用C++写Slate UI要快速得多。

说到底,Puerts的价值在于它提供了一个安全、高效、现代的脚本层,将Unreal引擎强大的渲染、物理、动画等底层能力,与TypeScript的快速迭代、动态灵活、生态丰富的优势结合了起来。它不是为了取代C++,而是作为C++的完美补充,让团队能把正确的技术用在正确的地方,最终提升整个项目的开发效率和创造力上限。刚开始集成可能会遇到一些磨合问题,但一旦跑通流程,你会发现它为项目开发带来的流畅感,是传统纯C++或蓝图开发难以比拟的。