手把手教你将单机游戏改造成对战网游(附详细教程)

前言:本Demo原来是Cocos Creator官方的一个Demo,本文章利用了第三方联网插件工具Matchvs将其改造成了一个三人对战的Demo,(在线体验地址)。

注意:

1.游戏满三人才可以开启,匹配成功后,玩家通过键盘AD键操纵小怪物向左向右移动抢摘星星。

2.下载Demo源码后,需用Cocos Creator打开工程(建议使用1.7.0及以上版本)。

游戏配置

Demo运行之前需要去Matchvs 官网配置游戏相关信息,以获取Demo运行所需要的GameID、AppKey、SecretID。如图:

获取到相关游戏信息之后,运行Demo,即可进入房间,准备开始游戏,如图所示:

初始化SDK

在引入SDK之后,在初始化前需要先调用Matchvs.MatchvsEngine.getInstance()获取一个Matchvs引擎对象实例:

var engine = Matchvs.MatchvsEngine.getInstance();

另外我们需要定义一个对象,该对象定义一些回调方法,用于获取游戏中玩家加入、离开房间、数据收发的信息,这些方法在特定的时刻会被SDK调用。

var response = {

    // 可以现在定义一些回调方法,也可以过后再定义。

};

为方便使用,我们把engine和reponse放到单独的文件Mvs.js中,使用module.exports将它们作为全局变量使用:

var engine = Matchvs.MatchvsEngine.getInstance();

var response = {};

module.exports = {

    engine: engine,

    response: engine

};

// 文件路径:assets\scripts\Mvs.js

其他文件可以用require函数引入engine和reponse:

var mvs = require("Mvs");

// 引擎实例:mvs.engine

// 引擎回调实现:mvs.response

完成以上步骤后,我们可以调用初始化接口建立相关资源。

mvs.engine.init(response, channel, platform, gameId);

// 文件路径:assets\scripts\Lobby.js

注意 在整个应用全局,开发者只需要对引擎做一次初始化。

建立连接

接下来,我们就可以从Matchvs获取一个合法的用户ID,通过该ID连接至Matchvs服务端。

获取用户ID:

cc.Class({

    onLoad: function() {

        mvs.response.registerUserResponse = this.registerUserResponse.bind(this);

        mvs.engine.registerUser();

    },

    registerUserResponse: function(userInfo) {

        // 注册成功,userInfo包含相关用户信息

    },

    // ...

})

// 文件路径:assets\scripts\Lobby.js

用户信息需要保存起来,我们使用一个类型为对象的全局变量GLB来存储:

GLB.userInfo = userInfo;

登录:

cc.Class({

    onLoad: function() {

        // ...

        mvs.engine.login(userInfo.id, userInfo.token, gameId, gameVersion, appKey,

            secret, deviceId, gatewayId);

        // ...

    },

    loginResponse: function(loginRsp) {

        // 登录成功,loginRsp包含登录相关信息

    },

    // ...

})

// 文件路径:assets\scripts\Lobby.js

加入房间

成功连接至Matchvs后,立即随机匹配加入一个房间进行游戏。

代码如下:

cc.Class({

    loginResponse: function() {

        // ...

        mvs.response.joinRoomResponse = this.joinRoomResponse.bind(this);

        mvs.engine.joinRandomRoom(maxPlayer, userProfile);

        // ...

    },

    joinRoomResponse: function(status, userInfoList, roomInfo) {

        // 加入房间成功,status表示结果,roomUserInfoList为房间用户列表,roomInfo为房间信息

        // ...

    },

    // ...

})

// 文件路径:assets\scripts\Lobby.js

停止加入

我们设定如果有3个玩家匹配成功则满足开始条件且游戏设计中不提供中途加入,此时需告诉Matchvs不要再向房间里加人。

代码如下:

cc.Class({

    joinRoomResponse: function(status, userInfoList, roomInfo) {

        // 加入房间成功,status表示结果,roomUserInfoList为房间用户列表,roomInfo为房间信息

        // ...

        if (userIds.length >= GLB.MAX_PLAYER_COUNT) {

            mvs.response.joinOverResponse = this.joinOverResponse.bind(this); // 关闭房间之后的回调

            var result = mvs.engine.joinOver("");

            this.labelLog("发出关闭房间的通知");

            if (result !== 0) {

                this.labelLog("关闭房间失败,错误码:", result);

            }

            GLB.playerUserIds = userIds;

        }

    },

    joinOverResponse: function(joinOverRsp) {

        if (joinOverRsp.status === 200) {

            this.labelLog("关闭房间成功");

            // ...

        } else {

            this.labelLog("关闭房间失败,回调通知错误码:", joinOverRsp.status);

        }

    },

})

// 文件路径:assets\scripts\Lobby.js

在这里需要记下房间的用户列表,记入到全局变量GLB.playerUserIds中,后面要使用到。

发出游戏开始通知

如果收到服务端的房间关闭成功的消息,就可以通知游戏开始了。

cc.Class({

    // ...

    joinOverResponse: function(joinOverRsp) {

        if (joinOverRsp.status === 200) {

            this.labelLog("关闭房间成功");

            this.notifyGameStart();

        } else {

            this.labelLog("关闭房间失败,回调通知错误码:", joinOverRsp.status);

        }

    },

    notifyGameStart: function () {

        GLB.isRoomOwner = true;

        var event = {

            action: GLB.GAME_START_EVENT,

            userIds: GLB.playerUserIds

        }

        mvs.response.sendEventResponse = this.sendEventResponse.bind(this); // 设置事件发射之后的回调

        mvs.response.sendEventNotify = this.sendEventNotify.bind(this); // 设置事件接收的回调

        var result = mvs.engine.sendEvent(JSON.stringify(event));

        // ...

        // 发送的事件要缓存起来,收到异步回调时用于判断是哪个事件发送成功

        GLB.events[result.sequence] = event;

    },

    sendEventResponse: function (info) {

        // ... 输入校验

        var event = GLB.events[info.sequence]

        if (event && event.action === GLB.GAME_START_EVENT) {

            delete GLB.events[info.sequence]

            this.startGame()

        }

    },

    sendEventNotify: function (info) {

        if (info

            && info.cpProto

            && info.cpProto.indexOf(GLB.GAME_START_EVENT) >= 0) {

            GLB.playerUserIds = [GLB.userInfo.id]

            // 通过游戏开始的玩家会把userIds传过来,这里找出所有除本玩家之外的用户ID,

            // 添加到全局变量playerUserIds中

            JSON.parse(info.cpProto).userIds.forEach(function(userId) {

                if (userId !== GLB.userInfo.id) GLB.playerUserIds.push(userId)

            });

            this.startGame()

        }

    },

    startGame: function () {

        this.labelLog('游戏即将开始')

        cc.director.loadScene('game')

    },

})

// 文件路径:assets\scripts\Lobby.js

游戏数据传输

游戏进行中在创建星星、玩家进行向左、向右操作时,我们将这些操作广播给房间内其他玩家。界面上同步展示各个玩家的状态变化。

其中星星是房主创建和展示,然后通知其他玩家,其他玩家收到消息后展示,相关的代码如下:

cc.Class({

    onLoad: function() {

        mvs.response.sendEventNotify = this.sendEventNotify.bind(this);

        // ...

    },

    sendEventNotify: function (info) {

        // ...

        if (info.cpProto.indexOf(GLB.NEW_START_EVENT) >= 0) {

            // 收到创建星星的消息通知,则根据消息给的坐标创建星星

            this.createStarNode(JSON.parse(info.cpProto).position)

        } /* 其他else if条件 */

    },

    // 根据坐标位置创建渲染星星节点

    createStarNode: function (position) {

        // ...

    },

    // 发送创建星星事件

    spawnNewStar: function () {

        if (!GLB.isRoomOwner) return;    // 只有房主可创建星星

        var event = {

            action: GLB.NEW_START_EVENT,

            position: this.getNewStarPosition()

        }

        var result = mvs.engine.sendEvent(JSON.stringify(event))

        if (!result || result.result !== 0)

            return console.error('创建星星事件发送失败');

        this.createStarNode(event.position);

    },

    // 随机返回'新的星星'的位置

    getNewStarPosition: function () {

        // ...

    },

    // ...

})

// 文件路径:assets\scripts\Game.js

玩家进行向左、向右操作时,这些消息会发送给其他玩家:

cc.Class({

    setInputControl: function () {

        var self = this;

        cc.eventManager.addListener({

            event: cc.EventListener.KEYBOARD,

            onKeyPressed: function (keyCode, event) {

                var msg = { action: GLB.PLAYER_MOVE_EVENT };

                switch (keyCode) {

                    case cc.KEY.a:

                    case cc.KEY.left:

                        msg.accLeft = true;

                        msg.accRight = false;

                        break;

                    case cc.KEY.d:

                    case cc.KEY.right:

                        msg.accLeft = false;

                        msg.accRight = true;

                        break;

                    default:

                        return;

                }

                var result = mvs.engine.sendEvent(JSON.stringify(msg));

                if (result.result !== 0)

                    return console.error("移动事件发送失败");

                self.accLeft = msg.accLeft;

                self.accRight = msg.accRight;

            },

            onKeyReleased: function (keyCode, event) {

                var msg = { action: GLB.PLAYER_MOVE_EVENT };

                switch (keyCode) {

                    case cc.KEY.a:

                        msg.accLeft = false;

                        break;

                    case cc.KEY.d:

                        msg.accRight = false;

                        break;

                    default:

                        return;

                }

                var result = mvs.engine.sendEvent(JSON.stringify(msg));

                if (result.result !== 0)

                    return console.error("停止移动事件发送失败");

                if (msg.accLeft !== undefined) self.accLeft = false;

                if (msg.accRight !== undefined) self.accRight = false;

            }

        }, self.node);

    },

    onLoad: function () {

        // ...

        this.setInputControl();

    }

    // ...

})

// 文件路径:assets\scripts\Player1.js

cc.Class({

    sendEventNotify: function (info) {

        if (/* ... */) {

            // ...

        } else if (info.cpProto.indexOf(GLB.PLAYER_MOVE_EVENT) >= 0) {

            // 收到其他玩家移动的消息,根据消息信息修改加速度

            this.updatePlayerMoveDirection(info.srcUserId, JSON.parse(info.cpProto))

        } /* 更多else if条件*/

    },

    // 更新每个玩家的移动方向

    updatePlayerMoveDirection: function (userId, event) {

        // ...

    },

    // ...

})

// 文件路径:assets\scripts\Game.js

考虑到数据同步会有延迟,不同客户端收到的数据的延迟也会有差异,如果只在同步玩家左右移动的操作数据,那么过一段时间之后,不同客户端的小怪物位置可能会不一样,因此每隔一段时间还是需要再同步一次小怪物的位置、速度和加速度数据:

cc.Class({

    onLoad: function () {

        // ...

        setInterval(() => {

            mvs.engine.sendEvent(JSON.stringify({

                action: GLB.PLAYER_POSITION_EVENT,

                x: this.node.x,

                xSpeed: this.xSpeed,

                accLeft: this.accLeft,

                accRight: this.accRight,

                ts: new Date().getTime()

            }));

        }, 200);

        // ..

    }

    // ...

})

// 文件路径:assets\scripts\Player1.js

cc.Class({

    sendEventNotify: function (info) {

        if (/* ... */) {

            // ...

        } else if (info.cpProto.indexOf(GLB.PLAYER_POSITION_EVENT) >= 0) {

            // 收到其他玩家的位置速度加速度信息,根据消息中的值更新状态

            this.receiveCountValue++;

            this.receiveCount.string = "receive msg count: " + this.receiveCountValue;

            var cpProto = JSON.parse(info.cpProto);

            var player = this.getPlayerByUserId(info.srcUserId);

            if (player) {

                player.node.x = cpProto.x;

                player.xSpeed = cpProto.xSpeed;

                player.accLeft = cpProto.accLeft;

                player.accRight = cpProto.accRight;

            }

            // ...

        } /* 更多else if条件 */

    },

    // ...

})

// 文件路径:assets\scripts\Game.js

最终效果如下:

搞定。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些阅读 2,027评论 0 2
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,717评论 2 17
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,054评论 1 10
  • 今天的动态静心鼻呼还是会卡在上颚,只是比昨天稍稍好些,发疯的环节喉咙发痒比较严重,好几次恶心呕吐(没吐出来),...
    陈玫瑰7W6阅读 279评论 0 4
  • 昨天,小A又来和我抱怨了。 “你说那谁谁谁什么意思嘛!平常不是都一副有事找我,好说好说的样子么?怎么一到关键情况立...
    送你一团火阅读 840评论 0 1