BlueRoseNote/03-UnrealEngine/Gameplay/PuerTS/Puerts(一)——学习资料归纳.md

18 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
Puerts——学习资料归纳 2023-07-24 15:12:29 Puerts TypeScript

前言

https://github.com/Tencent/puerts

Puerts

TypeScript

TypeScript Setup

安装:

npm install -g typescript

编译:

tsc xxx.ts

Puerts

Setup

  1. 将对应版本的Puerts放入插件目录。比如puerts_nodejs的puerts_nodejs\puerts_nodejs\Puerts直接放到插件目录即可。
  2. 编译插件。
  3. 进入插件目录Plugins\Puerts执行node enable_puerts_module.js
  4. 在项目根目录下执行npm init并且添加。之后重新生成一次VS解决方案并且执行npm install
"dependencies": {
		"@types/react": "^15.6.6",
		"@types/react-reconciler": "^0.18.0",
		"@types/mocha": "^7.0.1"
 }
  1. 代码写在项目目录下的TypeScript中,之后就会在编辑器Blueprints/TypeScript目录下出现资产图标。
  2. 加入ReactUMG。 非必须
    1. 进入 Content/javascript 目录 npm init 创建 package.json。
    2. 创建成功后,向文件中粘贴如下内容

"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<puerts::FJsEnv>(std::make_unique<puerts::DefaultJSModuleLoader>(TEXT("JavaScript")), std::make_shared<puerts::FDefaultLogger>(), 8080);

阻塞等待调试器链接

GameScript = MakeShared<puerts::FJsEnv>(std::make_unique<puerts::DefaultJSModuleLoader>(TEXT("JavaScript")), std::make_shared<puerts::FDefaultLogger>(), 8080);
GameScript->WaitDebugger();
GameScript->Start("QuickStart", Arguments);

方式2自动绑定模式下调试配置

  1. 菜单上选择Edit->ProjectSettings,打开设置页面后在Plugins -> Puerts Setting页面中开启调试以及设置端口。

TypeScript

Express

使用ts-node将ts文件编译在内存中

在使用ts-node之前需要进行全局安装

$ npm install ts-node -g

# 用ts-node直接运行项目这个库会将我们的ts文件编译成js文件保存在内存中进行引用
$ ts-node ./bin/www
# 热更新模式
$ ts-node-dev ./bin/www

虽然ts-node可以帮我们直接运行ts文件但在开发完成后部署在生产环境时还是推荐使用tsc打包出来的js文件会更加稳定。

配置npm脚本

"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

蓝图相关

//蓝图加载
//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

//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脚本方法

//h
TSharedPtr<puerts::FJsEnv> GameScript;

//cpp
GameScript = MakeShared<puerts::FJsEnv>();
TArray<TPair<FString, UObject*>> Arguments;  
Arguments.Add(TPair<FString, UObject*>(TEXT("GameInstance"), this));  
GameScript->Start("QuickStart", Arguments);
//在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

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<T>(arr: UE.TArray<T>)
{
    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');
});

打包流程

生成的js脚本不是ue资产文件(*.asset),需要手动设置打包。 到“项目设置/打包/Additional Not-Asset Directories to Package”把Content下的“JavaScript”目录添加进去。