实现接口功能

最近更新时间:2019-08-29 15:43:28

操作场景

本文档指导您如何实现匹配加房到开始帧同步、结束帧同步的流程。

前提条件

已创建 MainView.js、RoomView.js 、GameView.js 文件。

操作步骤

实现快速加房

快速加房操作在 MainView 完成。交互逻辑是玩家单击【快速加房】后,SDK 发起房间匹配请求,请求成功后玩家将加入房间,此时要跳转到 RoomView。

  1. 在 MainView.js 的 onInit 方法中为 button 绑定点击事件。
  2. 为 MainView 类添加 matchRoom 方法。
  3. 编译代码后,单击【快速加房】,模拟器页面将跳转到 RoomView。

MainView 示例代码如下:

    // 游戏主页
    import * as Util from "../Util.js";
    import BaseView from "./BaseView.js";
    import Component from "../component/index.js";
    const Global = Util.Global;

    export default class MainView extends BaseView {
        constructor() {
            super();
        }

        onInit() {
            const button = new Component.Button(20, 20, "快速加房");
            // 绑定点击事件
            button.onClick(() => this.matchRoom());

            const msgBox = new Component.MsgBox(20, 100, "");

            this.addComponent(button);
            this.addComponent(msgBox);
        }

        // 房间匹配方法
        matchRoom() {

            this.loading('匹配中...');

            const playerInfo = {
                name: Util.mockName(),
            };

            Global.name = playerInfo.name;

            const matchRoomPara = {
                playerInfo,
                maxPlayers: 2,
                roomType: "1",
            };

            // 调用房间匹配接口实现快速加房
            Global.room.matchRoom(matchRoomPara, event => {
                wx.hideLoading();
                if (event.code === Global.ErrCode.EC_OK) {
                    // 接口调用成功,跳转到 RoomView
                    return this.open(Global.RoomView);
                }

                return this.toast("匹配失败" + event.code + " " + event.msg);
            });
        }

        onUpdate() {}

        onDestroy() {}
    }

    Global.MainView = MainView;

实现修改玩家状态

RoomView 有两个按钮,分别是【准备】、【退出房间】。交互逻辑是单击【准备】将修改玩家自定义状态,当房间内全部玩家都准备好后,可以开始帧同步进行游戏。单击【退出房间】,玩家将发起退房请求,成功后进入 MainView。

  1. 为【准备】绑定点击事件。在 RoomView.js 的 onInit 中为 button1 添加事件。示例代码如下:

     const button1 = new Component.Button(20, 20, "准备");
     // 点击后切换状态
     button1.onClick(() => this.changeCustomPlayerStatus(this.customPlayerStatus === 1 ? 0 : 1));
    
     this.button = button1;    
  2. 0和1表示玩家“未准备”、“已准备”两种状态。customPlayerStatus 为 RoomView 类的属性,用来保存玩家状态。示例代码如下:

    export default class RoomView extends BaseView {
    
    button;
    msgBox;
    
    customPlayerStatus;
    
    // ...
  3. 为 RoomView 类添加 changeCustomPlayerStatus 方法。示例代码如下:

    // 修改用户自定义状态
    changeCustomPlayerStatus(customPlayerStatus) {
     const changeCustomPlayerStatusPara = {
         customPlayerStatus
     };
    
     Global.room.changeCustomPlayerStatus(changeCustomPlayerStatusPara, event => {
         if (event.code !== Global.ErrCode.EC_OK) {
             return this.toast("操作失败" + event.code);
         }
    
         return this.customPlayerStatus = customPlayerStatus;
     });
    }

检查房间属性

MGOBE SDK 中的 Room 对象会管理所有房间变化,任何玩家进行加房、退房、修改玩家状态等会改变房间信息的操作后,Room 对象会自动更新房间信息。
因此,玩家在 RoomView 修改状态后,只需要访问 Room 实例的 roomInfo 属性就能获得最新房间信息,页面也可以同步更新。可以直接使用 Room 实例的 onUpdate 方法检查房间属性并更新页面。

  1. 为 RoomView 类添加 onRoomUpdate 方法检查房间最新属性,更新按钮和文本框,并当全部玩家处于【已准备】状态时跳转到 GameView 页面。

    onRoomUpdate() {
     // 更新 按钮
     const playerId = MGOBE.Player.id;
     const player = Global.room.roomInfo.playerList.find(player => player.id === playerId);
    
     if (player) {
         this.customPlayerStatus = player.customPlayerStatus;
    
         // 更新 按钮 文字
         if (player.customPlayerStatus === 0) {
             this.button.setText("准备");
         } else {
             this.button.setText("取消准备");
         }
     }
    
     // 更新 MsgBox
     let msg = "房间ID:\n" + Global.room.roomInfo.id + "\n玩家列表:\n";
     Global.room.roomInfo.playerList.forEach((player, i) => {
         msg += i + ":" + player.id + (player.customPlayerStatus === 1 ? " 已准备" : " 未准备") + "\n";
     });
    
     this.msgBox.setText(msg);
    
     if (Global.room.isInRoom() && !Global.room.roomInfo.playerList.find(player => player.customPlayerStatus !== 1)) {
         // 全部玩家准备好就跳转
         setTimeout(() => this.open(Global.GameView), 1000);
     }
    }
  2. 在 onInit 追加代码,绑定 onRoomUpdate。示例代码如下:

    onInit() {
     // ...
     Global.room.onUpdate = this.onRoomUpdate.bind(this);
     // 刷新canvas
     this.onRoomUpdate();
    }
  3. 在 onDestroy 中解除绑定 onRoomUpdate。示例代码如下:

    onDestroy() {
     Global.room.onUpdate = null;
    }
  4. 编译代码,玩家单击【快速加房】进入 RoomView 后,单击【准备】,按钮和文本框都将更新。如果房间内的玩家都是【已准备】状态,页面将跳转到 GameView。
    房间信息更新

处理其他玩家加房

  1. 当其他玩家进入房间时,SDK 会触发 onJoinRoom 回调。因此可以在进入页面时为房间绑定 onJoinRoom 广播回调函数。修改 RoomView.js 中的 onInit 方法。示例代码如下所示:

    onInit() {
     // ...
    
     // 设置广播回调
     Global.room.onJoinRoom = this.onJoinRoom.bind(this);
    }
  2. 添加 onJoinRoom 方法,这里做一个 toast 提醒玩家即可。示例代码如下所示:

    // 加房广播
    onJoinRoom(event) {
     this.toast("新玩家加入");
    }
    注意:

    Global.room.onJoinRoom 指定了一个回调函数,当页面跳转到其他页面时,这个回调函数引用还在。如果该函数依赖于页面脚本的上下文,有可能在触发广播时报错。

  3. 离开页面时置空 Global.room.onJoinRoom。示例代码如下所示:

    onDestroy() {
     Global.room.onUpdate = null;
     Global.room.onJoinRoom = null;
    }
  4. 使用两个设备进行“快速加房”操作(如模拟器+手机预览),两个玩家有可能匹配在一起,这时页面上将弹出“新玩家加入”提示。

    注意:

    手机预览时打开调试,跳过域名检查。

    模拟器界面效果图如下所示:
    模拟器界面效果
    手机预览效果图如下所示:
    手机预览效果

实现退房功能

  1. 在 RoomView 中为 button2 绑定点击事件。示例代码如下:

    onInit() {
    
     // ...
    
     const button2 = new Component.Button(150, 20, "退出房间");
     button2.onClick(() => this.leaveRoom());
    
     // ...
    }
  2. 在 RoomView 类中实现 leaveRoom 方法。示例代码如下:

    // 退出房间
    leaveRoom() {
     Global.room.leaveRoom({}, event => {
         if (event.code !== Global.ErrCode.EC_OK && event.code !== Global.ErrCode.EC_ROOM_PLAYER_NOT_IN_ROOM) {
             return this.toast("操作失败" + event.code);
         }
         return this.open(Global.MainView);
     });
    }

RoomView 最终代码如下:

// 房间页
import * as Util from "../Util.js";
import BaseView from "./BaseView.js";
import Component from "../component/index.js";
const Global = Util.Global;

export default class RoomView extends BaseView {

    button;
    msgBox;

    // 自定义玩家状态:“0和1表示玩家“未准备”、“已准备”
    customPlayerStatus;

    constructor() {
        super();
    }

    onInit() {
        const button1 = new Component.Button(20, 20, "准备");
        // 点击后切换状态
        button1.onClick(() => this.changeCustomPlayerStatus(this.customPlayerStatus === 1 ? 0 : 1));

        this.button = button1;

        const button2 = new Component.Button(150, 20, "退出房间");
        button2.onClick(() => this.leaveRoom());

        const msgBox = new Component.MsgBox(20, 100, "");
        this.msgBox = msgBox;

        this.addComponent(button1);
        this.addComponent(button2);
        this.addComponent(msgBox);

        Global.room.onUpdate = this.onRoomUpdate.bind(this);
        // 刷新canvas
        this.onRoomUpdate();
        // 设置广播回调
        Global.room.onJoinRoom = this.onJoinRoom.bind(this);
    }

    // 修改用户自定义状态
    changeCustomPlayerStatus(customPlayerStatus) {
        const changeCustomPlayerStatusPara = {
            customPlayerStatus
        };

        Global.room.changeCustomPlayerStatus(changeCustomPlayerStatusPara, event => {
            if (event.code !== Global.ErrCode.EC_OK) {
                return this.toast("操作失败" + event.code);
            }

            return this.customPlayerStatus = customPlayerStatus;
        });
    }

    onRoomUpdate() {
        // 更新 按钮
        const playerId = MGOBE.Player.id;
        const player = Global.room.roomInfo.playerList.find(player => player.id === playerId);

        if (player) {
            this.customPlayerStatus = player.customPlayerStatus;

            // 更新 按钮 文字
            if (player.customPlayerStatus === 0) {
                this.button.setText("准备");
            } else {
                this.button.setText("取消准备");
            }
        }

        // 更新 MsgBox
        let msg = "房间ID:\n" + Global.room.roomInfo.id + "\n玩家列表:\n";
        Global.room.roomInfo.playerList.forEach((player, i) => {
            msg += i + ":" + player.id + (player.customPlayerStatus === 1 ? " 已准备" : " 未准备") + "\n";
        });

        this.msgBox.setText(msg);

        if (Global.room.isInRoom() && !Global.room.roomInfo.playerList.find(player => player.customPlayerStatus !== 1)) {
            // 全部玩家准备好就跳转
            setTimeout(() => this.open(Global.GameView), 1000);
        }
    }

    // 加房广播
    onJoinRoom(event) {
        this.toast("新玩家加入");
    }

    // 退出房间
    leaveRoom() {
        Global.room.leaveRoom({}, event => {
            if (event.code !== Global.ErrCode.EC_OK && event.code !== Global.ErrCode.EC_ROOM_PLAYER_NOT_IN_ROOM) {
                return this.toast("操作失败" + event.code);
            }
            return this.open(Global.MainView);
        });
    }

    onUpdate() { }

    onDestroy() {
        Global.room.onUpdate = null;
        Global.room.onJoinRoom = null;
    }
}

Global.RoomView = RoomView;
  1. 编译代码后,在模拟器中进行加房操作进入 RoomView 后,单击【退出房间】,即可跳转到 MainView。

实现开始帧同步

在 RoomView 中,全部玩家单击【准备】后,页面自动跳转到 GameView 页面。此时需要在 GameView 中检查房间帧同步状态,如果房间未开启帧同步,则调用开始帧同步接口。之后继续将玩家状态改为【未准备】,避免出现回到 RoomView 中又自动跳转 GameView 的情况。

  1. 修改 GameView.js 的 onInit 方法。示例代码如下:

    onInit() {
     const button = new Component.Button(20, 20, "结束帧同步");
    
     const msgBox = new Component.MsgBox(20, 100, "");
     this.msgBox = msgBox;
    
     this.addComponent(button);
     this.addComponent(msgBox);
    
     if (Global.room.roomInfo.frameSyncState !== Global.ENUM.FrameSyncState.START) {
         // 调用 startFrameSync 接口
         this.startFrameSync();
     }
    
     // 修改玩家状态
     this.changeCustomPlayerStatus(0);
    }
  2. 在 GameView 类中添加 startFrameSync 方法。示例代码如下:

    // 开始帧同步
    startFrameSync() {
     const func = () => Global.room.startFrameSync({}, event => {
         if (event.code !== Global.ErrCode.EC_OK) {
             return this.dialog("操作失败,是否重试?", () => func());
         }
     });
    
     func();
    }
  3. 在 GameView 类中添加 changeCustomPlayerStatus 方法。示例代码如下:

    // 修改用户状态
    changeCustomPlayerStatus(customPlayerStatus) {
     const changeCustomPlayerStatusPara = { customPlayerStatus };
    
     const func = () => Global.room.changeCustomPlayerStatus(changeCustomPlayerStatusPara, event => {
         if (event.code !== Global.ErrCode.EC_OK) {
             this.dialog("操作失败,是否重试?", () => func());
         }
     });
    
     func();
    }

实现发送帧消息

玩家在游戏过程中发送的帧消息都是由一些指令组成,例如“跳跃”、“发射子弹”等操作。这里实现一个简单的指令,每个玩家发送一个随机数出去。在 GameView 类中添加一个 sendFrame 方法。示例代码如下:

// 玩家发送帧消息
sendFrame() {
    const data = {
        name: Global.name,
        action: "random",
        number: Math.ceil(Math.random() * 100),
    }

    Global.room.sendFrame({ data });
}

实现接收帧广播

开始帧同步后,SDK 会收到服务器推送的帧广播消息。开发者需要绑定 onFrame 回调函数,接收并处理每一帧消息。

  1. 在 GameView 类的 onInit 中为 room 对象绑定帧消息广播回调,并实现 onFrame 方法。示例代码如下:

     onInit() {
    
             // ...
    
             // 设置广播回调
             Global.room.onRecvFrame = this.onRecvFrame.bind(this);
     }
    
     // ...
    
     onRecvFrame(event) {
             // 在这里处理帧广播
     }
  2. 在 onFrame 中添加代码,实现记录帧广播消息,并定时发送帧消息。示例代码如下:

     frameId = 0;
     frameItems = [];
    
     onRecvFrame(event) {
             // 在这里处理帧广播
    
             const frameId = event.data.frame.id;
    
             // 每隔 15 帧发送一次帧消息
             if (frameId > this.frameId + 15) {
                     this.frameId = frameId;
                     this.sendFrame();
             }
    
             // 记录帧广播消息
             if (event.data.frame.items) {
                     this.frameItems = this.frameItems.concat(event.data.frame.items);
             }
     }
  3. 在 onUpdate 中将帧消息渲染到页面上。示例代码如下:

     onUpdate() {
             // 渲染层不断更新页面
             this.drawFrameItems();
     }
    
     // ...
    
     drawFrameItems() {
             // 只显示5行
             const max = 5;
    
             if (this.frameItems.length > max) this.frameItems = this.frameItems.slice(this.frameItems.length - max);
    
             let msg = "";
             this.frameItems.forEach(item => {
                     msg += item.data.name + " : " + item.data.number + "\n";
             });
    
             this.msgBox.setText(msg);
     }
  4. 编译项目,在模拟器上依次单击【快速加房】、【准备】后,进入 GameView,页面将自动更新 MsgBox,并显示玩家消息。如下图所示:
    GameView效果

实现停止帧同步

为 button 绑定点击事件监听,实现停止帧同步。

  1. 在 onInit 中添加代码。示例代码如下:

    onInit() {
     const button = new Component.Button(20, 20, "结束帧同步");
     button.onClick(() => this.stopFrameSync());
    
     // ...
    }
  2. 使用 stopFrameSync 方法实现。示例代码如下:

    // 停止帧同步
    stopFrameSync() {
    
     if (Global.room.frameSyncState === Global.ENUM.FrameSyncState.STOP) {
         return;
     }
    
     const func = () => Global.room.stopFrameSync({}, event => {
         if (event.code !== Global.ErrCode.EC_OK) {
             this.dialog("操作失败,是否重试?", () => func());
         }
     });
    
     func();
    }
  3. 帧同步停止后,页面需要跳转到 RoomView。检查房间帧同步状态有两种方法:

    • 监听 onStartFrameSync、onStopFrameSync 广播。
    • 在 room.onUpdate 中检查 room.roomInfo.frameStatus 的值。
  4. 利用 room.onUpdate 回调同时检查帧同步状态和房间成员状态,如果房间停止帧同步并且房间成员状态全部为 0,就跳转到 RoomView。示例代码如下:

    onRoomUpdate() {
     if (Global.room.roomInfo.frameSyncState === Global.ENUM.FrameSyncState.STOP &&
         !Global.room.roomInfo.playerList.find(player => player.customPlayerStatus === 1)) {
         return this.open(Global.RoomView);
     }
    }
  5. 在 onInit 方法、startFrameSync 的回调中绑定 onRoomUpdate。示例代码如下:

     onInit() {
    
             // ...
    
             if (Global.room.roomInfo.frameSyncState !== Global.ENUM.FrameSyncState.START) {
                     // 调用 startFrameSync 接口
                     this.startFrameSync();
             } else {
                     Global.room.onUpdate = this.onRoomUpdate.bind(this);
             }
    
             // ...
     }
    
     // ...
    
     // 开始帧同步
     startFrameSync() {
             const func = () => Global.room.startFrameSync({}, event => {
                     if (event.code !== Global.ErrCode.EC_OK) {
                             return this.dialog("操作失败,是否重试?", () => func());
                     }
                     Global.room.onUpdate = this.onRoomUpdate.bind(this);
             });
    
             func();
     }
  6. 在 onDestroy 中清除 onUpdate、onFrame 回调。示例代码如下:

    onDestroy() {
     Global.room.onUpdate = null;
     Global.room.onFrame = null;
    }
  7. 编译代码,在模拟器中进入 GameView 开始帧同步,然后单击【结束帧同步】,界面将跳转到 RoomView,并且玩家状态为【未准备】。

    GameView 最终代码如下:

     // 帧同步游戏页面
     import * as Util from "../Util.js";
     import BaseView from "./BaseView.js";
     import Component from "../component/index.js";
     const Global = Util.Global;
    
     export default class GameView extends BaseView {
    
             msgBox;
    
             constructor() {
                     super();
             }
    
             onInit() {
                     const button = new Component.Button(20, 20, "结束帧同步");
                     button.onClick(() => this.stopFrameSync());
    
                     const msgBox = new Component.MsgBox(20, 100, "");
                     this.msgBox = msgBox;
    
                     this.addComponent(button);
                     this.addComponent(msgBox);
    
                     if (Global.room.roomInfo.frameSyncState !== Global.ENUM.FrameSyncState.START) {
                             // 调用 startFrameSync 接口
                             this.startFrameSync();
                     } else {
                             Global.room.onUpdate = this.onRoomUpdate.bind(this);
                     }
    
                     // 修改玩家状态
                     this.changeCustomPlayerStatus(0);
                     // 设置广播回调
                     Global.room.onRecvFrame = this.onRecvFrame.bind(this);
             }
    
             // 开始帧同步
             startFrameSync() {
                     const func = () => Global.room.startFrameSync({}, event => {
                             if (event.code !== Global.ErrCode.EC_OK) {
                                     return this.dialog("操作失败,是否重试?", () => func());
                             }
                             Global.room.onUpdate = this.onRoomUpdate.bind(this);
                     });
    
                     func();
             }
    
             // 停止帧同步
             stopFrameSync() {
    
                     if (Global.room.frameSyncState === Global.ENUM.FrameSyncState.STOP) {
                             return;
                     }
    
                     const func = () => Global.room.stopFrameSync({}, event => {
                             if (event.code !== Global.ErrCode.EC_OK) {
                                     this.dialog("操作失败,是否重试?", () => func());
                             }
                     });
    
                     func();
             }
    
             onRoomUpdate() {
                     if (Global.room.roomInfo.frameSyncState === Global.ENUM.FrameSyncState.STOP &&
                             !Global.room.roomInfo.playerList.find(player => player.customPlayerStatus === 1)) {
                             return this.open(Global.RoomView);
                     }
             }
    
             // 修改用户状态
             changeCustomPlayerStatus(customPlayerStatus) {
                     const changeCustomPlayerStatusPara = { customPlayerStatus };
    
                     const func = () => Global.room.changeCustomPlayerStatus(changeCustomPlayerStatusPara, event => {
                             if (event.code !== Global.ErrCode.EC_OK) {
                                     this.dialog("操作失败,是否重试?", () => func());
                             }
                     });
    
                     func();
             }
    
             // 玩家发送帧消息
             sendFrame() {
                     const data = {
                             name: Global.name,
                             action: "random",
                             number: Math.ceil(Math.random() * 100),
                     }
    
                     Global.room.sendFrame({ data });
             }
    
             frameId = 0;
             frameItems = [];
    
             onRecvFrame(event) {
                     // 在这里处理帧广播
    
                     const frameId = event.data.frame.id;
    
                     // 每隔 15 帧发送一次帧消息
                     if (frameId > this.frameId + 15) {
                             this.frameId = frameId;
                             this.sendFrame();
                     }
    
                     // 记录帧广播消息
                     if (event.data.frame.items) {
                             this.frameItems = this.frameItems.concat(event.data.frame.items);
                     }
             }
    
             drawFrameItems() {
                     // 只显示5行
                     const max = 5;
    
                     if (this.frameItems.length > max) this.frameItems = this.frameItems.slice(this.frameItems.length - max);
    
                     let msg = "";
                     this.frameItems.forEach(item => {
                             msg += item.data.name + " : " + item.data.number + "\n";
                     });
    
                     this.msgBox.setText(msg);
             }
    
             onUpdate() {
                     // 渲染层不断更新页面
                     this.drawFrameItems();
             }
    
             onDestroy() {
                     Global.room.onUpdate = null;
                     Global.room.onFrame = null;
             }
     }
    
     Global.GameView = GameView;

至此,这个 HelloWorld 示例结束,单击 这里 下载完整代码。