Puerts for Unreal Engine 常见问题排查指南:从启动告警到 GC、打包与调试的 16 个实战陷阱

发布时间:2026/9/17 12:08:46
Puerts for Unreal Engine 常见问题排查指南:从启动告警到 GC、打包与调试的 16 个实战陷阱
Puerts for Unreal Engine 常见问题排查指南从启动告警到 GC、打包与调试的 16 个实战陷阱【免费下载链接】puertsPUER(普洱) Typescript. Lets write your game in UE or Unity with TypeScript.项目地址: https://gitcode.com/GitHub_Trending/pu/puerts导读本文基于 Puerts 的 Unreal 英文 FAQ 文档对应中文版见 doc/unreal/zhcn/faq.md整理而成面向在 UE4/UE5 中使用 Puerts 运行 TypeScript 的开发者。它覆盖了 Puerts 在 Unreal 集成中最常踩到的 16 类问题——从启动期的new (std::nothrow) int[0]告警、自动绑定模式下扩展函数缺失到 WaitDebugger 卡死、TS 类继承不生成代理蓝图、打包后脚本不执行、GC 生命周期混乱等。读完本文你将能对每一个问题做出准确定位并掌握对应的修复命令、配置项与源码级原理同时了解 unreal/Puerts 插件内部是如何实现这些行为与修复逻辑的。1. 启动告警new (std::nothrow) int[0] return nullptr, try fix it!现象控制台/日志中出现该告警。原因Unreal 引擎重载了new运算符但其实现不完全符合 C 标准——当使用std::nothrow方式分配长度为 0 的数组时UE 返回了nullptr。而标准要求此时应返回有效指针只有内存不足OOM时才返回nullptr。这一差异会让严格遵循标准的 V8 运行时误判为 OOM进而直接abort。目前该问题仅在 Windows 上被发现且已经 Epic 官方确认。源码佐证Puerts 在 JsEnvModule.cpp 的StartupModule中主动探测int* Dummy new (std::nothrow) int[0]; if (!Dummy) { UE_LOG(JsEnvModule, Warning, TEXT(new (std::nothrow) int[0] return nullptr, try fix it!)); MallocWrapper new FMallocWrapper(GMalloc); GMalloc MallocWrapper; } delete[] Dummy;检测到该 Bug 后Puerts 会用一个FMallocWrapper包装替换全局GMalloc覆盖内存分配行为来修复该问题注意ENGINE_MAJOR_VERSION 5 ENGINE_MINOR_VERSION 5的引擎版本下只记录 Error 日志不再执行包装修复说明新版引擎已处理此问题。结论这只是一条信息性告警提示当前 UE 版本存在该 BugPuerts 已尽量自动修复不影响功能可放心忽略。2. 自动绑定模式下部分扩展函数用不了现象通过UExtensionMethods注册的 C 扩展函数在 TypeScript 中调用不到。原因Puerts 模块启动得比较早在它启动时遍历 Class 无法发现比它更晚启动的模块中定义的扩展函数。解决方案在所有模块初始化完成之后调用如下 API 让 Puerts 重新扫描扩展函数IPuertsModule::Get().InitExtensionMethodsMap();源码佐证该接口定义在 PuertsModule.h实现在 PuertsModule.cpp会根据当前是单 JsEnv 还是多 JsEnvJsEnvGroup模式分别转发。真正的重扫逻辑在 JsEnvImpl.cppvoid FJsEnvImpl::InitExtensionMethodsMap() { for (TObjectIteratorUClass It; It; It) { UClass* Class *It; if (Class-IsChildOfUExtensionMethods() Class-IsNative()) { // 遍历其静态 UFunction登记为可用的扩展方法 } } }也就是说只要后启动模块中的UExtensionMethods子类已经完成注册Class 可见调用一次该 API 即可重新建立扩展方法映射。3. 勾选 Wait Debugger 选项后启动卡住现象编辑器或游戏启动后进程挂起不动。原因该选项的设计意图就是阻塞进程等待调试器连接调试器连上之后程序才会继续往下走。如果你还没配置好调试器又不小心勾选了它就无法进入界面去取消。解决方案直接终止进程然后编辑Config/DefaultPuerts.ini把WaitDebugger改为False后重新启动。源码佐证该配置定义于 PuertsSetting.h包括两个字段配置项类型默认值说明WaitDebuggerboolfalse是否在启动时等待调试器WaitDebuggerTimeoutdouble0等待超时秒0 表示无限等待加载逻辑见 PuertsModule.cpp使用GConfig-GetBool/GetDouble从PuertsConfigIniPath读取阻塞动作在 PuertsModule.cpp 中通过JsEnv-WaitDebugger(Settings.WaitDebuggerTimeout)触发最终由 JsEnv.cpp 转发到运行时。注意多 JsEnv 的 Group 模式不支持 WaitDebugger见 PuertsModule.cpp会直接打印Do not support WaitDebugger in Group Mode!。4. TS 生成蓝图调用StaticClass()返回的 UClass 不符合预期现象在 TypeScript 继承 UE 类生成的蓝图上调用StaticClass()拿到的UClass与直觉不符例如创建出来的对象没有子类方法或CreateDefaultSubobject报“类是 abstract 的无法创建”。原因TS 类本身没有StaticClass()方法所以该调用实际命中继承链上第一个定义了StaticClass()的类返回的也是那个类通常是父 UE 类的UClass。没理解这一点就容易误用。正确做法通过以下方式加载蓝图UE.Class.Load(path/to/your/blueprint/file)不要依赖StaticClass()去获取 TS 生成蓝图的 UClass。5. macOS 报错“无法打开 libv8.dylib因为无法验证开发者”原因macOS 的 Gatekeeper 隔离属性quarantine阻止了未签名 dylib 的加载。解决方案进入 dylib 所在目录通常是YourProject/Plugins/Puerts/ThirdParty/v8/Lib/macOSdylib执行sudo xattr -r -d com.apple.quarantine *.dylib该命令会递归移除当前目录下所有 dylib 的隔离标记。6. 纯蓝图工程报 “XXXProject could not be compiled”现象在纯蓝图工程中加入 Puerts 插件后提示工程无法编译要求手动从源码重建。原因对于纯蓝图工程直接双击 uproject 文件时 UE 可能不会自动编译第三方 C 插件因为工程本身没有 C 源码可触发构建。解决方案手动生成 Visual StudiomacOS 下为 Xcode工程然后在 IDE 中编译一次插件被编译进工程后即可正常使用。7. 打包后运行时报“字段找不到”现象编辑器里运行正常打包后脚本访问蓝图的某个字段却报不存在。原因这是 UE 对FName在编辑器与运行时处理不一致导致的——编辑器下FName大小写敏感运行时大小写不敏感。举例你在蓝图里定义了一个count字段编辑器下生成代码字段名为count运行正常但打包后如果在访问该蓝图之前已有另一处代码先初始化了一个Count字段那么FName.ToString()返回的是第一次构造该FName时输入的字符串此后凡是转成小写后相同的输入都会复用第一次的结果于是你实际拿到的字段名变成Count脚本中访问的count自然就不存在了。建议在 Blueprint 字段命名时统一大小写风格避免count与Count这类仅大小写不同的字段并存脚本侧访问前确认实际生成的字段名。8. UE5 报Construct TypeScript Object TestActor_C_1(...) on illegal thread!原因UE5 默认开启了AsyncLoadingThreadEnabled异步加载线程导致对象构造发生在非 GameThread 上触发了 Puerts 的线程安全检查。解决方案关闭AsyncLoadingThreadEnabled选项UE4 默认关闭UE5 默认开启。该选项通常在工程的构建/加载相关配置中设置。补充说明Puerts 内部大量路径都带有线程校验例如 JsEnvImpl.cpp 中SINGLE_THREAD_VERIFY宏下的ensureMsgf(BoundThreadId FPlatformTLS::GetCurrentThreadId(), TEXT(Access by illegal thread!))可见跨线程访问是被显式防御的。9. 如何避免 access a invalid object 异常现象调用一个已经被标记为无效的对象时Puerts 抛出 access a invalid object 异常。原理该异常由 Puerts 内部的对象生命周期跟踪功能抛出。一旦对象被标记为无效对它的一切调用包括UObject::IsValid这类普通 UE 调用都会抛异常。技术上给该跟踪功能加一个“仅判断不抛异常”的 API 并不难但这样会导致业务代码到处写判断严重影响可读性因此 Puerts 刻意没有提供。建议从设计上避免持有无效对象——比如切场景时 UE 会强制销毁 Actor应在切场景时通知 TS 侧清理相关引用如果无法避免且该异常不影响核心逻辑可用try-catch吞掉异常。10. GC 相关stub 与 UE 对象的两种生命周期模型核心概念一个 UE 对象传入 TS 后TS 侧会建立一个 stubTS 对象与之对应TS 调用这个 stub 会被转发到真实的 UE 原生调用。两者之间的生命周期关系有两种模型模型一stub 对象持有 UE 对象由 JS GC 管理如果 stub 对象在 TS 侧无引用会被 JS GC 回收进而释放对 UE 对象的强引用如果此时 UE 引擎侧也没有其他引用该 UE 对象才会被 UE GC 回收。模型二UE 对象持有 stub 对象由 UE GC 管理如果 UE 对象在引擎侧无引用会被 UE GC 回收进而释放对 stub 对象的强引用如果此时 TS 侧也没有引用该 stub该 stub 才会被 JS GC 回收。哪些情况是“UE 对象持有 stub 对象”TS 类继承 UE 类型mixin中参数指明objectTakeByNative已废弃的makeUClass不建议使用。其余情况均为“stub 对象持有 UE 对象”这类对象可以通过 TS 侧持有来阻止 UE GC。重要提醒即使对象不会被 GC 释放也依然不能保证它不被销毁。UE 的 GC 与 C#、Java、Lua、JS 等虚拟机 GC 不同——那些 GC 下对象只要被持有就绝不会销毁而 UE 允许通过 API 强制删除对象可能是用户主动调用也可能是引擎调用最常见的是切场景后场景挂载的所有 Actor 自动销毁。因此不能依赖“有引用就不会被销毁”的假设必要时仍需在切场景等时机主动清理 TS 侧引用。11. UE 对象被Puerts_UserObjectRetainer引用含义这说明该 UE 对象的“JS 侧代理对象”还没有被释放。GC 释放需要满足两个条件没有指向该对象的引用GC 扫描到它并完成释放。对于条件 1需要检查 JS 代码逻辑也可以借助 CDTChrome DevTools等工具查看内存确认对象被什么引用。对于条件 2以 V8 为例V8 是分代 GC老生代内存的扫描触发需要一定条件如内存分配量较大、较快。如果需要加快 GC 进程可以调用FJsEnv::LowMemoryNotification(); // 通知 V8 加速 GC FJsEnv::RequestFullGarbageCollectionForTesting(); // 立即执行一整趟全量 GC较慢建议只在切场景等时机调用源码佐证两个接口在 JsEnv.h 声明、JsEnv.cpp 转发实际 V8 调用在 JsEnvImpl.cpp前者调用MainIsolate-LowMemoryNotification()后者调用MainIsolate-RequestGarbageCollectionForTesting()。12. 手机/PC 打包后脚本不执行或报找不到脚本原因生成的 JS 脚本不是 UE 资产文件*.asset默认不会被打进包里因此打包后运行时加载不到脚本。解决方案打开Project Settings → Packaging → Additional Non-Asset Directories to Package附加非资产目录把Content/JavaScript目录添加进去然后重新打包。13. TypeScript 版本升级背景开启“继承 UE 类”功能后Puerts 需要调用 TypeScript 编译器来分析 TS 语法并生成代理蓝图。编译器安装在YourProject/Plugins/Puerts/Content/JavaScript/PuertsEditor该目录还会拷贝到YourProject/Content/JavaScript/PuertsEditor两处都有package.json记录版本号当前仓库内置版本为typescript: 4.7.4见 unreal/Puerts/Content/JavaScript/PuertsEditor/package.json。升级步骤修改上述两个目录里的package.json中的 TypeScript 版本号两处都要改分别进入这两个目录执行npm install .。版本兼容性以当前 FAQ 记录为准且随 Puerts 的修改可能变动状态版本有项目长期使用稳定3.4.5、4.4.4、4.7.4有项目简单测试通过4.8.2不支持高于4.8.3重要如果未开启“继承 UE 类”功能则不需要用 TypeScript 库分析语法因此不适用上述版本限制可以使用任意版本的 TypeScript。14.ue_bp.d.ts报错重新生成无效原因蓝图声明文件默认是增量生成的文件不变化就不重新生成。有时它所依赖的类型发生了变化或者文件被版本管理工具修改过导致增量生成无法修复。解决方案在编辑器控制台执行全量生成Puerts.Gen FULL源码佐证该控制台命令注册于 DeclarationGenerator.cppFAutoConsoleCommand命令名Puerts.Gen负责触发 DTS 生成流程。15. TS 继承 UE 类后不生成代理蓝图的定位步骤按以下顺序排查检查命令是否可用在 UE 命令行界面输入puerts ls如果报Puerts command not initialized说明环境没安装好或该功能未启用请对照安装文档检查。源码中该报错出现在 PuertsEditorModule.cpp即CmdImpl尚未初始化时Puerts命令的回调分支。确认 TS 文件是否在编译范围内查找指定 TS 类puerts ls TsTestActor如果查不到该文件说明它没有被纳入 TS 工程请检查tsconfig.json标准 TS 工程配置配置方式参见 TypeScript 官方文档。检查标记列如果puerts ls TsTestActor能查到文件看isBP与processed两列——若isBP false且processed true说明类格式不正确请参考 Puerts 的《继承引擎类功能》文档。单独编译该文件可手动触发单个文件的编译puerts compile file-id其中file-id是puerts ls TsTestActor返回的 ID例如e9050088932a23f720713a9a5073986e。若编译报错则解决错误无编译错误时一般就能正常生成对应的代理蓝图。16. 生成的ue_bp.d.ts报语法错误原因蓝图的路径、字段名、参数名等含有 TS 不支持的字符。解决方案如果此类蓝图数量较少加入黑名单Project Settings → Puerts中配置对应 PuertsSetting.h 中的D.ts Ignore Class Name List/D.ts Ignore Struct Name List如果此类蓝图数量较多把需要在代码中访问的、合法的蓝图放入一个单独目录然后在编辑器控制台指定搜索路径生成类型声明Puerts.Gen PATH/Game/StarterContent该命令只会搜索Content/StarterContent目录从而避开非法命名的蓝图。17. 概率性报Maximum call stack size exceeded判断要点注意是概率性出现而不是必然复现。原因通常是多线程访问了同一个FJsEnv。解决方案在JsEnv.Build.cs中加入THREAD_SAFE宏重新编译试试。源码佐证JsEnv.Build.cs 中ThreadSafe默认为false构建时通过PublicDefinitions.Add(ThreadSafe ? THREAD_SAFE : NOT_THREAD_SAFE)写入宏。打开THREAD_SAFE后JsEnvImpl.cpp 等文件中的#ifdef THREAD_SAFE分支例如 L942-L944 的v8::Locker Locker(MainIsolate)会启用锁保护。注意事项以上是 V8 后端的现象与解决方案QJSQuickJS后端目前不支持多线程多线程访问时可能抛出没有文件信息的异常unknown:-1如果该错误是必现的那多半不是多线程问题而是 JS 代码中存在递归死循环。附FAQ 问题速查表问题一句话解法关键文件new (std::nothrow) int[0]告警信息性告警Puerts 已自动修复忽略即可JsEnvModule.cpp自动绑定模式扩展函数缺失模块加载完后调用InitExtensionMethodsMap()PuertsModule.cppWaitDebugger 卡死关闭进程改DefaultPuerts.ini中WaitDebuggerFalsePuertsSetting.hStaticClass返回异常 UClass用UE.Class.Load(path)加载蓝图—macOS dylib 无法验证开发者sudo xattr -r -d com.apple.quarantine *.dylib—纯蓝图工程提示无法编译手动生成 VS/Xcode 工程编译—打包后字段找不到FName编辑器/运行时大小写策略差异统一命名—UE5 报 illegal thread关闭AsyncLoadingThreadEnabled—access a invalid object设计上避免持有无效对象必要时 try-catch—GC 生命周期stub 持有 UE / UE 持有 stub 两种模型切场景强制销毁—Puerts_UserObjectRetainer引用释放 JS 引用 触发 GCLowMemoryNotificationJsEnvImpl.cpp打包后脚本不执行打包设置里添加Content/JavaScript目录—TypeScript 版本升级改两处package.json后npm install .PuertsEditor/package.jsonue_bp.d.ts报错重生成无效执行Puerts.Gen FULLDeclarationGenerator.cpp不生成代理蓝图puerts ls/puerts compile file-id逐层定位PuertsEditorModule.cppue_bp.d.ts语法错误黑名单或Puerts.Gen PATH...限定路径PuertsSetting.h概率性 Maximum call stack加THREAD_SAFE宏QJS 不支持多线程JsEnv.Build.cs结语这 16 个 FAQ 覆盖了 Puerts for Unreal 从环境安装到运行时行为的大部分“坑位”。其中既有引擎本身行为导致的告警如new重载、FName大小写也有 Puerts 架构特性带来的约束如 stub 的 GC 模型、线程安全、扩展方法扫描时机。对照 unreal/Puerts/Source 下的实现代码可以更清晰地理解每一个现象背后的机制——这也是在遇到 FAQ 未覆盖的新问题时最可靠的排查路径。【免费下载链接】puertsPUER(普洱) Typescript. Lets write your game in UE or Unity with TypeScript.项目地址: https://gitcode.com/GitHub_Trending/pu/puerts创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考