泡饭工坊项目流程

来自泡泡

泡饭工坊介绍

泡饭工坊是什么?

泡饭是一套网页虚拟环境下的代码编辑工具,内嵌了多种预设和模板,集成了vscode来编辑代码,自动编译,可以方便的预览和分享给小伙伴体验。

泡饭链接

[链接](https://webdev.popx.com)

泡饭账号注册登录

没账号的需要在泡饭注册账号

点击左上角个人信息

点击注册

注册页面

请记住您的账号密码,这关系到后续我们管理您的使用权限

和我们内部人员沟通,申请后台权限

如需开通后台使用权限,将您的账号告知我们内部人员帮操作,等待通知完成后,重新刷新网页即可

第一次需要创建一个工程


有了工程后,打开工程

打开泡饭体验项目

参考视频泡饭下载模板[1]

参考视频泡饭运行模板[2]

如何用泡饭开发自己的项目

点击跳转后台

就第一次需要创建打开一个工程,才能出现地图编辑器的跳转页面

后台创建游戏,绑定地图

  • 需要泡饭跳转到后台,去新建游戏,生成独属于你的gameid和secret,这在后续的开发和上传时至关重要,请点击下方超链接,去参考其中新建gameid的部分
  • 请看该链接中,新建游戏玩法的部分

修改package.json进入自己的游戏和地图

- 点击左侧修改配置按钮

  
 - gameid 开发者后台中指定的游戏玩法
 - secretkey 游戏玩法对应的密钥
 - defaultMap 游戏玩法中,指定的地图,请在后台至少指定一张,默认填写"地图_0"
 - sdkVersion sdk版本号,点击修改可以弹出下拉框,一般选用release标签的稳定版本,修改后,会去修改对应的bridge依赖,并且下载
 - resVersion 美术资源版本号,会影响游戏内地图和模型的显示,一般选用最新的版本即可

首次运行需要先发布

当前的gameid是最新的,

目前后台服务器还没有一个初始版本的记录,所以需要在package.json中,修改完gameid之后。

首次运行要发布游戏(后续不需要每次发布,直接保存就可以编译调试)。

等待右上脚提示发布完成,即可启动进入游戏

启动项目

一般选用启动游戏(服务器模式)

登录界面

- 运行后,浏览器将显示登陆界面。

   
 - 账号密码随便输入,会创建新账号
 - 输入账号密码后,选择指定地图(可选),即可看到战斗服务器世界中有自己的人物。
 - 默认的demo工程中,如果没有修改gameid和secretKey,地图请填写"地图_0"

泡饭模板的多人合作

发布模板

设置团队成员权限和名称


发布后管理项目

发布后可以在商店模板里去修改自己发布的项目。

其他成员就可以在模板商店拉取模板了

比如上图中的playername1的账号,就可以拉取模板了

打开项目

点击编辑按钮,切换到vscode运行环境。

团队成员修改提交

我们尝试写第一行代码提交下

具体在vscode中,怎么使用git来提交和拉取,可以参考如下文档

[vscode使用git插件](https://blog.csdn.net/khhmd/article/details/144131405)

多人合作冲突的解决(遇到冲突再看,可以先跳过)

选择远端的master分支来merge

根据需求去修改冲突文件

冲突文件修改完成,resolve

同步到远端

前端基础逻辑

根据以下步骤,一步步进入泡泡的游戏世界,即使你是新手也无需担心。

前端游戏入口

- /src/client/index.ts 文件中的 `XPopManager.onGameStart` 是前端的主要入口点。

创建第一个人物

在 `simpleGame` 类中,我们将创建并初始化第一个游戏人物。

  
export class simpleGame {  
    private firstModel: XModelHero;  
  
    constructor() {  
        // 创建第一个人物  
        this.firstModel = new XModelHero();  
        let path = 'role/model_avatar_body_default';  
  
        // 加载人物模型并播放动画  
        this.firstModel.loadModel(path, null, this, (success) => {  
            // 播放跑步动画  
            let actName = "act_avatar_base_run";  
            this.firstModel.playAniUp(`${actName}_up`);  
            this.firstModel.playAniDown(`${actName}_down`);  
  
            // 摄像机跟随  
            let camera = XPopManager.instance.xScene.camera;  
            camera.focus = this.firstModel;  
  
            // 定时更新人物位置  
            XTimer.loop(100, this, this.updateModelPos.bind(this));  
        });  
    }  
  
    updateModelPos(delta: number, frame?: string) {  
        // 更新人物位置  
        this.firstModel.position.x += 0.1;  
    }  
}  
  
// 设置游戏启动时执行  
XPopManager.onGameStart = () => {  
    new simpleGame();  
}  
  • 注意:请将原代码中的 new Main() 替换为 new simpleGame();

上述的代码我们以最简单的方式,去掉一切服务器交互,添加一个基础模型到游戏中跑动

再提醒一次,每次有代码修改后,运行前需调用命令进行编译 #编译

播放人物的动作

我们的人物有上下半身,在知道动画名字的情况下,需要分上下半身来播放动画

以'act_avatar_plant_collect'为例

    let animbaseStr = 'act_avatar_plant_collect';
    role.model.playAniUp(`${animbaseStr}_up`);
    role.model.playAniDown(`${animbaseStr}_down`);

那么如何快速查看已有哪些动画呢?

[动作查看器](https://webdev.popx.com/v1/game/client_host.html?author=qc_jump&project=resourceviewer_0926&diyserver=1&gameid=0bkyyeesxnzsrd9s&editorFormal=true)

添加按键控制

看demo中的代码,提供了一个MoveComponent类 摇杆的显示资源放在res-extra目录中

以下是示例中添加摇杆控制的代码

        //摇杆背景
        let rocker = new XImage('res-extra/imgs/stick_bg.png');
        rocker.width = rocker.height = 164;
        rocker.pivotX = rocker.pivotY = 82;
        rocker.pos(178, -198);
        leftbottom.addChild(rocker);
        //摇杆方向
        let imgdir = new XImage('res-extra/imgs/stick_dir.png');
        imgdir.centerX = imgdir.centerY = 0;
        imgdir.anchorX = imgdir.anchorY = 0.5;
        imgdir.name = "dir";
        rocker.addChild(imgdir);
        //摇杆中心点
        let node = new XImage('res-extra/imgs/stick_rocker.png');
        node.centerX = node.centerY = 0;
        node.anchorX = node.anchorY = 0.5;
        node.name = "node";
        rocker.addChild(node);
        //移动
        let hero = this.heros.get(this.roleIndex);
        if (hero) {
            let movecomponent = rocker.addComponent(MoveComponent) as MoveComponent;
            movecomponent.init(hero, ctrlayer, rocker);
        }

示例中,添加了一个触摸区域挂载在根节点的左下角,触摸反馈会根据XEvent来传递给节点 代码中根据摇杆方向变化来控制人物移动 其中涉及了部分修改玩家状态的服务器代码,可以先忽略

摄像机的跟随

代码中,摄像机跟随人物移动,只需要简单的一行

let hero = new Xmodel();
//这是全局的摄像机
XPopManager.instance.xScene.camera.focus = hero;

摄像机的旋转视角,demo中也提供了一个类CameraComponent 和摇杆的节点类似,通过XEvent来传递点击事件, 根据点击事件的拖动,来修改摄像机的旋转视角 xcamera.rotate

    private onCtrlMouseMove(e: XEvent): void {
        if(this._touchId != e.touchId) return;
        let xcamera = XPopManager.instance.xScene.camera as XCamera;
        if(!xcamera)return;
        if(this._mouseX == 0) this._mouseX = e.stageX;
        if(this._mouseY == 0) this._mouseY = e.stageY;
        let offx = e.stageX-this._mouseX;
        let offy = e.stageY-this._mouseY;
        this._mouseX = e.stageX;
        this._mouseY = e.stageY;
        xcamera.rotate(offx, offy);
    }

砖块说明

如图所示,我们的世界地图由若干个方形大地块组成,

每个地块里,可以划分成32*32的平面坐标,一层层叠高,可以有64层高,

转化为三维坐标系就是32*64*32,对应xyz轴,

地块上,可以添加以坐标为单位的砖块

其中,cubeId表示砖块的类型,比如草地就是id为1的砖块,该砖块具有可以踩踏行走的特性。

某些其他的砖块具有别的特性,都通过cubeid来区分,当开发者接触地图编辑器时,就会有所了解。

我们以Demo中,MoveComponent中的一段代码来参考

            // 游戏世界地图
            let scene = XPopManager.instance.xScene;
            // 判断水平方向是否有砖块
            // 根据坐标位置,找到世界中,其坐标对应的砖块id
            let cubeid = scene.getCube(dir.x ,position.y, dir.z)
            if(cubeid){
                // 根据砖块id,找到砖块的类型,判断其是否和人物碰撞阻挡
                scene.getCubeInfo(cubeid, this.cubeInfo);
                if(this.cubeInfo.collisionPlayer){
                    dir.x = position.x;
                    dir.z = position.z;
                }
            }
            //如果没有砖块或者有无阻挡砖块,给一个向下的位移
            cubeid = scene.getCube(dir.x ,position.y-0.1, dir.z)
            if(cubeid){
                scene.getCubeInfo(cubeid, this.cubeInfo);
                if(!this.cubeInfo.collisionPlayer){
                    dir.y = position.y-0.1;
                }
            }else{
                dir.y = position.y-0.1;
            }
  • 再以图中为例,v3的坐标代表了地图上的位置,(24,3,26)
  • 如果需要设置模型在地图上的位置,用如下代码
let pos = new XVector3(x,y,z);
xModel.position = pos;

资源加载说明

演示加载图片

        // 测试加载图片
        // 创建一个全屏的挂载UI层
        let mainui = new XImage();
        mainui.left = mainui.right = mainui.top = mainui.bottom = 0;
        XPopManager.rootNode.addChild(mainui);

        // 图片路径,如果是本地的额外资源,就加上"res-extra/"前缀,否则就是远端资源
        let path = 'avatarstart/ball_btn2.png';
        // 创建图片
        let img = new XImage(path);
        // 添加到父节点显示
        mainui.addChild(img);
        // 设置位置
        img.left = img.top = 100;

编译,运行后,可以看到,添加了指定的图片

公共资源

PGT为开发者提供了很多基础的图片,模型,音频等等的资源。

怎么查看有哪些公共资源呢?

  • 打开项目目录下的package.json文件

学会查看远端json文件地址

查看pacakge.json

  • 查看当前的远端资源版本,比如"resVersion": "1.0.15",
  • 具体有哪些资源版本可用如下命令

查看资源版本列表:npm run reslist (只包含正式发布的版本)

查看所有资源版本列表:npm run reslistall

  • 如果用的泡饭工坊,直接在配制节目选择下拉框即可查看
  • 把下面链接里的v1.0.15替换成和你目前版本一致的数字,就可以查看远端资源列表

resVersion

如下是1.0.5版本的资源列表,可以修改数字查找不同版本

[远端文件列表json](https://h5ppreview.jumpw.com/popion/popx-res/version-1.0.5.json)

确认好resVersion和远程链接

在上述json文件中,可以搜索文件名,来判断资源在不在美术包里

查看json文件
  • json文件左侧是游戏内加载的路径,右侧是实际的打过md5值的网络路径

这就是我们刚刚演示的加载图片

avatarstart/ball_btn2.png": "avatarstart/ball_btn2-2c717f83a3.png

  • 游戏内我们通过加载接口去加载左侧路径
  • 游戏外,直接在刚刚的cdnUrl后拼接带md5值的完整路径即可查看

[查看网络资源](https://h5ppreview.jumpw.com/popion/sdk/v1.0.17/avatarstart/ball_btn2-2c717f83a3.png)

  • 音频文件同理,模型文件直接游戏内调用接口显示

额外资源

最基础的demo中,会看到用于演示的摇杆资源是放在res-extra目录下的。

一般来说,开发者的一些特制UI,图形,可以放在这里。

加载时在路径前加上"res-extra/"即可。

  • 注意,我们目前不支持第三方模型文件,模型必须提交审核,不放在res-extra中

模型查看器

  • 除了图片资源外,为了方便开发者使用,我们额外做了一个模型查看器

[公共模型查看器](https://webdev.popx.com/client_host.html?author=qc_jump&project=resourceviewer_0926&diyserver=1&gameid=09kyye6gcmmda79s&editorFormal=true)

UI界面开发

git拉取ui工程到本地

泡饭的项目发布后获取到了git地址

查看git密钥


使用accesstoken来拉取项目到本地

常见的git工具中

git clone 地址

输入泡饭账号和accesstoken即可拉取,注意不是泡饭的密码,是token

没有工具时用命令拉取说明

安装本地ui的编辑环境

laya 版本 v2.13.1

- [laya官方ide](https://ldc2.layabox.com/layadownload/?type=layaairide-LayaAir%20IDE%202.13.1)

安装项目自定义封装的cmd命令库

- 推荐安装方式:

  
   npm i popxcmd -g --registry https://registry.popx.com
  

node 版本 v10.24.1

- 必须使用指定的nodejs版本 ,推荐安装nvm以自由转换node版本。

 - [nvm下载及安装方式](https://blog.csdn.net/ppz8823/article/details/130862191)  
  • 记得nvm需要换源,否则后续安装可能会出错,按照下述操作

  • 最下面两行修改node和npm的镜像地址
node_mirror: https://npmmirror.com/mirrors/node/
npm_mirror: https://npmmirror.com/mirrors/npm/


  • 安装nvm后,命令行运行如下命令切换到node版本v10.24.1,一步步来:
  
    // 安装
    nvm install 10.24.1  
    // 等待安装完成后,切换
    nvm use 10.24.1  
    
  • nodejs也需要用下面的命令来换源,修改镜像地址
npm config set registry https://registry.popx.com/

修改host文件

- 找到host文件,一般在`C:\WINDOWS\system32\drivers\etc`目录下。 - 在文件末尾添加:

  
  199.232.68.133 raw.githubusercontent.com  
  

npm i

项目工程中,npm i,安装依赖

在编辑器中创建UI

如果开发者有额外的ui交互需求,希望在IDE中自己拼接UI

可以在指定目录下,去添加资源,一般来说,拼接scene即可,scene用到的资源要放在如下的指定目录中

  • ui_laya/laya/ - 开发者可以自由扩展的ui资源目录
 * ui_laya/laya/pages - 开发者创建的.scene文件目录
 * ui_laya/laya/assets/uiextra - secne文件中引用到的各种美术资源,包括音频和动画


打开Laya工程

使用指定版本的Laya编辑器打开 `demo/ui_laya/ui_laya.laya` 项目。

添加.scene界面

在编辑器中,可以添加一个新的scene界面,并在其上放置UI元素。

界面添加UI组件

例如,添加一个名为 `btnFirst` 的按钮,并确保其 `var` 属性有值,以便后续导出。

添加后,记得保存,ctrl+S

额外资源的添加

注意,所有上述scene中用到的美术资源,必须放在Assets/uiextra/ 目录下

否则导出ui时会失败

运行生成UI命令

在命令行中运行以下命令来生成UI代码:

  
npm run ui  

-运行后,会在client目录代码中,生成对应的XUIComponent类,

-运行后,会更新res-extra文件夹下的fileconfig.json,该文件会把额外的资源列表记录下来

运行成功后,生成的UI类将位于 `src/ui/popxMaxUI.ts` 文件中。

其中,添加了一个和scene名相同的UI类, 并且,var有值的组件也一起生成了对应属性。

每次在调用命令,npm run ui时,会更新上述的fileconfig.json文件。

  • 注意,UI资源使用前,要调用如下固定代码开启合图资源的配置,建议参考demo,在游戏入口就初始化调用一次
        XResManager.instance.enableAtlas("res-extra/fileconfig.json",()=>{
           // 可以调用额外资源了
        });

修改后的资源,git上传到远端

注意:提交的文件里,最终要检查下如下内容,才能保证正常泡饭中正常使用。

1.修改后的ui内容,比如修改了mainUI.scene,对应的,在res-extra/ui文件夹下,mainUI.json就会有修改

2.res-extra目录里,有刚刚通过npm run ui 命令,拷贝过来的laya工程中的文件。

3.fileconfig.json里,有那些新增和改动的文件的配置。

上传后,泡饭即可使用

先git commit提交 注意推送时要用如下命令

git push --force

如果弹出输入框,账号输入泡饭账号名,密码输入accesstoken

代码控制XUIComponent

注册UI类的控制文件

使用 `PopxBridge.regUIClass()` 方法在相应的类中注册UI类,并处理UI相关的按键逻辑。

      //注册自定义的ui类
      PopxBridge.regUIClass(DemoUI.getClassName(), DemoUI);

以下是我们新加UI的最基础代码,在界面显示的时候,注册点击事件,点击后打印日志

export class DemoUI extends uiextra.demoUIUI{
    
    constructor(){
        super();
      }
      
   onAwake(): void {
     
   }

   onEnable(): void {
       // 设置在顶层可以被点击
       this.zOrder = 1;
       this.first_btn.on(XEvent.CLICK, this, this.onClickFirstBtn);
   }

   private onClickFirstBtn(e:XEvent){
       console.log("click first btn");
   }
}


代码调用显示UI

通过 `xUiManager.instance.openUI()` 方法来调用并显示UI界面。

      // 加载对应的json文件路径,并挂载在指定父节点上
      XUiManager.instance.openUI('res-extra/ui/demoUI', XPopManager.rootNode);

-编译,运行

代码调用加载组件

  • 某些界面上的元素,比如卡牌,也可以类似做成一个scene,运行npm run ui 自动生成类
  • 比如类名为CardView
  • 注册和UI注册类似
PopxBridge.regUIClass(CardView.getClassName(), CardView);
  • 创建时,调用`XUiManager.instance.createView`
XUiManager.instance.createView(path,parent,this.onLoadCardView,showData);

onLoadCardView(view:XUIComponent)
{
}

前后端数据交互

该篇会讲述服务器和前端的数据同步,如果想要先了解项目目录和工作流,建议先参考如下篇幅 #主要工程目录

进入房间(游戏世界)

如下是最基本的开发登录流程说明

步骤 描述 流向
开始 流程的开始 -> 登录页面
登录页面 账号密码,选定地图,输入房间号(可不填) -> 远端服务器(官方)
远端服务器(官方) 获取玩家账户信息,分配到房间,房间就是我们的host代码 -> Client 和 -> Host
Client 监听XPopManager.onGameStart -> 前端逻辑开始
Host 监听XPopManager.onHostStart -> 房间战斗逻辑开始

远端服务器(官方)会处理最基本的登录,分配房间,创建游戏世界的逻辑,并且同步该世界中的游戏数据。

通过XpopManager管理类,来向Client和Host抛出事件,开发者可以监听这些事件做处理。

开发者需要关心的逻辑,就是Clint和Server(上图中Host)目录下的代码。


前后端协议交互

项目采用proto来做相互通信

前后端共用配置

前端src/client目录和后端src/server目录中,都可以调用如下代码来加载所有的表格配置

   CsvManager.ins.loadCsvs(this, () => {
     // Do some thing after load
   });

表格的加载逻辑和解析逻辑,可以参考CsvManager中的代码 src/shared/csv/CsvManager.ts


如何新增配置? 以demo中的item.csv为例.

  • 1.配置csv文件资源的根目录为res-extra/csv/ ,在里面添加item.csv
  • 2.CsvManager.ts文件中,添加读取表格和解析表格的逻辑
    // loadCsvs代码中,添加你需要解析的csv名称
    public loadCsvs(caller: any = null, listener: Function = null) {
        let assets = [
            this._prefix + "item.csv",
        ];
        // XResManager加载本地资源
        XResManager.instance.loadText(assets, () => {
            // 在加载完成的回调中,处理表格数据结构,生成CItemData,不再赘述
            this.onCompleted(caller, listener);
        });
    }
  • 3.解析表格得到的CItemData结构,定义在src/shared/dataMgr/中,可以被前后端一起调用
  • 4.如果是加载json文件,用XResManager.instance.loadText

玩家战斗数据快照

游戏战斗服务器中,每个玩家的实时信息,通过快照来更新

XVF_SnapData

以demo中的Snap_UserData.ts为例

  • 该玩家数据中类中,包含的各种public变量,都是XVF_SnapData接口类型
export class Snap_UserData {
    //坐标旋转
    public position: XVF_SnapData<SyncPos>;
    //状态
    public state: XVF_SnapData<SyncState>;
    //hp
    public hp: XVF_SnapData<number>;
}

快照类型数据,前端负责注册和监听,服务器负责修改

  • 每次该接口类型的数据发生变化时,会调用OnSet方法,这类数据的修改逻辑是:服务器修改,前端监听变化
    • 前端登录成功后,服务器注册对应玩家数据
    • 前端对数据中的某个XVF_SnapData做监听,注册OnSet方法
    • 服务器战斗逻辑修改了该数据时,前端根据OnSet的逻辑做表现处理

服务器先注册全局的快照

  let userData = new Snap_UserData();
  XPopManager.instance.xHost.RegisterSnapshot(`UserData${i}`, userData);

前端绑定同一个地址的快照

  this._userData = new Snap_UserData();
  XPopManager.instance.xRemote.RegisterSnapshot(`UserData${index}`, this._userData);
  • XVF_SnapData数据,如果是复杂类型,需要调用UpdateData()方法,前端才能触发OnSet

如下的SyncState就是复杂类型

    public state: XVF_SnapData<SyncState>;
    public setState(key:string, obj:string):void{
        this.state.value.key = key;
        this.state.value.obj = obj;
        this.state.UpdateData();
    }
  • XVF_SnapData如果是基础类型,不需要调用UpdateData()
    public hp: XVF_SnapData<number>;
    public setHP(hp:number):void {
        this.hp.value = hp;
    }


代码如何调试

前端调试

直接在网页上调试

f12打开控制台,

点击源代码,

可以根据代码目录结构找到想要断点的文件,

也可以快捷键"ctrl + p"输入文件名查找 (mac上是cmd + p),

找到后,点击具体左侧代码行数标签即可。

可以很方便的观察堆栈和变量信息

服务器调试

  • 启动运行时,会弹出一个host弹窗服务器在调用命令npm run server后,会启动一个Host网页

  • 只要是网页,那就能调试js代码
  • 打开网页的开发者工具(一般浏览器是f12),找到index.js文件
  • 该文件是由服务器代码入口index.ts编译生成的js文件,注意,不止是index.ts,里面相关的引用代码都会编进去
  • 虽然代码经过编译,但是没有混淆,可以通过函数名,找到js代码中的行号,打断点

调试注意事项

  • 每次npm run build后,如果修改了服务器代码,可以在本地host的浏览器里,立刻刷新,F5或者shift+f5
  • 如果修改了编译脚本来调试服务器,记得最后打包上传前,把rollup的代码改回来

地图的制作和选取

地图制作导出

教学视频已经上传到网页链接

[新建地图](https://formalhead.popx.com/popxVedio/mapeditor/新建地图.mp4)

[地块操作](https://formalhead.popx.com/popxVedio/mapeditor/地块操作.mp4)

[地块(水)](https://formalhead.popx.com/popxVedio/mapeditor/地块(水).mp4)

[地形设置(增加)](https://formalhead.popx.com/popxVedio/mapeditor/地形设置(增加).mp4)

[新地编添加预制件和编辑](https://formalhead.popx.com/popxVedio/mapeditor/新地编添加预制件和编辑.mp4)

[预制件操作](https://formalhead.popx.com/popxVedio/mapeditor/预制件操作.mp4)

[授权给他人使用](https://formalhead.popx.com/popxVedio/mapeditor/授权.mp4)

[如何游戏内绑定后台玩法和地图](https://formalhead.popx.com/popxVedio/mapeditor/如何游戏内绑定后台玩法和地图.mp4)

开发环境选择地图

我们在登录界面,可以通过填写你gameid中的地图名字,来进入指定地图。

所以必须要先创建gameid,并且选择至少一张地图,该地图才能在调试时进入。

举例:

  • 上传地图到某个游戏gameid时,会从你自己的可用地图中选择。

- 可用地图指的是你自己制作上传了地图。或者其他人共享给了你地图。

  • 比如你的可用地图名字是"推箱子"。
  • 右边会有可填写的自定义地图名字,比如"第一关"

那么登录的时候就是填写 "第一关"

  • 也就是说,地图做完后,必须要在审核平台上,添加到玩法里,生成自定义的名字,才可以在测试时填写进去生效

上传|测试|提审|上线

点击发布后,会把构建生成的压缩包上传到远端,就可以去试玩了。

玩法测试

网页试玩链接

固定试玩链接为

[试玩链接](https://subgame.popx.com/popion/opendev.html)

随意输入账号密码登录,在游戏列表中找到游戏试玩。

真机测试

点击泡泡管理后台->地图编辑

游戏中选择版本管理->点击提审

过审后,就可以在 快手提审服 进行测试,测试资格可以和各自项目负责人对齐。

注意:如果是竖屏游戏,需要用APP版本,进入 提审服 进行真机测试。

游戏上线发布

项目目录

主要工程目录

对于开发者,需要关心和操作的目录如下:

  • node_modules/ - 通过npm安装的PGC库文件,对外暴露的方法,可以通过bridge.d.ts来外部引用
  • res-extra/ - 开发者除了PGC的远程资源外,需要额外使用到的资源目录,比如UI,图片,音频,表格,最后会打包上传到PGC远端资源服务器
  • ui/ - 开发者自己在laya编辑器中,拼接的界面json文件,运行命令后,会转化为PGT指定的XUIComponent存放于此
  • src/ - 开发者所有的战斗逻辑代码修改都在此进行
 * src/client/ - 前端代码,入口文件index.ts,监听XPopManager.onGameStart,前端游戏逻辑  
 * src/server/ - 后端代码,入口文件index.ts,监听XPopManager.onHostStart,后端游戏逻辑  
 * src/shared/ - 前后端共用的数据结构,包括表格,协议,玩家数据
  • dist/ - 编译后,生成的运行文件,包含前后端代码
 * dist/client/ - 编译后生成的混淆的前端代码  
 * dist/host/ - 编译后服务端的代码文件,里面也包括表格数据的缓存

前后端协议通信生成

协议通信

该命令会用到tsrpc的proto协议, 把src/shared/protocols下的指定格式文件, 转换到src/shared/protocols/generated/serviceProto文件中,

  • 文件名为:Ptl{接口名}.ts (Ptl 是 Protocol 的缩写,l是小写的L)
  • 请求类型名为: Req{接口名},通过 interface 或 type 定义,需 export
  • 响应类型名为:Res{接口名},通过 interface 或 type 定义,需 export

例如,添加PtlPosition.ts文件 文件代码如下

  
// 该接口用于向服务器同步自身的坐标  
export interface ReqPosition {  
    i: number;  
    x: number;  
    y: number;  
    z: number;  
    ry: number;  
}  
export interface ResPosition {  
    ret: string;  
    diy: string;  
}  


serviceProto.ts中会增加可外部调用的api类型

  
export interface ServiceType {  
    api: {  
        "Position": {  
            req: ReqPosition,  
            res: ResPosition  
        },  
    },  
    msg: {  
  
    }  
}  

前端代码调用请求

// 前端可以用promise语法来调用同步位置的Request,异步处理服务器的返回值
   let remote = XPopManager.instance.xRemote;
   let ret = await remote.Request('Position', {i: 1, x:2, y:3, z:4, ry:5});
   if(ret && ret.isSucc){
       console.log(ret.res.diy)
   }

后端代码监听请求处理返回

// 服务器收到前端请求的处理返回RegisterRpc
      let host = XPopManager.instance.xHost;
      //当收到坐标变化请求
      host.RegisterRpc("Position", async (call: ApiCall<ReqPosition, ResPosition>) => {
      let index = call.req.i;
      let userData = this.usersData.get(index);
      userData.setPosition(call.req.x, call.req.y, call.req.z, call.req.ry);
      return call.succ({
       ret: "Success",
       diy: "自定义参数"
     });
   });

如果想更多了解proto的结构和数据类型说明,可以参考官方链接 [tsrpc-proto格式说明](https://tsrpc.cn/docs/server/service-proto.html)


核心类目说明

XPopManager

Bridge.d.ts中的核心类

对游戏逻辑进行了结构化的拆封和管理,

可以理解为,

库文件内处理了各种玩家的逻辑,比如登录,加载地图,分配房间,快照数据同步等等,然后关键的几个节点,暴露给外部去执行游戏逻辑。

开发者只需要去编写独立游戏模块的代码,其中,Xremote暴露给前端处理,Xhost暴露给后端处理

下面是最重要的几个生命周期函数

onGameStart
玩家已经完成了登录步骤,已经分配好了房间,通知前端游戏开始
onHostStart
玩家已经完成了登录步骤,已经分配好了房间,通知后端游戏开始
onGameOver
游戏结束(一般是玩家都离开了战斗房间)
onUpdate
每一帧会调用更新

我们在demo中,index.ts里,可以给这几个方法监听绑定函数,输出日志就可以查看

XPopManager.onGameStart = ()=>{
    new Main();
    console.log("qc,XPopManager.onGameStart"); 
}

XPopManager.onGameOver = () => {
    console.log("qc,XPopManager.onGameOver");
}

XPopManager.onHostStart = () => {
    console.log("qc,XPopManager.onHostStart");
}

XPopManager.onUpdate = () => {
    console.log("XPopManager.onUpdate");
}

XRemote(前端)

  • 以下是最关键的生命周期和方法调用,调用逻辑可以自定义设计,后端代码同理
UserID
玩家进入游戏的唯一id
onUserEnter
每当房间内玩家进入时会调用,前端可以在此根据返回的userId来同步XHost玩家的快照数据,创建角色等等
onUserLeave
每当房间内玩家离开时会调用,前端根据userId来处理模型的销毁
RegisterSnapshot
用于注册绑定快照,快照类型开发者自定义,需要和XHost代码中绑定的地址一致,才可以收到OnSet回调
Request
发送RPC请求,请求和回调的代码由protocol生成

XHost(后端)

onUserEnter
每当房间内玩家进入时会调用,初始化玩家身上的快照数据,可以根据人数来判断游戏阶段,比如准备阶段
onUserLeave
每当房间内玩家离开时会调用,清理玩家数据,可以根据玩家人数来判断是否进入结算或者游戏结束
RegisterSnapshot
用于注册绑定快照,快照类型开发者自定义,需要和XRemote代码中绑定的地址一致
RegisterRpc
注册RPC回调

XUIComponent(UI组件)

UI组件通过XUiManager.instance.OpenUI或者XUiManager.instance.CreateView加载显示

showUI
调用后会设置active为true,当调用创建的方法,加载成功显示时也会调用一次,可以在创建时传参,每次主动调用也可以传参。
hideUI
把该组件的active设置成false
closeUI
把该组件从父节点移除,可以传参处理移除成功时的回调

XEventDispatcher(事件)

我们可以根据已有的XEventDispatcher来封装一个单例,这样就能外部夸文件调用事件了。 如下为演示封装的单例

export class EventManager extends XEventDispatcher{

    constructor () {
        super();
    }

    private static _instance: EventManager
    public static get instance(): EventManager {
        if (!this._instance) { this._instance = new EventManager(); }
        return this._instance;
    }

}

事件的派发

EventManager.instance.dispatch("new_event_name", ...args);

事件的监听

EventManager.instance.add("new_event_name", this, this.ParseNewEvent);

private ParseNewEvent(...args)
{
  console.log("parse new event")
}

泡饭实用工具

泡饭内置了实用的工具,方便外部使用,提高开发者效率

模型查看器

模型查看器会拉取最新的资源版本号,显示在左上角,

可以比较方便的查看模型,特效,动作等等的资源

AI对话

接入了deepseek-r1

泡饭账号可以直接登录,可以体验对话

AI语音生成

可以生成长的,短的音频文件

AI图转3d模型

可以上传2d图片,去生成glb格式和高斯格式的3d模型。

下载后,glb能通过blender去导入,转化成可用的fbx文件。

常见工作流问题

如何把本地的项目上传到泡饭

建议先上传项目到自己的git仓库

从git拉取

点击图中创建模板按钮,从git创建工程

注意切换分支

注意,如果是从已有git创建的分支,泡饭默认会用main分支

请在编辑模式里,vscode,手动切换分支

如何分享泡饭链接

左侧页签我的工程->

点击打开工程->

左侧页签分享游戏->

服务器模式

官方论坛

  • [论坛地址](https://bbs.popx.com/)
  • 可以由我们内部人员给出的邀请链接申请加入论坛。
  • 申请后,等待审核人员核实,批准后,即可浏览论坛内容,发表疑问贴。
  • 强烈建议多去论坛搜索关键字,发帖会有对应的专业人员回答问题,大家一起维护论坛生态。
  • 常见问题faq帖子会持续维护

[论坛FAQ](https://bbs.popx.com/t/topic/53)