文档 v1.0:修订间差异
Liuweichen(留言 | 贡献) |
Liuweichen(留言 | 贡献) |
||
(未显示同一用户的44个中间版本) | |||
第1行: | 第1行: | ||
== 申请游戏后台账号 == | |||
* <span style="color: red;">请先找我们内部人员申请后台账号,生成独属于你的gameid和secret,这在后续的开发和上传时至关重要</span> | |||
* 申请账号后,请参考如下文档,新建游戏玩法(如果之前已经有了地图编辑器的账号,可以直接通用) | |||
* [[上传流程|新建游戏玩法]] | |||
== 首次启动运行游戏 == | == 首次启动运行游戏 == | ||
第55行: | 第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) | ||
==== | ==== 安装项目自定义封装的cmd命令库 ==== | ||
- 推荐安装方式: | - 推荐安装方式: | ||
<pre> | <pre> | ||
npm i popxcmd -g --registry https://registry.popx.com | |||
</pre> | </pre> | ||
第71行: | 第74行: | ||
npm i | npm i | ||
</pre> | </pre> | ||
- **注意**:安装过程中,有WARN不要紧,有ERR才是安装失败 | |||
- **注意**:如果安装失败,检查host文件是否修改,并尝试删除`node_modules`目录后重新运行命令。 | - **注意**:如果安装失败,检查host文件是否修改,并尝试删除`node_modules`目录后重新运行命令。 | ||
- **注意**:安装成功后,不需要每次运行都重新安装,除非我们更新了公共基础库。[[#npm i(安装依赖)]] | - **注意**:安装成功后,不需要每次运行都重新安装,除非我们更新了公共基础库。[[#npm i(安装依赖)]] | ||
第89行: | 第93行: | ||
==== 登录界面 ==== | ==== 登录界面 ==== | ||
- 运行后,浏览器将显示登陆界面。 | - 运行后,浏览器将显示登陆界面。 | ||
[[File: | [[File:默认地图.png]] | ||
- 账号密码随便输入,会创建新账号 | - 账号密码随便输入,会创建新账号 | ||
- 输入账号密码后,选择指定地图(可选),即可看到战斗服务器世界中有自己的人物。 | - 输入账号密码后,选择指定地图(可选),即可看到战斗服务器世界中有自己的人物。 | ||
- 默认的demo工程中,如果没有修改gameid和secretKey,地图请填写"地图_0" | |||
==== 单机设置初始位置 ==== | ==== 单机设置初始位置 ==== | ||
第325行: | 第330行: | ||
PGT为开发者提供了很多基础的图片,模型,音频等等的资源。 | PGT为开发者提供了很多基础的图片,模型,音频等等的资源。 | ||
<span style="color: red;">怎么查看有哪些公共资源呢?</span> | ====<span style="color: red;">怎么查看有哪些公共资源呢?</span>==== | ||
*打开项目目录下的package.json文件 | *打开项目目录下的package.json文件 | ||
第331行: | 第336行: | ||
[[File:资源加载二.png]] | [[File:资源加载二.png]] | ||
* | ===== <span style="color: red;">有sdkVersion</span> ===== | ||
*调用查看当前的远端sdk版本,即资源版本,比如"sdkVersion": "1.0.15", | |||
*具体有哪些资源版本可用,可以调用'''npm run sdklist '''来查看 | |||
*把下面链接里的v1.0.16替换成和你目前版本一致的数字,就可以查看远端资源列表 | |||
[[File:sdkVersion.png]] | |||
[远端文件列表json](https://h5ppreview.jumpw.com/popion/sdk/v1.0.16/version.json) | |||
===== <span style="color: red;">sdkVersion>1.2.60的版本</span> ===== | |||
同样是查看pacakge.json,但是字段换成了"resVersion": "1.0.5" | |||
[远端文件列表json](https://h5ppreview.jumpw.com/popion/ | <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值的网络路径 | ||
第353行: | 第375行: | ||
*游戏外,直接在刚刚的cdnUrl后拼接带md5值的完整路径即可查看 | *游戏外,直接在刚刚的cdnUrl后拼接带md5值的完整路径即可查看 | ||
[查看网络资源](https://h5ppreview.jumpw.com/popion/ | [查看网络资源](https://h5ppreview.jumpw.com/popion/sdk/v1.0.17/avatarstart/ball_btn2-2c717f83a3.png) | ||
[[File:资源加载五.png]] | [[File:资源加载五.png]] | ||
第382行: | 第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/ | |||
== UI界面开发 == | == UI界面开发 == | ||
第616行: | 第631行: | ||
} | } | ||
</pre> | </pre> | ||
== 本地多人联机调试 == | == 本地多人联机调试 == | ||
第712行: | 第724行: | ||
== 地图的制作和选取 == | == 地图的制作和选取 == | ||
=== 地图官网地址 === | === 地图官网地址 === | ||
*[ | *[[上传流程|地图编辑器页签可以操作]] | ||
=== 地图制作导出 === | === 地图制作导出 === | ||
第720行: | 第731行: | ||
[[File:地图编辑视频.png]] | [[File:地图编辑视频.png]] | ||
=== | === 开发环境选择地图 === | ||
我们在登录界面,可以通过填写你gameid中的地图名字,来进入指定地图。 | |||
所以必须要先创建gameid,并且选择至少一张地图,该地图才能在调试时进入。 | |||
举例: | 举例: | ||
* | *上传地图到某个游戏gameid时,会从你自己的'''可用地图'''中选择。 | ||
- 可用地图指的是你自己制作上传了地图。或者其他人共享给了你地图。 | |||
- | |||
*比如你的可用地图名字是"推箱子"。 | |||
* | *右边会有可填写的自定义地图名字,比如"第一关" | ||
那么登录的时候就是填写 "第一关" | |||
*'''也就是说,地图做完后,必须要在审核平台上,添加到玩法里,生成自定义的名字,才可以在测试时填写进去生效 ''' | |||
== | == 打包与上传 == | ||
=== 美术资源发布流程 === | === 美术资源发布流程 === | ||
第755行: | 第757行: | ||
*2.美术上传完资源后,会生成一个资源版本号。告诉开发者 | *2.美术上传完资源后,会生成一个资源版本号。告诉开发者 | ||
*3.开发者地图编辑器打开时,输入这个版本号,就可以添加新增的预制体了。 | *3.开发者地图编辑器打开时,输入这个版本号,就可以添加新增的预制体了。 | ||
*4.开发者程序 调试/打包/上传 的时候,手动修改pakage. | *4.开发者程序 调试/打包/上传 的时候,手动修改pakage.json里的sdkversion或者cdnurl和该版本号一致。 | ||
=== 打包 === | === 打包 === | ||
第766行: | 第768行: | ||
[[File:打包.png]] | [[File:打包.png]] | ||
=== | === 上传 === | ||
*首先,要向我们内部人员申请一个账号来上传游戏,申请成功后会生成一个gameid | *首先,要向我们内部人员申请一个账号来上传游戏,申请成功后会生成一个gameid | ||
*修改配置,把gameid替换成自己的 | *修改配置,把gameid替换成自己的 | ||
*密钥修改成自己的 | |||
-打开项目目录下'''package.json''' | -打开项目目录下'''package.json''' | ||
[[File: | [[File:Gameid和密钥.png]] | ||
<pre> | <pre> | ||
npm run | npm run publish | ||
</pre> | </pre> | ||
运行命令后,会把构建生成的压缩包上传到远端,可以试玩了 | |||
=== | === 玩法测试 === | ||
[ | * [[上传流程|查看文档中试玩/审核/发布的部分]] | ||
== 项目目录和命令 == | == 项目目录和命令 == | ||
第787行: | 第790行: | ||
对于开发者,需要关心和操作的目录如下: | 对于开发者,需要关心和操作的目录如下: | ||
* '''node_modules/''' - | * '''node_modules/''' - 通过npm安装的PGC库文件,对外暴露的方法,可以通过bridge.d.ts来外部引用 | ||
* '''res-extra/''' - | * '''res-extra/''' - 开发者除了PGC的远程资源外,需要额外使用到的资源目录,比如UI,图片,音频,表格,最后会打包上传到PGC远端资源服务器 | ||
* '''ui/''' - 开发者自己在laya编辑器中,拼接的界面json文件,运行命令后,会转化为PGT指定的XUIComponent存放于此 | * '''ui/''' - 开发者自己在laya编辑器中,拼接的界面json文件,运行命令后,会转化为PGT指定的XUIComponent存放于此 | ||
* '''src/''' - 开发者所有的战斗逻辑代码修改都在此进行 | * '''src/''' - 开发者所有的战斗逻辑代码修改都在此进行 | ||
第808行: | 第811行: | ||
[[File:库文件更新.png]] | [[File:库文件更新.png]] | ||
==== npm run proto(协议通信)==== | ==== npm run proto(协议通信)==== | ||
该命令会用到tsrpc的proto协议, | 该命令会用到tsrpc的proto协议, | ||
第898行: | 第901行: | ||
==== npm run publish ( 打包文件 ) ==== | ==== npm run publish ( 打包文件 ) ==== | ||
运行命令后,会把dist目录下的前后端代码,额外的资源目录,打包到压缩包内,添加md5值 | *运行命令后,会把dist目录下的前后端代码,额外的资源目录,打包到压缩包内,添加md5值 | ||
*会把构建生成的压缩包上传到远端,在审核平台就能看到了 | |||
*审核平台再去操作试玩,提审,发布等流程 | |||
==== npm run server ( 启动本地host服务 ) ==== | ==== npm run server ( 启动本地host服务 ) ==== | ||
启动一个本地host服务器, | 启动一个本地host服务器, | ||
让自己的服务器代码生效,可以本地调试, | |||
局域网内其他用户在登录时,输入地址host地址,输入你的房间号,就可以链接到你的本地游戏一起调试 | |||
==== npm run sdklist( 查看可用资源列表 ) ==== | |||
会把目前可用的资源版本列表显示出来 | |||
[[File:查看可用.png]] | |||
==== npm run sdkupdate( 自动匹配更新库文件 ) ==== | |||
自动根据sdk版本更新bridge,防止版本不匹配 | |||
== 核心类目说明 == | == 核心类目说明 == | ||
第952行: | 第960行: | ||
[[File:XPopManager生命周期.png]] | [[File:XPopManager生命周期.png]] | ||
=== XRemote(前端) === | === XRemote(前端) === | ||
*以下是最关键的生命周期和方法调用,调用逻辑可以自定义设计,后端代码同理 | *以下是最关键的生命周期和方法调用,调用逻辑可以自定义设计,后端代码同理 | ||
第1,007行: | 第1,016行: | ||
== 官方论坛 == | == 官方论坛 == | ||
*[论坛地址](https://bbs.popx.com/) | *[论坛地址](https://bbs.popx.com/) | ||
* | *可以由我们内部人员给出的邀请链接申请加入论坛(需要输入邀请码后等待审核) | ||
*申请后,等待审核人员核实,批准后,即可浏览论坛内容,发表疑问贴。 | *申请后,等待审核人员核实,批准后,即可浏览论坛内容,发表疑问贴。 | ||
*强烈建议多去论坛搜索关键字,发帖会有对应的专业人员回答问题,大家一起维护论坛生态。 | *强烈建议多去论坛搜索关键字,发帖会有对应的专业人员回答问题,大家一起维护论坛生态。 |
2024年12月6日 (五) 05:45的最新版本
申请游戏后台账号
- 请先找我们内部人员申请后台账号,生成独属于你的gameid和secret,这在后续的开发和上传时至关重要
- 申请账号后,请参考如下文档,新建游戏玩法(如果之前已经有了地图编辑器的账号,可以直接通用)
- 新建游戏玩法
首次启动运行游戏
Demo资源下载
- [gitlab_demo地址](https://git.jumpw.com/popx_demo/demox.git)
- gitlab权限找我们内部人员申请
运行环境配置
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文件。
- 注意,我们目前不支持第三方模型文件,模型必须提交审核,不放在res-extra中
模型查看器
- 除了图片资源外,为了方便开发者使用,我们额外做了一个模型查看器
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)