跳转到内容
主菜单
主菜单
移至侧栏
隐藏
导航
首页
最近更改
随机页面
MediaWiki帮助
泡泡
搜索
搜索
创建账号
登录
个人工具
创建账号
登录
查看“文档 v1.0”的源代码
页面
讨论
大陆简体
阅读
查看源代码
查看历史
工具
工具
移至侧栏
隐藏
操作
阅读
查看源代码
查看历史
常规
链入页面
相关更改
特殊页面
页面信息
←
文档 v1.0
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
管理员
您可以查看和复制此页面的源代码。
== 申请游戏后台账号 == * <span style="color: red;">请先找我们内部人员申请后台账号,生成独属于你的gameid和secret,这在后续的开发和上传时至关重要</span> * 申请账号后,请参考如下文档,新建游戏玩法(如果之前已经有了地图编辑器的账号,可以直接通用) * [[上传流程|新建游戏玩法]] == 首次启动运行游戏 == === 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 ==== - <span style="color: red;">必须使用指定的nodejs版本</span> ,推荐安装nvm以自由转换node版本。 - [nvm下载及安装方式](https://blog.csdn.net/ppz8823/article/details/130862191) *记得nvm需要换源,否则后续安装可能会出错,按照下述操作 [[File:nvm换源.png]] *最下面两行修改node和npm的镜像地址 <pre> node_mirror: https://npmmirror.com/mirrors/node/ npm_mirror: https://npmmirror.com/mirrors/npm/ </pre> *安装nvm后,命令行运行如下命令切换到node版本v10.24.1,一步步来: <pre> // 安装 nvm install 10.24.1 // 等待安装完成后,切换 nvm use 10.24.1 </pre> *nodejs也需要用下面的命令来换源,修改镜像地址 <pre> npm config set registry https://registry.popx.com/ </pre> *如果package.json中, bridge 使用 1.0.573 以上的版本,必须用新源https://registry.popx.com/,老版本的bridge建议升级到最新 ==== 修改host文件 ==== - 找到host文件,一般在`C:\WINDOWS\system32\drivers\etc`目录下。 - 在文件末尾添加: <pre> 199.232.68.133 raw.githubusercontent.com </pre> ==== laya 版本 v2.13.1 ==== - [laya官方ide](https://ldc2.layabox.com/layadownload/?type=layaairide-LayaAir%20IDE%202.13.1) ==== 安装项目自定义封装的cmd命令库 ==== - 推荐安装方式: <pre> npm i popxcmd -g --registry https://registry.popx.com </pre> === 如何运行游戏 === ==== 安装依赖 ==== - 在demo目录下安装相关依赖: - 打开demo目录文件夹,运行: <pre> npm i </pre> - **注意**:安装过程中,有WARN不要紧,有ERR才是安装失败 - **注意**:如果安装失败,检查host文件是否修改,并尝试删除`node_modules`目录后重新运行命令。 - **注意**:安装成功后,不需要每次运行都重新安装,除非我们更新了公共基础库。[[#npm i(安装依赖)]] ==== 编译 ==== - <span style="color: red;">每次有代码修改后,运行前需调用命令进行编译:</span> <pre> npm run build </pre> [[File:输入编译命令.png]] ==== 运行 ==== - 每次编译完成后,使用VSCODE运行调试,或按键F5启动游戏。 - <span style="color: red;">不需要打开laya的IDE环境,直接用VSCode调试即可</span> [[File:编译后运行.png]] ==== 登录界面 ==== - 运行后,浏览器将显示登陆界面。 [[File:默认地图.png]] - 账号密码随便输入,会创建新账号 - 输入账号密码后,选择指定地图(可选),即可看到战斗服务器世界中有自己的人物。 - 默认的demo工程中,如果没有修改gameid和secretKey,地图请填写"地图_0" ==== 单机设置初始位置 ==== 我们完整的demo代码中,会启动本地服务器,并且在服务器逻辑中同步玩家坐标。 如果只运行了前端代码,没有启动服务器,会卡在一个空白的位置。 其实是人在地图的底下。要解决这个问题,可以在前端创建人物的地方,加一行代码,让人物初始位置在地图上方。 位置是'''src/client/index.ts''' <pre> hero.position = new XVector3(0, 12, 0); </pre> [[File:前端修改初始位置.png]] <span style="color: red;">每次有代码修改后,运行前需调用命令进行编译</span> [[#编译]] 当然,想要快速体验已有的服务器同步功能,可以跳转到 [[#本地多人联机调试]] == 前端基础逻辑 == 根据以下步骤,一步步进入泡泡的游戏世界,即使你是新手也无需担心。 === 前端游戏入口 === - '''/src/client/index.ts''' 文件中的 `XPopManager.onGameStart` 是前端的主要入口点。 === 创建第一个人物 === 在 `simpleGame` 类中,我们将创建并初始化第一个游戏人物。 <pre> 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(); } </pre> *注意:请将原代码中的 '''new Main()''' 替换为 '''new simpleGame()'''; 上述的代码我们以最简单的方式,去掉一切服务器交互,添加一个基础模型到游戏中跑动 <span style="color: red;">再提醒一次,每次有代码修改后,运行前需调用命令进行编译</span> [[#编译]] === 播放人物的动作 === 我们的人物有上下半身,在知道动画名字的情况下,需要分上下半身来播放动画 以'act_avatar_plant_collect'为例 <pre> let animbaseStr = 'act_avatar_plant_collect'; role.model.playAniUp(`${animbaseStr}_up`); role.model.playAniDown(`${animbaseStr}_down`); </pre> 那么如何快速查看已有哪些动画呢? [动作查看器](https://webdev.popx.com/client.html?autoStart=1&userName=wuyaxi&project=actionviewer) === 添加按键控制 === 看demo中的代码,提供了一个MoveComponent类 摇杆的显示资源放在'''res-extra'''目录中 [[File:摇杆资源.png]] 以下是示例中添加摇杆控制的代码 <pre> //摇杆背景 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); } </pre> 示例中,添加了一个触摸区域挂载在根节点的左下角,触摸反馈会根据'''XEvent'''来传递给节点 代码中根据摇杆方向变化来控制人物移动 其中涉及了部分修改玩家状态的服务器代码,可以先忽略 === 摄像机的跟随 === 代码中,摄像机跟随人物移动,只需要简单的一行 <pre> let hero = new Xmodel(); //这是全局的摄像机 XPopManager.instance.xScene.camera.focus = hero; </pre> 摄像机的旋转视角,demo中也提供了一个类'''CameraComponent''' 和摇杆的节点类似,通过'''XEvent'''来传递点击事件, 根据点击事件的拖动,来修改摄像机的旋转视角 '''xcamera.rotate''' <pre> 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); } </pre> === 砖块说明 === [[File:砖块说明.png]] 如图所示,我们的世界地图由若干个方形大'''地块'''组成, 每个地块里,可以划分成32*32的平面坐标,一层层叠高,可以有64层高, 转化为三维坐标系就是32*64*32,对应xyz轴, 地块上,可以添加以坐标为单位的'''砖块'''。 其中,cubeId表示砖块的类型,比如草地就是id为1的砖块,该砖块具有可以踩踏行走的特性。 某些其他的砖块具有别的特性,都通过cubeid来区分,当开发者接触地图编辑器时,就会有所了解。 我们以Demo中,'''MoveComponent'''中的一段代码来参考 <pre> // 游戏世界地图 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; } </pre> *再以图中为例,v3的坐标代表了地图上的位置,(24,3,26) *如果需要设置模型在地图上的位置,用如下代码 <pre> let pos = new XVector3(x,y,z); xModel.position = pos; </pre> == 资源加载说明 == === 演示加载图片 === <pre> // 测试加载图片 // 创建一个全屏的挂载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; </pre> 编译,运行后,可以看到,添加了指定的图片 [[File:资源加载一.png]] === 公共资源 === PGT为开发者提供了很多基础的图片,模型,音频等等的资源。 ====<span style="color: red;">怎么查看有哪些公共资源呢?</span>==== *打开项目目录下的package.json文件 [[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" <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值的网络路径 [[File:资源加载四.png]] 这就是我们刚刚演示的加载图片 ''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) [[File:资源加载五.png]] *音频文件同理,模型文件直接游戏内调用接口显示 === 额外资源 === 最基础的demo中,会看到用于演示的摇杆资源是放在'''res-extra'''目录下的。 一般来说,开发者的一些特制UI,图形,可以放在这里。 加载时在路径前加上"res-extra/"即可。 *<span style="color: red;">注意,额外资源调用前,要调用如下固定代码开启合图资源的配置,建议参考demo,在游戏入口就初始化调用一次</span> <pre> XResManager.instance.enableAtlas("res-extra/fileconfig.json",()=>{ // 可以调用额外资源了 }); </pre> 每次在调用命令,npm run ui时,会更新上述的'''fileconfig.json'''文件。 [[#npm run ui ( 生成ui类 )]] *<span style="color: red;">注意,我们目前不支持第三方模型文件,模型必须提交审核,不放在res-extra中</span> === 模型查看器 === *除了图片资源外,为了方便开发者使用,我们额外做了一个模型查看器 [公共模型查看器](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元素。 [[File:新界面.png]] ==== 界面添加UI组件 ==== 例如,添加一个名为 `btnFirst` 的按钮,并确保其 `var` 属性有值,以便后续导出。 [[File:UI按钮有值.png]] 添加后,记得保存,'''ctrl+S''' === 额外资源的添加 === 注意,所有上述scene中用到的美术资源,必须放在Assets/uiextra/ 目录下 否则导出ui时会失败 === 运行生成UI命令 === 在命令行中运行以下命令来生成UI代码: <pre> npm run ui </pre> 运行成功后,生成的UI类将位于 `src/ui/popxMaxUI.ts` 文件中。 [[File:UI生成代码.png]] 其中,添加了一个和scene名相同的UI类, 并且,var有值的组件也一起生成了对应属性。 === 代码控制XUIComponent === ==== 注册UI类的控制文件 ==== 使用 `PopxBridge.regUIClass()` 方法在相应的类中注册UI类,并处理UI相关的按键逻辑。 <pre> //注册自定义的ui类 PopxBridge.regUIClass(DemoUI.getClassName(), DemoUI); </pre> 以下是我们新加UI的最基础代码,在界面显示的时候,注册点击事件,点击后打印日志 <pre> 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"); } } </pre> ==== 代码调用显示UI ==== 通过 `xUiManager.instance.openUI()` 方法来调用并显示UI界面。 <pre> // 加载对应的json文件路径,并挂载在指定父节点上 XUiManager.instance.openUI('res-extra/ui/demoUI', XPopManager.rootNode); </pre> -编译,运行 [[File:UI点击按钮.png]] ==== 代码调用加载组件 ==== *某些界面上的元素,比如卡牌,也可以类似做成一个scene,运行npm run ui 自动生成类 *比如类名为CardView *注册和UI注册类似 <pre> PopxBridge.regUIClass(CardView.getClassName(), CardView); </pre> *创建时,调用`XUiManager.instance.createView` <pre> XUiManager.instance.createView(path,parent,this.onLoadCardView,showData); onLoadCardView(view:XUIComponent) { } </pre> == 前后端数据交互 == 该篇会讲述服务器和前端的数据同步,如果想要先了解项目目录和工作流,建议先参考如下篇幅 [[#主要工程目录]] === 进入房间(游戏世界) === 如下是最基本的开发登录流程说明 {| class="wikitable" |- ! 步骤 ! 描述 ! 流向 |- | 开始 | 流程的开始 | -> 登录页面 |- | 登录页面 | 账号密码,选定地图,输入房间号(可不填) | -> 远端服务器(官方) |- | 远端服务器(官方) | 获取玩家账户信息,分配到房间,房间就是我们的host代码 | -> Client 和 -> Host |- | Client | 监听XPopManager.onGameStart | -> 前端逻辑开始 |- | Host | 监听XPopManager.onHostStart | -> 房间战斗逻辑开始 |} 远端服务器(官方)会处理最基本的登录,分配房间,创建游戏世界的逻辑,并且同步该世界中的游戏数据。 通过'''XpopManager'''管理类,来向Client和Host抛出事件,开发者可以监听这些事件做处理。 开发者需要关心的逻辑,就是Clint和Server(上图中Host)目录下的代码。 === 前后端协议交互 === 项目采用proto来做相互通信,建议参考如下篇幅 [[#npm run proto(协议通信)]] === 前后端共用配置 === 前端'''src/client'''目录和后端'''src/server'''目录中,都可以调用如下代码来加载所有的表格配置 <pre> CsvManager.ins.loadCsvs(this, () => { // Do some thing after load }); </pre> 表格的加载逻辑和解析逻辑,可以参考CsvManager中的代码 '''src/shared/csv/CsvManager.ts''' ''如何新增配置?'' 以demo中的'''item.csv'''为例. *1.配置csv文件资源的根目录为'''res-extra/csv/''' ,在里面添加'''item.csv''' *2.'''CsvManager.ts'''文件中,添加读取表格和解析表格的逻辑 <pre> // 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); }); } </pre> *3.解析表格得到的'''CItemData'''结构,定义在'''src/shared/dataMgr/'''中,可以被前后端一起调用 *4.如果是加载json文件,用'''XResManager.instance.loadText''' === 玩家战斗数据快照 === 游戏战斗服务器中,每个玩家的实时信息,通过快照来更新 ==== XVF_SnapData ==== 以demo中的'''Snap_UserData.ts'''为例 *该玩家数据中类中,包含的各种public变量,都是''XVF_SnapData''接口类型 <pre> export class Snap_UserData { //坐标旋转 public position: XVF_SnapData<SyncPos>; //状态 public state: XVF_SnapData<SyncState>; //hp public hp: XVF_SnapData<number>; } </pre> <span style="color: red;">快照类型数据,前端负责注册和监听,服务器负责修改</span> *每次该接口类型的数据发生变化时,会调用OnSet方法,这类数据的修改逻辑是:服务器修改,前端监听变化 ** 前端登录成功后,服务器注册对应玩家数据 ** 前端对数据中的某个XVF_SnapData做监听,注册OnSet方法 ** 服务器战斗逻辑修改了该数据时,前端根据OnSet的逻辑做表现处理 服务器先注册全局的快照 <pre> let userData = new Snap_UserData(); XPopManager.instance.xHost.RegisterSnapshot(`UserData${i}`, userData); </pre> 前端绑定同一个地址的快照 <pre> this._userData = new Snap_UserData(); XPopManager.instance.xRemote.RegisterSnapshot(`UserData${index}`, this._userData); </pre> *XVF_SnapData数据,如果是复杂类型,需要调用UpdateData()方法,前端才能触发OnSet 如下的SyncState就是复杂类型 <pre> 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(); } </pre> *XVF_SnapData如果是基础类型,不需要调用UpdateData() <pre> public hp: XVF_SnapData<number>; public setHP(hp:number):void { this.hp.value = hp; } </pre> == 本地多人联机调试 == === 启动本地服务器 === *启动本地服,需要用到 npm run server 命令 *建议不要占用vscode的终端,在外部新开一个cmd窗口,这样不妨碍后续的编译运行调试 [[File:本地联机一.png]] [[File:本地联机二.png]] === 第一个账号创建房间 === 直接在vscode中,构建,运行 [[File:本地联机三.png]] 然后选择地图,勾选进入本地服,会生成房间,并且该房间的房间号,会显示在hostserver的网页链接中 [[File:本地联机四.png]] 由于本地服务器的代码中,在玩家进入时,给玩家分配了位置,所以初始位置在地图上了,而不是单机的在默认(0,0,0)的坐标 *src/server/index.ts/ <pre> 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); } </pre> [[File:本地联机五.png]] === 其他账号进入游戏 === *想要第二个人进入游戏,另外打开一个浏览器页面,输入刚刚npm run server 生成的链接 *输入房间号,输入相同地图,输入另一个账号名字 [[File:本地联机六.png]] *能两个人都出现在房间地图中了,并且可以同步坐标。具体的逻辑可以参考demo中的快照修改与监听 [[File:本地联机七.png]] === 代码如何调试 === ==== 前端调试 ==== 直接在vscode中打断点即可, 可以很方便的观察堆栈和变量信息 [[File:前端调试.png]] ==== 本地服务器调试 ==== *本地服务器在调用命令'''npm run server'''后,会启动一个Host网页 *只要是网页,那就能调试js代码 *打开网页的开发者工具(一般浏览器是f12),找到'''index.js'''文件 *这时有个问题,发现打不了断点,原因是代码编译过程被混淆了 [[File:服务器调试一.png]] *我们需要关闭混淆的配置,在'''rollup.config.js'''中修改 [[File:服务器调试二.png]] *修改plugins中,关闭sourcemap,不使用terser()压缩 [[File:服务器调试三.png]] *修改完后,每次编译后运行的服务器Host代码就都可以断点了 编译,启动服务器,运行 <pre> npm run build npm run server </pre> *这次会发现,Host的网页代码有了行号,可以断点调试了 [[File:服务器调试四.png]] ==== 调试注意事项 ==== *每次'''npm run build'''后,如果修改了服务器代码,可以在本地host的浏览器里,立刻刷新,F5或者shift+f5 *<span style="color: red;">如果修改了编译脚本来调试服务器,记得最后打包上传前,把rollup的代码改回来</span> == 地图的制作和选取 == === 地图官网地址 === *[[上传流程|地图编辑器页签可以操作]] === 地图制作导出 === 在项目目录'''/地图编辑器操作说明'''中,有视频教程 [[File:地图编辑视频.png]] === 开发环境选择地图 === 我们在登录界面,可以通过填写你gameid中的地图名字,来进入指定地图。 所以必须要先创建gameid,并且选择至少一张地图,该地图才能在调试时进入。 举例: *上传地图到某个游戏gameid时,会从你自己的'''可用地图'''中选择。 - 可用地图指的是你自己制作上传了地图。或者其他人共享给了你地图。 *比如你的可用地图名字是"推箱子"。 *右边会有可填写的自定义地图名字,比如"第一关" 那么登录的时候就是填写 "第一关" *'''也就是说,地图做完后,必须要在审核平台上,添加到玩法里,生成自定义的名字,才可以在测试时填写进去生效 ''' == 打包与上传 == === 美术资源发布流程 === 由于美术的模型资源都是由官方制作,所以需要走如下完整流程,才可以在地图编辑器和游戏运行时加载到资源 *1.开发者提需求让美术帮上传资源。 *2.美术上传完资源后,会生成一个资源版本号。告诉开发者 *3.开发者地图编辑器打开时,输入这个版本号,就可以添加新增的预制体了。 *4.开发者程序 调试/打包/上传 的时候,手动修改pakage.json里的sdkversion或者cdnurl和该版本号一致。 === 打包 === 在编译之后,项目目录下运行 <pre> npm run publish </pre> 运行命令后,会把dist目录下的前后端代码,额外的资源目录,打包到压缩包内,添加md5值 [[File:打包.png]] === 上传 === *首先,要向我们内部人员申请一个账号来上传游戏,申请成功后会生成一个gameid *修改配置,把gameid替换成自己的 *密钥修改成自己的 -打开项目目录下'''package.json''' [[File:Gameid和密钥.png]] <pre> npm run publish </pre> 运行命令后,会把构建生成的压缩包上传到远端,可以试玩了 === 玩法测试 === * [[上传流程|查看文档中试玩/审核/发布的部分]] == 项目目录和命令 == === 主要工程目录 === 对于开发者,需要关心和操作的目录如下: * '''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文件,如果官方有更新,可以修改版本号,重新安装 [[File:库文件更新.png]] ==== npm run proto(协议通信)==== 该命令会用到tsrpc的proto协议, 把'''src/shared/protocols'''下的指定格式文件, 转换到'''src/shared/protocols/generated/serviceProto'''文件中, * 文件名为:Ptl{接口名}.ts '''<span style="color: red;">(Ptl 是 Protocol 的缩写,l是小写的L)</span>''' * 请求类型名为: Req{接口名},通过 interface 或 type 定义,需 export * 响应类型名为:Res{接口名},通过 interface 或 type 定义,需 export 例如,添加'''PtlPosition.ts'''文件 文件代码如下 <pre> // 该接口用于向服务器同步自身的坐标 export interface ReqPosition { i: number; x: number; y: number; z: number; ry: number; } export interface ResPosition { ret: string; diy: string; } </pre> 在demo目录下运行 npm run proto后, '''serviceProto.ts'''中会增加可外部调用的api类型 <pre> export interface ServiceType { api: { "Position": { req: ReqPosition, res: ResPosition }, }, msg: { } } </pre> '''前端代码调用请求''' <pre> // 前端可以用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) } </pre> '''后端代码监听请求处理返回''' <pre> // 服务器收到前端请求的处理返回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: "自定义参数" }); }); </pre> 如果想更多了解proto的结构和数据类型说明,可以参考官方链接 [tsrpc-proto格式说明](https://tsrpc.cn/docs/server/service-proto.html) ==== npm run build ( 编译构建 ) ==== '''<span style="color: red;">每次代码有修改时,如果要调试运行,必须先运行此命令</span>''' 运行后,会在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( 查看可用资源列表 ) ==== 会把目前可用的资源版本列表显示出来 [[File:查看可用.png]] ==== npm run sdkupdate( 自动匹配更新库文件 ) ==== 自动根据sdk版本更新bridge,防止版本不匹配 == 核心类目说明 == === XPopManager === Bridge.d.ts中的核心类 对游戏逻辑进行了结构化的拆封和管理, 可以理解为, 库文件内处理了各种玩家的逻辑,比如登录,加载地图,分配房间,快照数据同步等等,然后关键的几个节点,暴露给外部去执行游戏逻辑。 开发者只需要去编写独立游戏模块的代码,其中,'''Xremote'''暴露给前端处理,'''Xhost'''暴露给后端处理 下面是最重要的几个生命周期函数 ;onGameStart:玩家已经完成了登录步骤,已经分配好了房间,通知前端游戏开始 ;onHostStart:玩家已经完成了登录步骤,已经分配好了房间,通知后端游戏开始 ;onGameOver:游戏结束(一般是玩家都离开了战斗房间) ;onUpdate:每一帧会调用更新 我们在demo中,index.ts里,可以给这几个方法监听绑定函数,输出日志就可以查看 <pre> 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"); } </pre> [[File:XPopManager生命周期.png]] === 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来封装一个单例,这样就能外部夸文件调用事件了。 如下为演示封装的单例 <pre> 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; } } </pre> 事件的派发 <pre> EventManager.instance.dispatch("new_event_name", ...args); </pre> 事件的监听 <pre> EventManager.instance.add("new_event_name", this, this.ParseNewEvent); private ParseNewEvent(...args) { console.log("parse new event") } </pre> == 官方论坛 == *[论坛地址](https://bbs.popx.com/) *可以由我们内部人员给出的邀请链接申请加入论坛(需要输入邀请码后等待审核) *申请后,等待审核人员核实,批准后,即可浏览论坛内容,发表疑问贴。 *强烈建议多去论坛搜索关键字,发帖会有对应的专业人员回答问题,大家一起维护论坛生态。 *常见问题faq帖子会持续维护 [论坛FAQ](https://bbs.popx.com/t/topic/53)
返回
文档 v1.0
。
开关有限宽度模式