Init
This commit is contained in:
184
03-UnrealEngine/Gameplay/PuerTS/Node-Pinus(一).md
Normal file
184
03-UnrealEngine/Gameplay/PuerTS/Node-Pinus(一).md
Normal 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 提供了一系列的工具和库供开发者使用,这些工具和库能够协助开发者更好地完成应用开发、调试以及部署等工作。这些工具和库涵盖全面,有管理控制工具,有用来做压力测试的工具,也有一些比较通用的库。
|
||||
|
||||
- 命令行工具pomelo:pomelo框架提供的一个较简单的工具,该工具可以帮助开发者更便捷、更有效地进行应用开发,包括创建项目、启动应用、停止应用、关闭应用等等,请参考pomelo命令行工具使用。
|
||||
- pomelo-cli:pomelo-cli是一个pomelo服务器群的管理客户端,通过连接注册到master服务器,可以对服务器群进行较为高级的管理,如运行时动态的添加关闭服务器,查看服务器的状态等等。请参考pomelo-cli更详细的文档。
|
||||
- pomelo-robot:pomelo-robot是一个用来对pomelo游戏框架进行性能测试的工具,可以帮助开发者做一些压力测试,请参考pomelo-robot更详细的文档
|
||||
- pomelo-daemon:pomelo-daemon 提供了一个 daemon 服务,可以用这个服务来进行分布式部署以及日志收集。请参考pomelo-daemon的使用。
|
||||
- pomelo-admin-web:pomelo-admin-web 是 pomelo 框架中基于pomelo-admin开发的web端监控的模块,可以通过 web 端的方式来对游戏服务器集群的运行状态,性能,日志等进行实时的监控。请参考pomelo-admin-web工具的使用。
|
||||
- pomelo-sync:pomelo-sync 模块是用来管理游戏进程中需要持久化的数据在内存与存储系统之间同步的。请参考pomelo sync 使用文档
|
||||
- pomelo-protobuf:pomelo-protobuf 是对google protobuf的一个实现,借助javascript的语言特性,实现了类.proto文件的运行时解析,并用在pomelo框架中,完成对要传输消息的压缩。protobuf不仅可以用在服务端,也同样可以用于web客户端。具体请参考pomelo-protobuf。
|
||||
|
||||
## Chat源码
|
||||
这个是很简单的应用,其代码结构如下图:
|
||||

|
||||
#### [](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里面有三个目录,分别是gate,connector,chat。在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服务器。在这个目录下,主要是客户端的js,css和静态资源等等。在本例子中,里面有用户登录,聊天的逻辑的js文件等等。我们在这个例子教程中,更多地关注的是服务器端的逻辑以及功能,对于客户端,我们几乎不需要怎么修改其代码,直接使用默认就好。
|
||||
|
||||
### chat分析
|
||||
我们要搭建的pomelo聊天室具有如下的运行架构:
|
||||

|
||||
在这个架构里,前端服务器也就是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,那么就定义了这个服务器的行为,就决定了这个服务器的类型。在本例子中,有三种服务器,gate,connector,chat,它们完成的具体逻辑如下:
|
||||
|
||||
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。
|
96
03-UnrealEngine/Gameplay/PuerTS/Puerts Quick Start.md
Normal file
96
03-UnrealEngine/Gameplay/PuerTS/Puerts Quick Start.md
Normal 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向作者提问。
|
483
03-UnrealEngine/Gameplay/PuerTS/Puerts(一)——学习资料归纳.md
Normal file
483
03-UnrealEngine/Gameplay/PuerTS/Puerts(一)——学习资料归纳.md
Normal 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
|
||||
- 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. 将对应版本的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”目录添加进去。
|
Reference in New Issue
Block a user