--- title: Puerts(一)——学习资料归纳 date: 2023-07-24 15:12:29 excerpt: tags: Puerts TypeScript rating: ⭐ --- # 前言 https://github.com/Tencent/puerts - [安装方法](https://github.com/Tencent/puerts/blob/master/doc/unreal/zhcn/install.md) - [FAQ](https://github.com/Tencent/puerts/blob/master/doc/unreal/zhcn/faq.md) - [更新日志](https://github.com/Tencent/puerts/blob/master/doc/unreal/zhcn/changelog.md) ## Puerts - 相关文章&视频: - [UE引擎里头跑个nodejs服务器是怎样一种体验?](https://zhuanlan.zhihu.com/p/428250631) - [在你的ios、android应用中嵌入官方版nodejs是什么感觉?](https://zhuanlan.zhihu.com/p/568969543) - [[UnrealCircle深圳] puerts-UE下TypeScript编程插件 | 腾讯 车雄生](https://www.bilibili.com/video/BV1oB4y1A7dY/?spm_id_from=333.337.search-card.all.click&vd_source=d47c0bb42f9c72fd7d74562185cee290) - 案例工程: - https://github.com/chexiongsheng/puerts_unreal_demo - 知乎文章: - UE5 PuerTS学习与实践:https://zhuanlan.zhihu.com/p/632862773 - UE4下基于V8实现的代码热刷新:https://zhuanlan.zhihu.com/p/364505146 - PuerTS:js调用ue的过程:https://zhuanlan.zhihu.com/p/396751427 - 基于Puerts的编辑器UI开发-Mixin的非最佳实践:https://zhuanlan.zhihu.com/p/551338775 - 调试 - UE4:PuerTS的js调试相关:https://zhuanlan.zhihu.com/p/406387721 - Puerts Inspector指南(一)在UE4和Unity里调试Javascript:https://zhuanlan.zhihu.com/p/359598262 ## TypeScript - http://www.patrickzhong.com/TypeScript/zh/tutorials/typescript-in-5-minutes.html - https://ts.xcatliu.com/ - https://jkchao.github.io/typescript-book-chinese/#why # TypeScript Setup 安装: ```bash npm install -g typescript ``` 编译: ```bash tsc xxx.ts ``` # Puerts ## Setup 1. 编译插件。 2. 进入插件目录`Plugins\Puerts`执行`node enable_puerts_module.js`。 3. 在项目根目录下执行`npm init`,并且添加。之后重新生成一次VS解决方案并且执行`npm install`。 ```json "dependencies": { "@types/react": "^15.6.6", "@types/react-reconciler": "^0.18.0", "@types/mocha": "^7.0.1" } ``` 4. 代码写在项目目录下的`TypeScript`中,之后就会在编辑器`Blueprints/TypeScript`目录下出现资产图标。 5. 加入ReactUMG。 1. 进入 Content/javascript 目录 npm init 创建 package.json。 2. 创建成功后,向文件中粘贴如下内容 ```json "dependencies": { "react": "^16.11.0", "react-reconciler": "^0.23.0" } ``` 3. 然后 npm install 一次。 4. 打开工程根目录的 tsconfig.json, 在 typeRoots 中 加入 "Plugins/ReactUMG/Typing"。 6. 打开 工程,在引擎中点击 ue.d.ts 。 7. 在`ProjectSettings - Packaging - Additional Not-Asset Directories to Package`中添加`Content/javaScript`。 8. 在项目的Source下模块文件中添加`"JsEnv", "UMG", "Puerts"`,比如`PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "JsEnv", "UMG", "Puerts" });` ### Node环境设置 目前最新版本的Puerts只需修改JsEnv模块文件`JsEnv.Build.cs`里的UseNodejs为true即可。 ### 调试方法 具体可以参考: - Puerts Inspector指南(一)在UE4和Unity里调试Javascript:https://zhuanlan.zhihu.com/p/359598262 调试器的选择有: 1. 在Chrome输入`devtools://devtools/bundled/inspector.html?v8only=true&ws=127.0.0.1:8080`。 2. 在Chrome输入`chrome://inspect`,点击Configure...,输入IP&Port后,点击Inspect。 3. 使用VSCode进行调试。 1. 在`Launch Program`处点击`add Configuration`。 2. 选择`Node.js: Attach`。 3. 设置端口。 4. 点击绿色箭头即可调试。 #### 方式1:自创建虚拟机模式下调试配置 创建FJsEnv传入调试端口 ```c++ //8080是调试端口 GameScript = MakeShared(std::make_unique(TEXT("JavaScript")), std::make_shared(), 8080); ``` 阻塞等待调试器链接 ```c++ GameScript = MakeShared(std::make_unique(TEXT("JavaScript")), std::make_shared(), 8080); GameScript->WaitDebugger(); GameScript->Start("QuickStart", Arguments); ``` #### 方式2:自动绑定模式下调试配置 1. 菜单上选择`Edit->ProjectSettings`,打开设置页面后在`Plugins -> Puerts Setting`页面中开启调试以及设置端口。 # TypeScript - Express TypeScript化改造:https://www.jianshu.com/p/978628a15027 ## Express #### 使用ts-node将ts文件编译在内存中 在使用`ts-node`之前需要进行全局安装 ```shell $ npm install ts-node -g # 用ts-node直接运行项目,这个库会将我们的ts文件编译成js文件保存在内存中进行引用 $ ts-node ./bin/www # 热更新模式 $ ts-node-dev ./bin/www ``` 虽然`ts-node`可以帮我们直接运行ts文件,但在开发完成后部署在生产环境时,还是推荐使用`tsc`打包出来的`js`文件会更加稳定。 #### 配置npm脚本 ```json "scripts": { "start": "ts-node app.ts", "dev": "ts-node-dev app.ts", "build": "tsc", "server": "node ./dist/app.js" }, ``` # Puerts Puerts的使用方法主要为 - 继承 - 继承原生UE C++类:继承并且实现之后就会在 - Mixin: - 在C++类中调用TypeScript文件 ## puerts_unreal_demo 食用方法 项目默认使用UTsGameInstance,可以在cpp中修改OnStart()中调用的TS脚本文件名,来查看结果。优先查看TS文件: - QuickStart - UsingMixin - UsingMakeUClass - UI - UsingWidget - UsingReactUMG ### 蓝图相关 ```javascript //蓝图加载 //UE.Class.Load方式 //let bpClass = UE.Class.Load('/Game/StarterContent/TestBlueprint.TestBlueprint_C') //let bpActor = UE.GameplayStatics.BeginDeferredActorSpawnFromClass(gameInstance, bpClass, undefined) as UE.Game.StarterContent.TestBlueprint.TestBlueprint_C; blueprint.load(UE.Game.StarterContent.TestBlueprint.TestBlueprint_C); const TestBlueprint_C = UE.Game.StarterContent.TestBlueprint.TestBlueprint_C; //别名 let bpActor = UE.GameplayStatics.BeginDeferredActorSpawnFromClass(gameInstance, TestBlueprint_C.StaticClass(), undefined) as UE.Game.StarterContent.TestBlueprint.TestBlueprint_C; UE.GameplayStatics.FinishSpawningActor(bpActor, undefined); bpActor.Foo(false, 8000, 9000); //如果确定后续不需要使用TestBlueprint_C了,应该unload节省内存 blueprint.unload(TestBlueprint_C); //蓝图结构体加载 //UE.UserDefinedStruct.Load方式 //let TestStruct = UE.UserDefinedStruct.Load("UserDefinedStruct'/Game/StarterContent/TestStruct.TestStruct'"); //let testStruct = UE.NewStruct(TestStruct) as UE.Game.StarterContent.TestStruct.TestStruct; blueprint.load(UE.Game.StarterContent.TestStruct.TestStruct); const TestStruct = UE.Game.StarterContent.TestStruct.TestStruct; let testStruct = new TestStruct(); testStruct.age = 10; testStruct.speed = 5; bpActor.Bar(testStruct); blueprint.unload(TestStruct); //蓝图枚举 console.log("-------------------------15---------------------------"); console.log(UE.Game.StarterContent.TestEnum.TestEnum.Blue); console.log(UE.Game.StarterContent.TestEnum.TestEnum.Red); console.log(UE.Game.StarterContent.TestEnum.TestEnum.Green); ``` ### Delegate ```javascript //Delegate function MutiCast1(i) {     console.warn("MutiCast1<<<", i); } function MutiCast2(i) {     console.warn("MutiCast2>>>", i);     actor.NotifyWithInt.Remove(MutiCast2);//调用一次后就停掉 } actor.NotifyWithInt.Add(MutiCast1) actor.NotifyWithInt.Add(MutiCast2) console.log("NotifyWithString.IsBound", actor.NotifyWithString.IsBound()); console.log("NotifyWithRefString.IsBound", actor.NotifyWithRefString.IsBound()); actor.NotifyWithRefString.Bind((strRef) => { //console.error("NotifyWithRefString"); console.log("NotifyWithRefString", $unref(strRef)); $set(strRef, "out to NotifyWithRefString");//引用参数输出 }); console.log("NotifyWithString.IsBound", actor.NotifyWithString.IsBound()); console.log("NotifyWithRefString.IsBound", actor.NotifyWithRefString.IsBound()); actor.NotifyWithStringRet.Bind((inStr) => { return "////" + inStr; }); actor.NotifyWithInt.Broadcast(888999); let strRef = $ref("666"); actor.NotifyWithRefString.Execute(strRef); console.log("out str:" + $unref(strRef)); let retStr = actor.NotifyWithStringRet.Execute("console.log('hello world')"); console.log("ret str:" + retStr); console.log("waiting native call script..........."); //Pass JsFunction as Delegate function IsJohn(str:string) : boolean { return str == "John"; } obj.PassJsFunctionAsDelegate(toManualReleaseDelegate(IsJohn)); //release after using releaseManualReleaseDelegate(IsJohn); //unhandledRejection on('unhandledRejection', function(reason: any) { console.log('unhandledRejection~~~'); }); new Promise(()=>{ throw new Error('unhandled rejection'); }); ``` ## 热更新方法 https://zhuanlan.zhihu.com/p/364505146 ## UE C++调用Puerts脚本方法 ```c++ //h TSharedPtr GameScript; //cpp GameScript = MakeShared(); TArray> Arguments; Arguments.Add(TPair(TEXT("GameInstance"), this)); GameScript->Start("QuickStart", Arguments); ``` ```javascript //在FJsEnv启动,调用Start时传入的参数可以通过argv获取。如果是继承ue类方式,这里的argv是空的 let gameInstance = (argv.getByName("GameInstance") as UE.GameInstance); let actor =  UE.GameplayStatics.BeginDeferredActorSpawnFromClass(gameInstance, UE.MainActor.StaticClass(), undefined) as UE.MainActor; UE.GameplayStatics.FinishSpawningActor(actor, undefined); console.log(actor.GetName()); console.log(actor.K2_GetActorLocation().ToString()); ``` ## UE C++访问Puerts 通过UDynamicDelegateProxy,其成员记录了绑定的虚拟机与JS函数。 # QuickStart.ts ```ts import * as UE from 'ue' import {$ref, $unref, $set, argv, on, toManualReleaseDelegate, releaseManualReleaseDelegate, blueprint} from 'puerts'; let obj = new UE.MainObject(); //调试器通过websocket发送断点信息,可能断点生效前脚本已经执行完备,可以通过debugger语句来主动触发断点 //debugger; //成员访问 console.log("------------------------0----------------------------"); console.log("before set", obj.MyString) obj.MyString = "PPPPP"; console.log("after set", obj.MyString) //简单类型参数函数 console.log("------------------------1----------------------------"); let sum = obj.Add(100, 300); console.log('sum', sum) //复杂类型参数函数 console.log("------------------------2----------------------------"); obj.Bar(new UE.Vector(1, 2, 3)); //引用类型参数函数 console.log("------------------------3----------------------------"); let vectorRef = $ref(new UE.Vector(1, 2, 3)) obj.Bar2(vectorRef); obj.Bar($unref(vectorRef)); //静态方法 console.log("-----------------------4-----------------------------"); let str1 = UE.JSBlueprintFunctionLibrary.GetName(); let str2 = UE.JSBlueprintFunctionLibrary.Concat(', ', str1); UE.JSBlueprintFunctionLibrary.Hello(str2); //扩展方法,和C#的扩展方法类似 console.log("-----------------------5-----------------------------"); let v = new UE.Vector(3, 2, 1) console.log(v.ToString()); v.Set(8, 88, 888) console.log(v.ToString()); //静态wrap console.log("-----------------------6-----------------------------"); let vec = new UE.Vector(1, 2, 3) console.log('vec', vec.ToString()) vec.X = 3 vec.Y = 2 vec.Z = 1 vec.Normalize(1) console.log('vec', vec.ToString()) console.log(vec.Projection().ToString()) console.log('vec', vec.ToString()) //枚举 console.log("-----------------------7-----------------------------"); obj.EnumTest(UE.EToTest.V1); obj.EnumTest(UE.EToTest.V13); //默认值 console.log("-----------------------8-----------------------------"); obj.DefaultTest(); obj.DefaultTest("hello john"); obj.DefaultTest("hello john", 1024); obj.DefaultTest("hello john", 1024, new UE.Vector(7, 8, 9)); //定长数组 console.log("-----------------------9-----------------------------"); console.log("MyFixSizeArray.Num()", obj.MyFixSizeArray.Num()) console.log("MyFixSizeArray[32]", obj.MyFixSizeArray.Get(32)) console.log("MyFixSizeArray[33]", obj.MyFixSizeArray.Get(33)) console.log("MyFixSizeArray[34]", obj.MyFixSizeArray.Get(34)) obj.MyFixSizeArray.Set(33, 1000) console.log("MyFixSizeArray[32]", obj.MyFixSizeArray.Get(32)) console.log("MyFixSizeArray[33]", obj.MyFixSizeArray.Get(33)) console.log("MyFixSizeArray[34]", obj.MyFixSizeArray.Get(34)) //TArray console.log("------------------------10----------------------------"); function printTArray(arr: UE.TArray) { console.log("-----Num:", arr.Num()); for(var i=0; i < arr.Num(); i++) { console.log(i, ":", arr.Get(i)); } } printTArray(obj.MyArray); obj.MyArray.Add(888); obj.MyArray.Set(0, 7); printTArray(obj.MyArray); //TSet console.log("------------------------11----------------------------"); console.log(obj.MySet.Num()) console.log(obj.MySet.Contains("John")); console.log(obj.MySet.Contains("Che")); console.log(obj.MySet.Contains("Hello")); //TMap console.log("------------------------12----------------------------"); console.log(obj.MyMap.Get("John")) console.log(obj.MyMap.Get("Che")) console.log(obj.MyMap.Get("Hello")) obj.MyMap.Add("Che", 10) console.log(obj.MyMap.Get("Che")) //ArrayBuffer console.log("-------------------------13---------------------------"); let ab = obj.ArrayBuffer; let u8a1 = new Uint8Array(ab); for (var i = 0; i < u8a1.length; i++) { console.log(i, u8a1[i]); } obj.ArrayBufferTest(ab); obj.ArrayBufferTest(new Uint8Array(ab)); let ab2 = obj.ArrayBufferTest(new Uint8Array(ab, 5)); let u8a2 = new Uint8Array(ab2); console.log(u8a2.length); for (var i = 0; i < u8a2.length; i++) { console.log(i, u8a2[i]); } //引擎方法 console.log("--------------------------14--------------------------"); //在FJsEnv启动,调用Start时传入的参数可以通过argv获取。如果是继承ue类方式,这里的argv是空的 let gameInstance = (argv.getByName("GameInstance") as UE.GameInstance); let actor = UE.GameplayStatics.BeginDeferredActorSpawnFromClass(gameInstance, UE.MainActor.StaticClass(), undefined) as UE.MainActor; UE.GameplayStatics.FinishSpawningActor(actor, undefined); console.log(actor.GetName()); console.log(actor.K2_GetActorLocation().ToString()); //蓝图加载 //UE.Class.Load方式 //let bpClass = UE.Class.Load('/Game/StarterContent/TestBlueprint.TestBlueprint_C') //let bpActor = UE.GameplayStatics.BeginDeferredActorSpawnFromClass(gameInstance, bpClass, undefined) as UE.Game.StarterContent.TestBlueprint.TestBlueprint_C; blueprint.load(UE.Game.StarterContent.TestBlueprint.TestBlueprint_C); const TestBlueprint_C = UE.Game.StarterContent.TestBlueprint.TestBlueprint_C; //别名 let bpActor = UE.GameplayStatics.BeginDeferredActorSpawnFromClass(gameInstance, TestBlueprint_C.StaticClass(), undefined) as UE.Game.StarterContent.TestBlueprint.TestBlueprint_C; UE.GameplayStatics.FinishSpawningActor(bpActor, undefined); bpActor.Foo(false, 8000, 9000); //如果确定后续不需要使用TestBlueprint_C了,应该unload节省内存 blueprint.unload(TestBlueprint_C); //蓝图结构体加载 //UE.UserDefinedStruct.Load方式 //let TestStruct = UE.UserDefinedStruct.Load("UserDefinedStruct'/Game/StarterContent/TestStruct.TestStruct'"); //let testStruct = UE.NewStruct(TestStruct) as UE.Game.StarterContent.TestStruct.TestStruct; blueprint.load(UE.Game.StarterContent.TestStruct.TestStruct); const TestStruct = UE.Game.StarterContent.TestStruct.TestStruct; let testStruct = new TestStruct(); testStruct.age = 10; testStruct.speed = 5; bpActor.Bar(testStruct); blueprint.unload(TestStruct); //蓝图枚举 console.log("-------------------------15---------------------------"); console.log(UE.Game.StarterContent.TestEnum.TestEnum.Blue); console.log(UE.Game.StarterContent.TestEnum.TestEnum.Red); console.log(UE.Game.StarterContent.TestEnum.TestEnum.Green); //Delegate console.log("--------------------------16--------------------------"); function MutiCast1(i) { console.warn("MutiCast1<<<", i); } function MutiCast2(i) { console.warn("MutiCast2>>>", i); actor.NotifyWithInt.Remove(MutiCast2);//调用一次后就停掉 } actor.NotifyWithInt.Add(MutiCast1) actor.NotifyWithInt.Add(MutiCast2) console.log("NotifyWithString.IsBound", actor.NotifyWithString.IsBound()); console.log("NotifyWithRefString.IsBound", actor.NotifyWithRefString.IsBound()); actor.NotifyWithRefString.Bind((strRef) => { //console.error("NotifyWithRefString"); console.log("NotifyWithRefString", $unref(strRef)); $set(strRef, "out to NotifyWithRefString");//引用参数输出 }); console.log("NotifyWithString.IsBound", actor.NotifyWithString.IsBound()); console.log("NotifyWithRefString.IsBound", actor.NotifyWithRefString.IsBound()); actor.NotifyWithStringRet.Bind((inStr) => { return "////" + inStr; }); actor.NotifyWithInt.Broadcast(888999); let strRef = $ref("666"); actor.NotifyWithRefString.Execute(strRef); console.log("out str:" + $unref(strRef)); let retStr = actor.NotifyWithStringRet.Execute("console.log('hello world')"); console.log("ret str:" + retStr); console.log("waiting native call script..........."); //Pass JsFunction as Delegate function IsJohn(str:string) : boolean { return str == "John"; } obj.PassJsFunctionAsDelegate(toManualReleaseDelegate(IsJohn)); //release after using releaseManualReleaseDelegate(IsJohn); //unhandledRejection on('unhandledRejection', function(reason: any) { console.log('unhandledRejection~~~'); }); new Promise(()=>{ throw new Error('unhandled rejection'); }); ```