This commit is contained in:
2025-08-02 12:09:34 +08:00
commit e70b01cdca
2785 changed files with 575579 additions and 0 deletions

View File

@@ -0,0 +1,184 @@
---
title: Node-Pinus
date: 2023-08-01 17:23:58
excerpt:
tags: TypeScript
rating: ⭐
---
# 前言
- Node-Pinus游戏服务器框架:https://github.com/node-pinus/pinus
- 案例:https://github.com/node-pinus/pinus/tree/master/examples/simple-example
- Pomelo的wiki:https://github.com/NetEase/pomelo/wiki/Home-in-Chinese
# ReadME
## 启动方法
1. 执行npm-install.bat或npm-install.sh
2. 编译游戏服
```
cd game-server
npm run build
```
## 启动游戏服
```bash
cd dist
node app
```
显示“all servers startup in xxx ms”即表示启动成功
## 启动网页服务器
```bash
cd web-server
node app
```
显示“Please log on http://127.0.0.1:3001/index.html”即表示启动成功
## 进入客户端网页
浏览器输入
http://127.0.0.1:3001/index.html
点击“Test Game Server”如返回“game server is ok.”即表示连接游戏服务器返回成功
## 调试游戏服务器的方法
1. 安装vscode
2. 在game-server目录启动vscode
3. 按照正常流程启动游戏服
4. 在“调试”界面选择Attach To Connector或Attach To Master
5. 按F5把调试器挂上去然后就可以断点调试了。
# Pomelo相关资料
## Pomelo工具和库介绍
pomelo 提供了一系列的工具和库供开发者使用,这些工具和库能够协助开发者更好地完成应用开发、调试以及部署等工作。这些工具和库涵盖全面,有管理控制工具,有用来做压力测试的工具,也有一些比较通用的库。
- 命令行工具pomelopomelo框架提供的一个较简单的工具该工具可以帮助开发者更便捷、更有效地进行应用开发包括创建项目、启动应用、停止应用、关闭应用等等请参考pomelo命令行工具使用。
- pomelo-clipomelo-cli是一个pomelo服务器群的管理客户端通过连接注册到master服务器可以对服务器群进行较为高级的管理如运行时动态的添加关闭服务器查看服务器的状态等等。请参考pomelo-cli更详细的文档。
- pomelo-robotpomelo-robot是一个用来对pomelo游戏框架进行性能测试的工具可以帮助开发者做一些压力测试请参考pomelo-robot更详细的文档
- pomelo-daemonpomelo-daemon 提供了一个 daemon 服务可以用这个服务来进行分布式部署以及日志收集。请参考pomelo-daemon的使用。
- pomelo-admin-webpomelo-admin-web 是 pomelo 框架中基于pomelo-admin开发的web端监控的模块可以通过 web 端的方式来对游戏服务器集群的运行状态性能日志等进行实时的监控。请参考pomelo-admin-web工具的使用。
- pomelo-syncpomelo-sync 模块是用来管理游戏进程中需要持久化的数据在内存与存储系统之间同步的。请参考pomelo sync 使用文档
- pomelo-protobufpomelo-protobuf 是对google protobuf的一个实现借助javascript的语言特性实现了类.proto文件的运行时解析并用在pomelo框架中完成对要传输消息的压缩。protobuf不仅可以用在服务端也同样可以用于web客户端。具体请参考pomelo-protobuf。
## Chat源码
这个是很简单的应用,其代码结构如下图:
![源码结构图](https://github.com/NetEase/pomelo/wiki/images/source.png)
#### [](https://github.com/NetEase/pomelo/wiki/chat%E6%BA%90%E7%A0%81%E4%B8%8B%E8%BD%BD%E4%B8%8E%E5%AE%89%E8%A3%85#game-server)game-server
game-server目录放的是所有游戏服务器的逻辑以文件app.js作为入口运行游戏的所有逻辑和功能。从图上可以看出其servers里面有三个目录分别是gateconnectorchat。在pomelo中使用路径来区分服务器类型因此三个目录代表了三种不同类型的服务器每一个目录下面可以定义handler,remote,定义了handler和remote就决定了这个服务器的行为。
- 对于gate服务器其逻辑实现代码在其gateHandler.js中它接受客户端查询connector的请求返回给客户端一个可以连接的connector的(ip,port);
- connector服务器其逻辑代码在entryHandler.js中它主要完成接受客户端的请求维护与客户端的连接路由客户端的请求到chat服务器;
- chat服务器其既有handler代码也有remote代码 handler中处理用户的send请求而remote是当有用户加入或者退出的时候由connector来发起远程调用时调用的。在remote里由于涉及到用户的加入和退出所以会有对channel的操作。
game-server 的子目录config下面是游戏服务器所用到的配置文件存放的地方配置信息使用JSON格式包含有日志master服务器和其他服务器的配置信息。除了这个pomelo所需的配置信息外一般情况下也将游戏逻辑所需要的配置信息放到这个目录下例如数据库的配置信息地图信息等。
logs子目录下存放游戏服务器产生的所有的日志信息。
#### [](https://github.com/NetEase/pomelo/wiki/chat%E6%BA%90%E7%A0%81%E4%B8%8B%E8%BD%BD%E4%B8%8E%E5%AE%89%E8%A3%85#web-server)web-server
由于我们这个聊天应用的客户端是web所以需要一个web服务器。在这个目录下主要是客户端的jscss和静态资源等等。在本例子中里面有用户登录聊天的逻辑的js文件等等。我们在这个例子教程中更多地关注的是服务器端的逻辑以及功能对于客户端我们几乎不需要怎么修改其代码直接使用默认就好。
### chat分析
我们要搭建的pomelo聊天室具有如下的运行架构
![](https://github.com/NetEase/pomelo/wiki/images/multi-chat.png)
在这个架构里前端服务器也就是connector专门负责承载连接 后端的聊天服务器则是处理具体逻辑的地方。 这样扩展的运行架构具有如下优势:
- 负载分离:这种架构将承载连接的逻辑与后端的业务处理逻辑完全分离,这样做是非常必要的, 尤其是广播密集型应用(例如游戏和聊天)。密集的广播与网络通讯会占掉大量的资源,经过分离后业务逻辑的处理能力就不再受广播的影响。
- 切换简便因为有了前、后端两层的架构用户可以任意切换频道或房间都不需要重连前端的websocket。
- 扩展性好用户数的扩展可以通过增加connector进程的数量来支撑。频道的扩展可以通过哈希分区等算法负载均衡到多台聊天服务器上。理论上这个架构可以实现频道和用户的无限扩展。
#### 客户端
聊天室的逻辑包括以下几个部分:
- 用户进入聊天室这部分逻辑负责把用户信息注册到session并让用户加入聊天室的channel。
- 用户发起聊天: 这部分包括了用户从客户端发起请求,服务端接收请求等功能。
- 广播用户的聊天: 所有在同一个聊天室的客户端收到请求并显示聊天内容。
- 用户退出: 这部分需要做一些清理工作包括session和channel的清理。
客户端首先要给gate服务器查询一个connector服务器gate给其回复一个connector的地址及端口号这里没有列出完整的代码具体的代码在路径web-server/public/js/client.js中,详细代码略去见client.js:
```javascript
function queryEntry(uid, callback) {
var route = 'gate.gateHandler.queryEntry';
// ...
}
$("#login").click(function() {
username = $("#loginUser").attr("value");
rid = $('#channelList').val();
// ...
//query entry of connection
queryEntry(username, function(host, port) {
pomelo.init({
host: host,
port: port,
log: true
}, function() {
// ...
});
});
});
```
客户端在查询到connector后需要发请求给connector服务器 第一次请求要给connector进程因为首次进入时需要绑定对应的uid信息这里略去详细代码:
```javascript
pomelo.request('connector.entryHandler.enter', {username: username, rid: rid}, function(){
// ...
});
```
当用户发起聊天的时候会请求服务chat.chatHandler.send大致代码如下:
```javascript
pomelo.request('chat.chatHandler.send', {content:msg, from: username, target: msg.target}, function(data) {
// ...
});
```
当有用户加入、离开以及发起聊天时,同房间的人将会收到服务端推送来的相应消息,这些在客户端是以回调的方式进行添加的,大致代码如下:
```javascript
pomelo.on('onAdd', function(data) {
// ...
});
pomelo.on('onLeave', function(data) {
// ...
});
pomelo.on('onChat', function(data) {
// ...
});
```
客户端的详细代码都在目录web-server/public/js/client.js文件中这里客户端的js是使用component进行管理的,详细请参阅component的参考文档。
#### 服务端
我们知道在pomelo中只要定义了一个服务器的handler和remote那么就定义了这个服务器的行为就决定了这个服务器的类型。在本例子中有三种服务器gateconnectorchat,它们完成的具体逻辑如下:
gate完成客户端对connector的查询在其handler里有其实现的代码由于在这里本例中仅仅配置了一台connector服务器因此直接返回其信息给客户端即可然后客户端就可以连接到connector了。
```javascript
handler.queryEntry = function(msg, session, next) {
var uid = msg.uid;
// ...
};
```
connector接受用户的连接完成用户的注册及绑定维护客户端session信息处理客户端的断开连接其逻辑代码在connector/handler/entryHandler.js中。大致如下
```javascript
handler.enter = function(msg, session, next) {
var self = this;
var rid = msg.rid;
var uid = msg.username + '*' + rid
var sessionService = self.app.get('sessionService');
// .....
};
```
chat服务器是执行聊天逻辑的地方它维护channel信息一个房间就是一个channel一个channel里有多个用户当有用户发起聊天的时候就会将其内容广播到整个channel。chat服务器还会接受connector的远程调用完成channel维护中的用户的加入以及离开因此chat服务器不仅定义了handler还定义了remote。当有客户端连接到connector上后connector会向chat发起远程过程调用chat会将登录的用户加到对应的channel中其大致代码为
```javascript
// chatHandler.js
handler.send = function(msg, session, next) {
var rid = session.get('rid');
var username = session.uid.split('*')[0];
// .....
};
// chatRemote.js
ChatRemote.prototype.add = function(uid, sid, name, flag, cb) {
var channel = this.channelService.getChannel(name, flag);
};
ChatRemote.prototype.kick = function(uid, sid, name) {
var channel = this.channelService.getChannel(name, false);
// ...
};
```
注意 在实现具体的Handler的时候最后需要调用next其中next的签名为 next(err, resp).如果没有出现错误那么err为空即可如果不是request请求而是notify的话则一样需要调用next此时resp参数是不需要的一般情况下如果没有错误的话就直接使用next(null)即可。
服务器配置信息在config目录下现在我们只关注servers.json, master.json。master.json配置是master服务器的配置信息包括地址端口号servers.json配置具体的应用服务器信息。在配置文件中分为development和production两种环境表示开发环境和产品环境我们在pomelo start后面可以通过-e可以指定使用哪个环境更多帮助参见pomelo start --help。

View File

@@ -0,0 +1,96 @@
---
title: 未命名
date: 2025-07-20 11:39:10
excerpt:
tags:
rating: ⭐
---
# 相关资料
- 官方文档:https://puerts.github.io/docs/puerts/unreal/manual
- 调试指南:https://puerts.github.io/docs/puerts/unreal/vscode_debug
- 脚本调用引擎API:https://puerts.github.io/docs/puerts/unreal/script_call_uclass
- 更多用法可以参考Puerts Demo https://puerts.github.io/docs/puerts/unreal/demos
- 尤其推荐***QuickStart.ts***:https://github.com/chexiongsheng/puerts_unreal_demo/blob/master/TypeScript/QuickStart.ts
- FAQ https://github.com/Tencent/puerts/blob/master/doc/unreal/zhcn/faq.md
- 哪里可以找到答案:
- https://github.com/Tencent/puerts/issues
- https://github.com/Tencent/puerts/discussions
# QuickStart
## Setup
1. 安装Nodejs v22.17.1。
2. 通过安装全局typeScrpit模块。
```bash
npm install -g typescript
```
3. 安装VSCode插件
![[Puerts_VSCode_TS.png]]
## Puerts Project Setup(跳过)
1. 下载Puerts插件:https://github.com/Tencent/puerts/releases
1. 可选版本有Nodejs一般情况下使用这个可使用NPM添加其他的库、V8纯净环境以及Quickjs包体小适合手机
2. 编译插件。
3. 进入插件目录`Plugins\Puerts`执行`node enable_puerts_module.js`
4. 在项目根目录下执行`npm init`并且添加。之后重新生成一次VS解决方案并且执行`npm install`。其中Mocha是必须安装的否则会出现找到编译后的js文件。
```json
"dependencies": {
"@types/mocha": "^10.0.10"
}
```
5. 打开工程,在引擎中点击 ue.d.ts 该功能用于生成项目、引擎符号信息生成之后就能找到相关符号了。如果想在ts文件中调用新增的蓝图&C++方法也需要点击ue.d.ts才能找到对应符号。可以阅读该文了解详细信息 https://puerts.github.io/docs/puerts/unreal/script_call_uclass
![[Puerts_UE_D_TS.png]]
6. 在`ProjectSettings - Packaging - Additional Not-Asset Directories to Package`中添加`Content/javaScript`。
# 调试方法
具体可以参考:
- 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. 点击绿色箭头即可调试。
***以下有2种添加调试入口的方式一般选择第二种勾选后启动游戏会处于冻结状态需要调试器VSCode、Chrome连上才能继续运行。如果没有设置好调试器可以打开Config\DefaultPuerts.ini把WaitDebugger改为False来跳过***
**添加调试入口方式1:自创建虚拟机模式下调试配置**
创建FJsEnv传入调试端口
```c++
//8080是调试端口 GameScript = MakeShared<puerts::FJsEnv>(std::make_unique<puerts::DefaultJSModuleLoader>(TEXT("JavaScript")), std::make_shared<puerts::FDefaultLogger>(), 8080);
```
阻塞等待调试器链接
```c++
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`页面中开启调试以及设置端口。
# 开发方法
## 继承引擎类功能
类似c++的继承式开发方法。
https://puerts.github.io/docs/puerts/unreal/uclass_extends
## 蓝图mixin
一般用于给蓝图添加一些TS逻辑。
https://puerts.github.io/docs/puerts/unreal/mixin
## 直接运行脚本
通过在c++对应类中GameMode、GameInstance等调用执行脚本的函数来调用指定名称的脚本。
```c++
GameScript = MakeShared<puerts::FJsEnv>(std::make_unique<puerts::DefaultJSModuleLoader>(TEXT("JavaScript")), std::make_shared<puerts::FDefaultLogger>(), 8080);
GameScript->Start("QuickStart", Arguments);
```
可以参考:https://github.com/chexiongsheng/puerts_unreal_demo/blob/master/TypeScript/QuickStart.ts·
# NEPY的问题
NEPY插件有好几个痛点
1. 通过自动生成C++代码进行符号导出所以每次使用脚本调用新的引擎类都会产生c++编译我支援的项目中每次都会产生1000+的编译任务,相当影响团队的开发效率。
2. Python是弱类型语言编码时容易写出bug或者卡壳。
3. NEPY针对蓝图的支持比较糟糕Puerts提供了mix蓝图方案。
4. NEPY的资料太少遇到问题大概率只能自己解决。Puerts的资料相对较多有bug或者问题都可以在github向作者提问。

View File

@@ -0,0 +1,483 @@
---
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
- PuerTSjs调用ue的过程https://zhuanlan.zhihu.com/p/396751427
- 基于Puerts的编辑器UI开发-Mixin的非最佳实践https://zhuanlan.zhihu.com/p/551338775
- 调试
- UE4PuerTS的js调试相关https://zhuanlan.zhihu.com/p/406387721
- Puerts Inspector指南在UE4和Unity里调试Javascripthttps://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. 将对应版本的Puerts放入插件目录。比如puerts_nodejs的`puerts_nodejs\puerts_nodejs\Puerts`直接放到插件目录即可。
2. 编译插件。
3. 进入插件目录`Plugins\Puerts`执行`node enable_puerts_module.js`
4. 在项目根目录下执行`npm init`并且添加。之后重新生成一次VS解决方案并且执行`npm install`。其中Mocha是必须安装的否则会出现找到编译后的js文件。
```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<puerts::FJsEnv>(std::make_unique<puerts::DefaultJSModuleLoader>(TEXT("JavaScript")), std::make_shared<puerts::FDefaultLogger>(), 8080);
```
阻塞等待调试器链接
```c++
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 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<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);
```
```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<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”目录添加进去。