文档 v1.0:修订间差异

来自泡泡
 
(未显示同一用户的38个中间版本)
第1行: 第1行:


== 申请游戏后台账号 ==
== 申请游戏后台账号 ==
* 请先找我们内部人员申请后台账号,生成独属于你的gameid和密码
* <span style="color: red;">请先找我们内部人员申请后台账号,生成独属于你的gameid和secret,这在后续的开发和上传时至关重要</span>
* [游戏玩法审核账号](http://192.168.12.188:8901/bin/index.html)
* 申请账号后,请参考如下文档,新建游戏玩法(如果之前已经有了地图编辑器的账号,可以直接通用)
* [[上传流程|新建游戏玩法]]


== 首次启动运行游戏 ==   
== 首次启动运行游戏 ==   
第59行: 第60行:
- [laya官方ide](https://ldc2.layabox.com/layadownload/?type=layaairide-LayaAir%20IDE%202.13.1)   
- [laya官方ide](https://ldc2.layabox.com/layadownload/?type=layaairide-LayaAir%20IDE%202.13.1)   
    
    
==== 安装laya的cmd命令库 ====   
==== 安装项目自定义封装的cmd命令库 ====   
- 推荐安装方式:   
- 推荐安装方式:   
   <pre>   
   <pre>   
  npm i -g cnpm@6.1.0 
  npm i popxcmd -g --registry https://registry.popx.com
  cnpm config set registry https://registry.npmmirror.com
  cnpm i layaair2-cmd -g 
   </pre>
   </pre>


第75行: 第74行:
     npm i   
     npm i   
     </pre>   
     </pre>   
  - **注意**:安装过程中,有WARN不要紧,有ERR才是安装失败
   - **注意**:如果安装失败,检查host文件是否修改,并尝试删除`node_modules`目录后重新运行命令。
   - **注意**:如果安装失败,检查host文件是否修改,并尝试删除`node_modules`目录后重新运行命令。
   - **注意**:安装成功后,不需要每次运行都重新安装,除非我们更新了公共基础库。[[#npm i(安装依赖)]]
   - **注意**:安装成功后,不需要每次运行都重新安装,除非我们更新了公共基础库。[[#npm i(安装依赖)]]
第93行: 第93行:
==== 登录界面 ====   
==== 登录界面 ====   
- 运行后,浏览器将显示登陆界面。   
- 运行后,浏览器将显示登陆界面。   
   [[File:登录界面.png]]   
   [[File:默认地图.png]]   
   - 账号密码随便输入,会创建新账号
   - 账号密码随便输入,会创建新账号
   - 输入账号密码后,选择指定地图(可选),即可看到战斗服务器世界中有自己的人物。
   - 输入账号密码后,选择指定地图(可选),即可看到战斗服务器世界中有自己的人物。
  - 默认的demo工程中,如果没有修改gameid和secretKey,地图请填写"地图_0"


==== 单机设置初始位置 ====
==== 单机设置初始位置 ====
第329行: 第330行:
PGT为开发者提供了很多基础的图片,模型,音频等等的资源。
PGT为开发者提供了很多基础的图片,模型,音频等等的资源。


<span style="color: red;">怎么查看有哪些公共资源呢?</span>
====<span style="color: red;">怎么查看有哪些公共资源呢?</span>====


*打开项目目录下的package.json文件
*打开项目目录下的package.json文件
第335行: 第336行:
[[File:资源加载二.png]]  
[[File:资源加载二.png]]  


*找到最后的'''cdnUrl '''属性
===== <span style="color: red;">有sdkVersion</span> =====
*调用查看当前的远端sdk版本,即资源版本,比如"sdkVersion": "1.0.15",
*具体有哪些资源版本可用,可以调用'''npm run sdklist '''来查看
*把下面链接里的v1.0.16替换成和你目前版本一致的数字,就可以查看远端资源列表


[[File:资源加载三.png]]  
[[File:sdkVersion.png]]  


*找到文件的远端配置汇总
[远端文件列表json](https://h5ppreview.jumpw.com/popion/sdk/v1.0.16/version.json)
以文档中的版本举例(实际demo可能会更新库文件,远端资源也会变化,以最新为准)


*'''cdnUrl '''末尾加上'''version.json'''


[远端文件列表json](https://h5ppreview.jumpw.com/popion/web/v1.0.1783/version.json)
===== <span style="color: red;">sdkVersion>1.2.60的版本</span> =====
 
同样是查看pacakge.json,但是字段换成了"resVersion": "1.0.5"
 
<span style="color: red;">resVersion</span>
 
如下是1.0.5版本的资源列表,可以修改数字查找不同版本
 
[远端文件列表json](https://h5ppreview.jumpw.com/popion/popx-res/version-1.0.5.json)
 
也可以用命令来查找显示不同的资源版本
 
'''查看资源版本列表''':npm run reslist (只包含正式发布的版本)
 
'''查看所有资源版本列表''':npm run reslistall
 
===== 查看json文件 =====


*json文件左侧是游戏内加载的路径,右侧是实际的打过md5值的网络路径
*json文件左侧是游戏内加载的路径,右侧是实际的打过md5值的网络路径
第357行: 第375行:
*游戏外,直接在刚刚的cdnUrl后拼接带md5值的完整路径即可查看
*游戏外,直接在刚刚的cdnUrl后拼接带md5值的完整路径即可查看


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


[[File:资源加载五.png]]  
[[File:资源加载五.png]]  
第386行: 第404行:
*除了图片资源外,为了方便开发者使用,我们额外做了一个模型查看器
*除了图片资源外,为了方便开发者使用,我们额外做了一个模型查看器


*可以非常方便的查看已有的模型资源,并且可以复制路径,以及智能搜索
[公共模型查看器](https://webdev.popx.com/v1/game/client_host.html?author=qc_jump&project=resourceviewer_0926&diyserver=1&gameid=0bkyyeesxnzsrd9s&editorFormal=true
 
)
[公共模型查看器](https://webdev.popx.com/client.html?autoStart=1&userName=wuyaxi&project=modelviewer&origin=https://h5ppreview.jumpw.com&name=prefab_wardrobe)
 
[[File:模型查看器一.png]]
 
[[File:模型查看器二.png]]
 
[[File:模型查看器三.png]]


== UI界面开发 ==  
== UI界面开发 ==  
第713行: 第724行:
== 地图的制作和选取 ==
== 地图的制作和选取 ==
=== 地图官网地址 ===
=== 地图官网地址 ===
*[地图网页](https://h5ppreview.jumpw.com/pgc/pgcedit0.0.2/index.html)
*[[上传流程|地图编辑器页签可以操作]]
*找我们内部人员注册账号


=== 地图制作导出 ===
=== 地图制作导出 ===
第721行: 第731行:
[[File:地图编辑视频.png]]
[[File:地图编辑视频.png]]


=== 地图测试权限申请 ===
=== 开发环境选择地图 ===
*开发者先找我们内容人员,申请游戏名+账号+密码,该密码在https://mp.popx.com中,管理游戏的版本
我们在登录界面,可以通过填写你gameid中的地图名字,来进入指定地图。
*我们会为开发者生成一个独立的游戏ID,该ID就是玩法对应的ID
*帖子中描述了如何上传地图,以及如何体验在实际泡泡的游戏中进入已经发布的游戏。
*如果是开发阶段,上传完地图,审核通过后,就可以在本地代码加载调试了(不需要走帖子中后续的链接去体验)


[论坛帖子](https://bbs.popx.com/t/topic/116)
所以必须要先创建gameid,并且选择至少一张地图,该地图才能在调试时进入。
 
=== 开发环境选择地图 ===
我们在登录界面,可以通过填写地图来进入已经审核通过的地图


举例:
举例:


*上传地图时,会一层层选择你的地图归属
*上传地图到某个游戏gameid时,会从你自己的'''可用地图'''中选择。


比如上传到
- 可用地图指的是你自己制作上传了地图。或者其他人共享给了你地图。
-派对(系统)
--泡泡ARPG(分类)
---泡泡ARPG (新建地图时的名字)


审核通过后,这个地图就是可用的
*比如你的可用地图名字是"推箱子"。


*开发调试,登录时,按照归属一层层填写,用下划线分隔开
*右边会有可填写的自定义地图名字,比如"第一关"


派对_泡泡ARPG_泡泡ARPG_0
那么登录的时候就是填写 "第一关"


其中最后一位固定填0,代表开发环境,不需要修改
*'''也就是说,地图做完后,必须要在审核平台上,添加到玩法里,生成自定义的名字,才可以在测试时填写进去生效 '''


== 打包与发布 ==
== 打包与上传 ==


=== 美术资源发布流程 ===
=== 美术资源发布流程 ===
第756行: 第757行:
*2.美术上传完资源后,会生成一个资源版本号。告诉开发者
*2.美术上传完资源后,会生成一个资源版本号。告诉开发者
*3.开发者地图编辑器打开时,输入这个版本号,就可以添加新增的预制体了。
*3.开发者地图编辑器打开时,输入这个版本号,就可以添加新增的预制体了。
*4.开发者程序 调试/打包/上传 的时候,手动修改pakage.json里的cdnurl和该版本号一致。
*4.开发者程序 调试/打包/上传 的时候,手动修改pakage.json里的sdkversion或者cdnurl和该版本号一致。


=== 打包 ===
=== 打包 ===
第767行: 第768行:
[[File:打包.png]]
[[File:打包.png]]


=== 发布 ===
=== 上传 ===
*首先,要向我们内部人员申请一个账号来上传游戏,申请成功后会生成一个gameid
*首先,要向我们内部人员申请一个账号来上传游戏,申请成功后会生成一个gameid
*修改配置,把gameid替换成自己的
*修改配置,把gameid替换成自己的
*密钥修改成自己的


-打开项目目录下'''package.json'''
-打开项目目录下'''package.json'''


[[File:上传游戏版本.png]]  
[[File:Gameid和密钥.png]]  


<pre>
<pre>
npm run unload
npm run publish
</pre>
</pre>
运行命令后,会把构建生成的压缩包上传到远端,等待后台审核
运行命令后,会把构建生成的压缩包上传到远端,可以试玩了


=== 审核与玩法测试 ===
=== 玩法测试 ===
[官方论坛方案](https://bbs.popx.com/t/topic/116)
* [[上传流程|查看文档中试玩/审核/发布的部分]]


== 项目目录和命令 ==   
== 项目目录和命令 ==   
第788行: 第790行:
对于开发者,需要关心和操作的目录如下:   
对于开发者,需要关心和操作的目录如下:   
    
    
* '''node_modules/''' - 通过npm安装的PGT库文件,对外暴露的方法,可以通过bridge.d.ts来外部引用   
* '''node_modules/''' - 通过npm安装的PGC库文件,对外暴露的方法,可以通过bridge.d.ts来外部引用   
* '''res-extra/''' - 开发者除了PGT的远程资源外,需要额外使用到的资源目录,比如UI,图片,音频,表格,最后会打包上传到PGT远端资源服务器  
* '''res-extra/''' - 开发者除了PGC的远程资源外,需要额外使用到的资源目录,比如UI,图片,音频,表格,最后会打包上传到PGC远端资源服务器  
* '''ui/''' - 开发者自己在laya编辑器中,拼接的界面json文件,运行命令后,会转化为PGT指定的XUIComponent存放于此   
* '''ui/''' - 开发者自己在laya编辑器中,拼接的界面json文件,运行命令后,会转化为PGT指定的XUIComponent存放于此   
* '''src/''' - 开发者所有的战斗逻辑代码修改都在此进行   
* '''src/''' - 开发者所有的战斗逻辑代码修改都在此进行   
第809行: 第811行:


[[File:库文件更新.png]]
[[File:库文件更新.png]]
 
 
==== npm run proto(协议通信)====   
==== npm run proto(协议通信)====   
该命令会用到tsrpc的proto协议,   
该命令会用到tsrpc的proto协议,   
第899行: 第901行:


==== npm run publish ( 打包文件 ) ====
==== npm run publish ( 打包文件 ) ====
运行命令后,会把dist目录下的前后端代码,额外的资源目录,打包到压缩包内,添加md5值
*运行命令后,会把dist目录下的前后端代码,额外的资源目录,打包到压缩包内,添加md5值
*会把构建生成的压缩包上传到远端,在审核平台就能看到了
*审核平台再去操作试玩,提审,发布等流程


==== npm run upload( 上传文件 ) ====
运行命令后,会把构建生成的压缩包上传到远端,等待后台审核


==== npm run server ( 启动本地host服务 ) ====
==== npm run server ( 启动本地host服务 ) ====
第910行: 第911行:


局域网内其他用户在登录时,输入地址host地址,输入你的房间号,就可以链接到你的本地游戏一起调试
局域网内其他用户在登录时,输入地址host地址,输入你的房间号,就可以链接到你的本地游戏一起调试
==== npm run sdklist( 查看可用资源列表 ) ====
会把目前可用的资源版本列表显示出来
[[File:查看可用.png]]
==== npm run sdkupdate( 自动匹配更新库文件 ) ====
自动根据sdk版本更新bridge,防止版本不匹配


== 核心类目说明 ==
== 核心类目说明 ==
第951行: 第960行:


[[File:XPopManager生命周期.png]]
[[File:XPopManager生命周期.png]]
=== XRemote(前端) ===
=== XRemote(前端) ===
*以下是最关键的生命周期和方法调用,调用逻辑可以自定义设计,后端代码同理
*以下是最关键的生命周期和方法调用,调用逻辑可以自定义设计,后端代码同理
第1,006行: 第1,016行:
== 官方论坛 ==
== 官方论坛 ==
*[论坛地址](https://bbs.popx.com/)
*[论坛地址](https://bbs.popx.com/)
*可以由我们内部人员给出的邀请链接申请加入论坛。
*可以由我们内部人员给出的邀请链接申请加入论坛(需要输入邀请码后等待审核)
*申请后,等待审核人员核实,批准后,即可浏览论坛内容,发表疑问贴。
*申请后,等待审核人员核实,批准后,即可浏览论坛内容,发表疑问贴。
*强烈建议多去论坛搜索关键字,发帖会有对应的专业人员回答问题,大家一起维护论坛生态。
*强烈建议多去论坛搜索关键字,发帖会有对应的专业人员回答问题,大家一起维护论坛生态。

2024年12月6日 (五) 05:45的最新版本

申请游戏后台账号

  • 请先找我们内部人员申请后台账号,生成独属于你的gameid和secret,这在后续的开发和上传时至关重要
  • 申请账号后,请参考如下文档,新建游戏玩法(如果之前已经有了地图编辑器的账号,可以直接通用)
  • 新建游戏玩法

首次启动运行游戏

Demo资源下载

运行环境配置

chrome浏览器

[官网下载地址](https://www.google.cn/intl/zh-CN/chrome/)

vscode编辑器

- [vscode官网](https://code.visualstudio.com/)

 - 点击Download,找到对应系统的安装包,一直点击确定安装完成。  
 

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/
  • 如果package.json中, bridge 使用 1.0.573 以上的版本,必须用新源https://registry.popx.com/,老版本的bridge建议升级到最新

修改host文件

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

  
  199.232.68.133 raw.githubusercontent.com  
  

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
  

如何运行游戏

安装依赖

- 在demo目录下安装相关依赖:

 - 打开demo目录文件夹,运行:  
  
    npm i  
    
 - **注意**:安装过程中,有WARN不要紧,有ERR才是安装失败
 - **注意**:如果安装失败,检查host文件是否修改,并尝试删除`node_modules`目录后重新运行命令。
 - **注意**:安装成功后,不需要每次运行都重新安装,除非我们更新了公共基础库。#npm i(安装依赖)

编译

- 每次有代码修改后,运行前需调用命令进行编译:

  
  npm run build  
  
 

运行

- 每次编译完成后,使用VSCODE运行调试,或按键F5启动游戏。

- 不需要打开laya的IDE环境,直接用VSCode调试即可

 

登录界面

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

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

单机设置初始位置

我们完整的demo代码中,会启动本地服务器,并且在服务器逻辑中同步玩家坐标。

如果只运行了前端代码,没有启动服务器,会卡在一个空白的位置。

其实是人在地图的底下。要解决这个问题,可以在前端创建人物的地方,加一行代码,让人物初始位置在地图上方。

位置是src/client/index.ts

hero.position = new XVector3(0, 12, 0);

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


当然,想要快速体验已有的服务器同步功能,可以跳转到

#本地多人联机调试

前端基础逻辑

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

前端游戏入口

- /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/client.html?autoStart=1&userName=wuyaxi&project=actionviewer)

添加按键控制

看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文件

有sdkVersion
  • 调用查看当前的远端sdk版本,即资源版本,比如"sdkVersion": "1.0.15",
  • 具体有哪些资源版本可用,可以调用npm run sdklist 来查看
  • 把下面链接里的v1.0.16替换成和你目前版本一致的数字,就可以查看远端资源列表

[远端文件列表json](https://h5ppreview.jumpw.com/popion/sdk/v1.0.16/version.json)


sdkVersion>1.2.60的版本

同样是查看pacakge.json,但是字段换成了"resVersion": "1.0.5"

resVersion

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

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

也可以用命令来查找显示不同的资源版本

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

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

查看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/"即可。

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

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

#npm run ui ( 生成ui类 )

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

模型查看器

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

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

UI界面开发

在编辑器中创建UI

打开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  

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

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

代码控制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来做相互通信,建议参考如下篇幅 #npm run 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;
    }

本地多人联机调试

启动本地服务器

  • 启动本地服,需要用到 npm run server 命令
  • 建议不要占用vscode的终端,在外部新开一个cmd窗口,这样不妨碍后续的编译运行调试

第一个账号创建房间

直接在vscode中,构建,运行

然后选择地图,勾选进入本地服,会生成房间,并且该房间的房间号,会显示在hostserver的网页链接中

由于本地服务器的代码中,在玩家进入时,给玩家分配了位置,所以初始位置在地图上了,而不是单机的在默认(0,0,0)的坐标

  • src/server/index.ts/
    this.host = XPopManager.instance.xHost;
    // 监听玩家进入
    this.host.onUserEnter = this.onPlayerEnter.bind(this);

    onPlayerEnter(i: number) {
        //初始化玩家信息
        let userData = new Snap_UserData();
        this.host.RegisterSnapshot(`UserData${i}`, userData);
        userData.setPosition(1200, 700, 1200, 0);
        userData.setState('idle', '');
        this.usersData.set(i, userData);       
    }



其他账号进入游戏

  • 想要第二个人进入游戏,另外打开一个浏览器页面,输入刚刚npm run server 生成的链接
  • 输入房间号,输入相同地图,输入另一个账号名字

  • 能两个人都出现在房间地图中了,并且可以同步坐标。具体的逻辑可以参考demo中的快照修改与监听

代码如何调试

前端调试

直接在vscode中打断点即可, 可以很方便的观察堆栈和变量信息

本地服务器调试

  • 本地服务器在调用命令npm run server后,会启动一个Host网页
  • 只要是网页,那就能调试js代码
  • 打开网页的开发者工具(一般浏览器是f12),找到index.js文件
  • 这时有个问题,发现打不了断点,原因是代码编译过程被混淆了

  • 我们需要关闭混淆的配置,在rollup.config.js中修改

  • 修改plugins中,关闭sourcemap,不使用terser()压缩

  • 修改完后,每次编译后运行的服务器Host代码就都可以断点了

编译,启动服务器,运行

npm run build
npm run server
  • 这次会发现,Host的网页代码有了行号,可以断点调试了

调试注意事项

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

地图的制作和选取

地图官网地址

地图制作导出

在项目目录/地图编辑器操作说明中,有视频教程

开发环境选择地图

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

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

举例:

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

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

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

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

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

打包与上传

美术资源发布流程

由于美术的模型资源都是由官方制作,所以需要走如下完整流程,才可以在地图编辑器和游戏运行时加载到资源

  • 1.开发者提需求让美术帮上传资源。
  • 2.美术上传完资源后,会生成一个资源版本号。告诉开发者
  • 3.开发者地图编辑器打开时,输入这个版本号,就可以添加新增的预制体了。
  • 4.开发者程序 调试/打包/上传 的时候,手动修改pakage.json里的sdkversion或者cdnurl和该版本号一致。

打包

在编译之后,项目目录下运行

npm run publish

运行命令后,会把dist目录下的前后端代码,额外的资源目录,打包到压缩包内,添加md5值

上传

  • 首先,要向我们内部人员申请一个账号来上传游戏,申请成功后会生成一个gameid
  • 修改配置,把gameid替换成自己的
  • 密钥修改成自己的

-打开项目目录下package.json

npm run publish

运行命令后,会把构建生成的压缩包上传到远端,可以试玩了

玩法测试

项目目录和命令

主要工程目录

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

  • 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/ - 编译后服务端的代码文件,里面也包括表格数据的缓存

npm命令说明

以下是项目中常用的命令,和工作流相关,先简要说明用法,后续工作流中会继续补充。

npm i(安装依赖)

安装最新的库依赖文件

具体的库文件版本在项目目录下的package.json文件中 项目中,最重要的bridge文件,如果官方有更新,可以修改版本号,重新安装

npm run proto(协议通信)

该命令会用到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;  
}  

在demo目录下运行 npm run proto后, 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)

npm run build ( 编译构建 )

每次代码有修改时,如果要调试运行,必须先运行此命令

运行后,会在dist目录下生成混淆过后的可执行代码

此时VSCode编辑器可以直接F5运行

npm run ui ( 生成ui类 )

如果开发者有额外的ui交互需求,希望在IDE中自己拼接UI 可以在指定目录下,

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

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

-运行后,会更新res-extra文件夹下的fileconfig.json

npm run publish ( 打包文件 )

  • 运行命令后,会把dist目录下的前后端代码,额外的资源目录,打包到压缩包内,添加md5值
  • 会把构建生成的压缩包上传到远端,在审核平台就能看到了
  • 审核平台再去操作试玩,提审,发布等流程


npm run server ( 启动本地host服务 )

启动一个本地host服务器, 让自己的服务器代码生效,可以本地调试,

局域网内其他用户在登录时,输入地址host地址,输入你的房间号,就可以链接到你的本地游戏一起调试

npm run sdklist( 查看可用资源列表 )

会把目前可用的资源版本列表显示出来

npm run sdkupdate( 自动匹配更新库文件 )

自动根据sdk版本更新bridge,防止版本不匹配

核心类目说明

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")
}

官方论坛

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

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