#推荐
Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

2024-10-03 5,577

连接到游戏服务器

  1. 在客户端文件 GameNetMgr.js 中,connectGameServer 方法会执行与游戏服务器的连接操作,并且向服务器发送一个 “login” 指令。
var sd = {
                token:data.token,
                roomid:data.roomid,
                time:data.time,
                sign:data.sign,
            };

2.服务器端的 socket_service.js 文件中,会监听并捕捉到 login 事件。


 
//启动服务
exports.start = function(conf,mgr){
	config = conf;
	
	var httpServer = require('http').createServer(app);
	io = require('socket.io')(httpServer);
	httpServer.listen(config.CLIENT_PORT);
	//监听是否有客户端连接进来,如果有客户连接进来就会调用这个函数
	//为这个客户创建一个socket通道
	io.sockets.on('connection',function(socket){
		//监听一个事件,然后data,客户端发送过来的数据
		//数据使用的是json协议的编码,来进行数据交换;底层发送json文本;
		socket.on('login',function(data){
			//转成json格式数据
			data = JSON.parse(data);
			if(socket.userId != null){
				//已经登陆过的就忽略
				return;
			}
			var token = data.token;
			var roomId = data.roomid;
			var time = data.time;
			var sign = data.sign;
 
			console.log(roomId);
			console.log(token);
			console.log(time);
			console.log(sign);
 
			
			//检查参数合法性
			if(token == null || roomId == null || sign == null || time == null){
				console.log(1);
				socket.emit('login_result',{errcode:1,errmsg:"invalid parameters"});
				return;
			}
			
			//检查参数是否被篡改
			var md5 = crypto.md5(roomId + token + time + config.ROOM_PRI_KEY);
			if(md5 != sign){
				console.log(2);
				//emit发送一个事件
				socket.emit('login_result',{errcode:2,errmsg:"login failed. invalid sign!"});
				return;
			}
			
			//检查token是否有效
			if(tokenMgr.isTokenValid(token)==false){
				console.log(3);
				socket.emit('login_result',{errcode:3,errmsg:"token out of time."});
				return;
			}
			
			//检查房间合法性
			var userId = tokenMgr.getUserID(token);
			var roomId = roomMgr.getUserRoom(userId);
 
			userMgr.bind(userId,socket);
			socket.userId = userId;
 
			//返回房间信息
			var roomInfo = roomMgr.getRoom(roomId);
			
			var seatIndex = roomMgr.getUserSeat(userId);
			roomInfo.seats[seatIndex].ip = socket.handshake.address;
 
			var userData = null;
			var seats = [];
			for(var i = 0; i < roomInfo.seats.length; ++i){
				var rs = roomInfo.seats[i];
				var online = false;
				if(rs.userId > 0){
					online = userMgr.isOnline(rs.userId);
				}
 
				seats.push({
					userid:rs.userId,
					ip:rs.ip,
					score:rs.score,
					name:rs.name,
					online:online,
					ready:rs.ready,
					seatindex:i
				});
 
				if(userId == rs.userId){
					userData = seats[i];
				}
			}
 
			//通知前端
			var ret = {
				errcode:0,
				errmsg:"ok",
				data:{
					roomid:roomInfo.id,
					conf:roomInfo.conf,
					numofgames:roomInfo.numOfGames,
					seats:seats
				}
			};
			socket.emit('login_result',ret);
 
			//通知其它客户端
			userMgr.broacastInRoom('new_user_comes_push',userData,userId);
			
			socket.gameMgr = roomInfo.gameMgr;
 
			//玩家上线,强制设置为TRUE
			socket.gameMgr.setReady(userId);
 
			socket.emit('login_finished');
 
			if(roomInfo.dr != null){
				var dr = roomInfo.dr;
				var ramaingTime = (dr.endTime - Date.now()) / 1000;
				var data = {
					time:ramaingTime,
					states:dr.states
				}
				userMgr.sendMsg(userId,'dissolve_notice_push',data);	
			}
		});

(1)验证传入参数的有效性,并根据 token 获取 uid,从而建立 uidsocket 之间的映射关系。

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

userMgr.bind(userId,socket);
socket.userId = userId;

2.服务器端的 socket_service.js 文件中,会监听并处理 login 事件。

var roomId = roomMgr.getUserRoom(userId);

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

(3)设置当前座位对应的 IP 地址配置。

var roomInfo = roomMgr.getRoom(roomId);

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

(4)遍历 roomInfo 中的所有座位,将每个座位的信息数据进行整合打包。

       if(userId == rs.userId){
					userData = seats[i];
				}

(5)通过 login_result 事件将结果发送给刚连接的客户端。

//通知前端
			var ret = {
				errcode:0,
				errmsg:"ok",
				data:{
					roomid:roomInfo.id,
					conf:roomInfo.conf,
					numofgames:roomInfo.numOfGames,
					seats:seats
				}
			};
			socket.emit('login_result',ret);

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

确保数据保存到全局的GameNetMgr里面

(6) 将该用户的加入消息广播给其他用户,使他们能够看到该用户已成功进入。

//通知其它客户端
			userMgr.broacastInRoom('new_user_comes_push',userData,userId);

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

(7) 通知玩家当前有人上线,如果玩家人数达到 4 人,则启动新一局游戏。

socket.gameMgr = roomInfo.gameMgr;

//玩家上线,强制设置为TRUE
socket.gameMgr.setReady(userId);

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

麻将场景客户端

  1. mjgame 场景上绑定了两个脚本文件,分别是 MJGame.jsMahjongMgr.js

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

注:新框架中新增了 Alert.js 脚本,现在共有 3 个脚本文件。

  1. MJGame.jsonLoad 函数中:加入了其他功能模块的一些组件。
onLoad: function () {
        cc.vv.utils.setFitSreenMode();
        this.addComponent("NoticeTip");
        this.addComponent("GameOver");
        this.addComponent("DingQue");
        this.addComponent("PengGangs");
        this.addComponent("MJRoom");
        this.addComponent("TimePointer");
        this.addComponent("GameResult");
        this.addComponent("Chat");
        this.addComponent("Folds");
        this.addComponent("ReplayCtrl");
        this.addComponent("PopupMgr");
        this.addComponent("HuanSanZhang");
        this.addComponent("ReConnect");
        this.addComponent("Voice");
        this.addComponent("UserInfoShow");
        this.addComponent("Status");
        
        this.initView();
        this.initEventHandlers();
        
        this.gameRoot.active = false;
        this.prepareRoot.active = true;
        this.initWanfaLabel();
        this.onGameBeign();
        cc.vv.audioMgr.playBGM("bgFight.mp3");
        cc.vv.utils.addEscEvent(this.node);
    },

3.initView:用于初始化场景,并隐藏多余的元素。

initView:function(){
        
        //搜索需要的子节点
        var gameChild = this.node.getChildByName("game");
        
        this._mjcount = gameChild.getChildByName('mjcount').getComponent(cc.Label);
        this._mjcount.string = "剩余" + cc.vv.gameNetMgr.numOfMJ + "张";
        this._gamecount = gameChild.getChildByName('gamecount').getComponent(cc.Label);
        this._gamecount.string = "" + cc.vv.gameNetMgr.numOfGames + "/" + cc.vv.gameNetMgr.maxNumOfGames + "局";
 
        var myselfChild = gameChild.getChildByName("myself");
        var myholds = myselfChild.getChildByName("holds");
 
        this._chupaidrag = gameChild.getChildByName('chupaidrag');
        this._chupaidrag.active = false;
        
        for(var i = 0; i < myholds.children.length; ++i){
            var sprite = myholds.children[i].getComponent(cc.Sprite);
            this._myMJArr.push(sprite);
            sprite.spriteFrame = null;
            this.initDragStuffs(sprite.node);
        }
        
        //var realwidth = this.node.width;
        //myholds.scaleX *= realwidth/1280;
        //myholds.scaleY *= realwidth/1280;  
        
        var sides = ["myself","right","up","left"];
        for(var i = 0; i < sides.length; ++i){
            var side = sides[i];
            
            var sideChild = gameChild.getChildByName(side);
            this._hupaiTips.push(sideChild.getChildByName("HuPai"));
            this._hupaiLists.push(sideChild.getChildByName("hupailist"));
            this._playEfxs.push(sideChild.getChildByName("play_efx").getComponent(cc.Animation));
            this._chupaiSprite.push(sideChild.getChildByName("ChuPai").children[0].getComponent(cc.Sprite));
            
            var opt = sideChild.getChildByName("opt");
            opt.active = false;
            var sprite = opt.getChildByName("pai").getComponent(cc.Sprite);
            var data = {
                node:opt,
                sprite:sprite
            };
            this._opts.push(data);
        }
        
        var opts = gameChild.getChildByName("ops");
        this._options = opts;
        this.hideOptions();
        this.hideChupai();
    },

4.监听由网络传递过来的转换事件。

 initEventHandlers:function(){
        cc.vv.gameNetMgr.dataEventHandler = this.node;
        
        //初始化事件监听器
        var self = this;
        
        this.node.on('game_holds',function(data){
           self.initMahjongs();
           self.checkQueYiMen();
        });
        
        this.node.on('game_begin',function(data){
            self.onGameBeign();
            //第一把开局,要提示
            if(cc.vv.gameNetMgr.numOfGames == 1){
                self.checkIp();
            }
        });
        
        this.node.on('check_ip',function(data){
            self.checkIp();
        });
        
        this.node.on('game_sync',function(data){
            self.onGameBeign();
            self.checkIp();
        });
        
        this.node.on('game_chupai',function(data){
            self.hideChupai();
            self.checkQueYiMen();
            if(data.last != cc.vv.gameNetMgr.seatIndex){
                self.initMopai(data.last,null);   
            }
            if(!cc.vv.replayMgr.isReplay() && data.turn != cc.vv.gameNetMgr.seatIndex){
                self.initMopai(data.turn,-1);
            }
        });
        
        this.node.on('game_mopai',function(data){
            self.hideChupai();
            var pai = data.pai;
            var localIndex = cc.vv.gameNetMgr.getLocalIndex(data.seatIndex);
            if(localIndex == 0){
                var index = 13;
                var sprite = self._myMJArr[index];
                self.setSpriteFrameByMJID("M_",sprite,pai,index);
                sprite.node.mjId = pai;                
            }
            else if(cc.vv.replayMgr.isReplay()){
                self.initMopai(data.seatIndex,pai);
            }
        });
        
        this.node.on('game_action',function(data){
            self.showAction(data);
        });
        
        this.node.on('hupai',function(data){
            //如果不是玩家自己,则将玩家的牌都放倒
            var seatIndex = data.seatindex;
            var localIndex = cc.vv.gameNetMgr.getLocalIndex(seatIndex);
            var hupai = self._hupaiTips[localIndex];
            hupai.active = true;
            
            if(localIndex == 0){
                self.hideOptions();
            }
            var seatData = cc.vv.gameNetMgr.seats[seatIndex];
            seatData.hued = true;
            if(cc.vv.gameNetMgr.conf.type == "xlch"){
                hupai.getChildByName("sprHu").active = true;
                hupai.getChildByName("sprZimo").active = false;
                self.initHupai(localIndex,data.hupai);
                if(data.iszimo){
                    if(seatData.seatindex == cc.vv.gameNetMgr.seatIndex){
                        seatData.holds.pop();
                        self.initMahjongs();                
                    }
                    else{
                        self.initOtherMahjongs(seatData);
                    }
                } 
            }
            else{
                hupai.getChildByName("sprHu").active = !data.iszimo;
                hupai.getChildByName("sprZimo").active = data.iszimo;
                
                if(!(data.iszimo && localIndex==0))
                {
                    //if(cc.vv.replayMgr.isReplay() == false && localIndex != 0){
                    //    self.initEmptySprites(seatIndex);                
                    //}
                    self.initMopai(seatIndex,data.hupai);
                }                                         
            }
            
            if(cc.vv.replayMgr.isReplay() == true && cc.vv.gameNetMgr.conf.type != "xlch"){
                var opt = self._opts[localIndex];
                opt.node.active = true;
                opt.sprite.spriteFrame = cc.vv.mahjongmgr.getSpriteFrameByMJID("M_",data.hupai);                
            }
            
            if(data.iszimo){
                self.playEfx(localIndex,"play_zimo");    
            }
            else{
                self.playEfx(localIndex,"play_hu");
            }
            
            cc.vv.audioMgr.playSFX("nv/hu.mp3");
        });
        
        this.node.on('mj_count',function(data){
            self._mjcount.string = "剩余" + cc.vv.gameNetMgr.numOfMJ + "张";
        });
        
        this.node.on('game_num',function(data){
            self._gamecount.string = "" + cc.vv.gameNetMgr.numOfGames + "/" + cc.vv.gameNetMgr.maxNumOfGames + "局";
        });
        
        this.node.on('game_over',function(data){
            self.gameRoot.active = false;
            self.prepareRoot.active = true;
        });
        
        
        this.node.on('game_chupai_notify',function(data){
            self.hideChupai();
            var seatData = data.seatData;
            //如果是自己,则刷新手牌
            if(seatData.seatindex == cc.vv.gameNetMgr.seatIndex){
                self.initMahjongs();                
            }
            else{
                self.initOtherMahjongs(seatData);
            }
            self.showChupai();
            var audioUrl = cc.vv.mahjongmgr.getAudioURLByMJID(data.pai);
            cc.vv.audioMgr.playSFX(audioUrl);
        });
        
        this.node.on('guo_notify',function(data){
            self.hideChupai();
            self.hideOptions();
            var seatData = data;
            //如果是自己,则刷新手牌
            if(seatData.seatindex == cc.vv.gameNetMgr.seatIndex){
                self.initMahjongs();                
            }
            cc.vv.audioMgr.playSFX("give.mp3");
        });
        
        this.node.on('guo_result',function(data){
            self.hideOptions();
        });
        
        this.node.on('game_dingque_finish',function(data){
            self.initMahjongs();
        });
        
        this.node.on('peng_notify',function(data){    
            self.hideChupai();
            
            var seatData = data;
            if(seatData.seatindex == cc.vv.gameNetMgr.seatIndex){
                self.initMahjongs();                
            }
            else{
                self.initOtherMahjongs(seatData);
            }
            var localIndex = self.getLocalIndex(seatData.seatindex);
            self.playEfx(localIndex,"play_peng");
            cc.vv.audioMgr.playSFX("nv/peng.mp3");
            self.hideOptions();
        });
        
        this.node.on('gang_notify',function(data){
            self.hideChupai();
            var seatData = data.seatData;
            var gangtype = data.gangtype;
            if(seatData.seatindex == cc.vv.gameNetMgr.seatIndex){
                self.initMahjongs();                
            }
            else{
                self.initOtherMahjongs(seatData);
            }
            
            var localIndex = self.getLocalIndex(seatData.seatindex);
            if(gangtype == "wangang"){
                self.playEfx(localIndex,"play_guafeng");
                cc.vv.audioMgr.playSFX("guafeng.mp3");
            }
            else{
                self.playEfx(localIndex,"play_xiayu");
                cc.vv.audioMgr.playSFX("rain.mp3");
            }
        });
        
        this.node.on("hangang_notify",function(data){
            var localIndex = self.getLocalIndex(data);
            self.playEfx(localIndex,"play_gang");
            cc.vv.audioMgr.playSFX("nv/gang.mp3");
            self.hideOptions();
        });
 
        this.node.on('login_result', function () {
            self.gameRoot.active = false;
            self.prepareRoot.active = true;
            console.log('login_result');
        });
    },

5.MJRoom.js:用于处理客户端游戏逻辑的代码组件。

cc.Class({
    extends: cc.Component,
 
    properties: {
        lblRoomNo:{
            default:null,
            type:cc.Label
        },
        // foo: {
        //    default: null,
        //    url: cc.Texture2D,  // optional, default is typeof default
        //    serializable: true, // optional, default is true
        //    visible: true,      // optional, default is true
        //    displayName: 'Foo', // optional
        //    readonly: false,    // optional, default is false
        // },
        // ...
        _seats:[],
        _seats2:[],
        _timeLabel:null,
        _voiceMsgQueue:[],
        _lastPlayingSeat:null,
        _playingSeat:null,
        _lastPlayTime:null,
    },
 
    // use this for initialization
    onLoad: function () {
        if(cc.vv == null){
            return;
        }
        
        this.initView();
        this.initSeats();
        this.initEventHandlers();
    },
    
    initView:function(){
        var prepare = this.node.getChildByName("prepare");
        var seats = prepare.getChildByName("seats");
        for(var i = 0; i < seats.children.length; ++i){
            this._seats.push(seats.children[i].getComponent("Seat"));
        }
        
        this.refreshBtns();
        
        this.lblRoomNo = cc.find("Canvas/infobar/Z_room_txt/New Label").getComponent(cc.Label);
        this._timeLabel = cc.find("Canvas/infobar/time").getComponent(cc.Label);
        this.lblRoomNo.string = cc.vv.gameNetMgr.roomId;
        var gameChild = this.node.getChildByName("game");
        var sides = ["myself","right","up","left"];
        for(var i = 0; i < sides.length; ++i){
            var sideNode = gameChild.getChildByName(sides[i]);
            var seat = sideNode.getChildByName("seat");
            this._seats2.push(seat.getComponent("Seat"));
        }
        
        var btnWechat = cc.find("Canvas/prepare/btnWeichat");
        if(btnWechat){
            cc.vv.utils.addClickEvent(btnWechat,this.node,"MJRoom","onBtnWeichatClicked");
        }
        
        
        var titles = cc.find("Canvas/typeTitle");
        for(var i = 0; i < titles.children.length; ++i){
            titles.children[i].active = false;
        }
        
        if(cc.vv.gameNetMgr.conf){
            var type = cc.vv.gameNetMgr.conf.type;
            if(type == null || type == ""){
                type = "xzdd";
            }
            
            titles.getChildByName(type).active = true;   
        }
    },
    
    refreshBtns:function(){
        var prepare = this.node.getChildByName("prepare");
        var btnExit = prepare.getChildByName("btnExit");
        var btnDispress = prepare.getChildByName("btnDissolve");
        var btnWeichat = prepare.getChildByName("btnWeichat");
        var isIdle = cc.vv.gameNetMgr.numOfGames == 0;
        
        btnExit.active = !cc.vv.gameNetMgr.isOwner() && isIdle;
        btnDispress.active = cc.vv.gameNetMgr.isOwner() && isIdle;
        
        btnWeichat.active = isIdle;
    },
    
    initEventHandlers:function(){
        var self = this;
        this.node.on('new_user',function(data){
            self.initSingleSeat(data);
        });
        
        this.node.on('user_state_changed',function(data){
            self.initSingleSeat(data);
        });
        
        this.node.on('game_begin',function(data){
            self.refreshBtns();
            self.initSeats();
        });
        
        this.node.on('game_num',function(data){
            self.refreshBtns();
        });
 
        this.node.on('game_huanpai',function(data){
            for(var i in self._seats2){
                self._seats2[i].refreshXuanPaiState();    
            }
        });
                
        this.node.on('huanpai_notify',function(data){
            var idx = data.seatindex;
            var localIdx = cc.vv.gameNetMgr.getLocalIndex(idx);
            self._seats2[localIdx].refreshXuanPaiState();
        });
        
        this.node.on('game_huanpai_over',function(data){
            for(var i in self._seats2){
                self._seats2[i].refreshXuanPaiState();    
            }
        });
        
        this.node.on('voice_msg',function(data){
            self._voiceMsgQueue.push(data);
            self.playVoice();
        });
        
        this.node.on('chat_push',function(data){
            var idx = cc.vv.gameNetMgr.getSeatIndexByID(data.sender);
            var localIdx = cc.vv.gameNetMgr.getLocalIndex(idx);
            self._seats[localIdx].chat(data.content);
            self._seats2[localIdx].chat(data.content);
        });
        
        this.node.on('quick_chat_push',function(data){
            var idx = cc.vv.gameNetMgr.getSeatIndexByID(data.sender);
            var localIdx = cc.vv.gameNetMgr.getLocalIndex(idx);
            
            var index = data.content;
            var info = cc.vv.chat.getQuickChatInfo(index);
            self._seats[localIdx].chat(info.content);
            self._seats2[localIdx].chat(info.content);
            
            cc.vv.audioMgr.playSFX(info.sound);
        });
        
        this.node.on('emoji_push',function(data){
            var idx = cc.vv.gameNetMgr.getSeatIndexByID(data.sender);
            var localIdx = cc.vv.gameNetMgr.getLocalIndex(idx);
            console.log(data);
            self._seats[localIdx].emoji(data.content);
            self._seats2[localIdx].emoji(data.content);
        });
    },
    
    initSeats:function(){
        var seats = cc.vv.gameNetMgr.seats;
        for(var i = 0; i < seats.length; ++i){
            this.initSingleSeat(seats[i]);
        }
    },
    
    initSingleSeat:function(seat){
        var index = cc.vv.gameNetMgr.getLocalIndex(seat.seatindex);
        var isOffline = !seat.online;
        var isZhuang = seat.seatindex == cc.vv.gameNetMgr.button;
        
        console.log("isOffline:" + isOffline);
        
        this._seats[index].setInfo(seat.name,seat.score);
        this._seats[index].setReady(seat.ready);
        this._seats[index].setOffline(isOffline);
        this._seats[index].setID(seat.userid);
        this._seats[index].voiceMsg(false);
        
        this._seats2[index].setInfo(seat.name,seat.score);
        this._seats2[index].setZhuang(isZhuang);
        this._seats2[index].setOffline(isOffline);
        this._seats2[index].setID(seat.userid);
        this._seats2[index].voiceMsg(false);
        this._seats2[index].refreshXuanPaiState();
    },
    
    onBtnSettingsClicked:function(){
        cc.vv.popupMgr.showSettings();   
    },
 
    onBtnBackClicked:function(){
        cc.vv.alert.show("返回大厅","返回大厅房间仍会保留,快去邀请大伙来玩吧!",function(){
            cc.vv.wc.show('正在返回游戏大厅');
            cc.director.loadScene("hall");    
        },true);
    },
    
    onBtnChatClicked:function(){
        
    },
    
    onBtnWeichatClicked:function(){
        var title = "<血战到底>";
        if(cc.vv.gameNetMgr.conf.type == "xlch"){
            var title = "<血流成河>";
        }
        cc.vv.anysdkMgr.share("天天麻将" + title,"房号:" + cc.vv.gameNetMgr.roomId + " 玩法:" + cc.vv.gameNetMgr.getWanfa());
    },
    
    onBtnDissolveClicked:function(){
        cc.vv.alert.show("解散房间","解散房间不扣房卡,是否确定解散?",function(){
            cc.vv.net.send("dispress");    
        },true);
    },
    
    onBtnExit:function(){
        cc.vv.net.send("exit");
    },
    
    playVoice:function(){
        if(this._playingSeat == null && this._voiceMsgQueue.length){
            console.log("playVoice2");
            var data = this._voiceMsgQueue.shift();
            var idx = cc.vv.gameNetMgr.getSeatIndexByID(data.sender);
            var localIndex = cc.vv.gameNetMgr.getLocalIndex(idx);
            this._playingSeat = localIndex;
            this._seats[localIndex].voiceMsg(true);
            this._seats2[localIndex].voiceMsg(true);
            
            var msgInfo = JSON.parse(data.content);
            
            var msgfile = "voicemsg.amr";
            console.log(msgInfo.msg.length);
            cc.vv.voiceMgr.writeVoice(msgfile,msgInfo.msg);
            cc.vv.voiceMgr.play(msgfile);
            this._lastPlayTime = Date.now() + msgInfo.time;
        }
    },
    
    // called every frame, uncomment this function to activate update callback
    update: function (dt) {
        var minutes = Math.floor(Date.now()/1000/60);
        if(this._lastMinute != minutes){
            this._lastMinute = minutes;
            var date = new Date();
            var h = date.getHours();
            h = h < 10? "0"+h:h;
            
            var m = date.getMinutes();
            m = m < 10? "0"+m:m;
            this._timeLabel.string = "" + h + ":" + m;             
        }
        
        
        if(this._lastPlayTime != null){
            if(Date.now() > this._lastPlayTime + 200){
                this.onPlayerOver();
                this._lastPlayTime = null;    
            }
        }
        else{
            this.playVoice();
        }
    },
    
        
    onPlayerOver:function(){
        cc.vv.audioMgr.resumeAll();
        console.log("onPlayCallback:" + this._playingSeat);
        var localIndex = this._playingSeat;
        this._playingSeat = null;
        this._seats[localIndex].voiceMsg(false);
        this._seats2[localIndex].voiceMsg(false);
    },
    
    onDestroy:function(){
        cc.vv.voiceMgr.stop();
//        cc.vv.voiceMgr.onPlayCallback = null;
    }
});

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

(1) 根据服务器返回的座位数据,对玩家的座位进行初始化设置。

this.initView();
        this.initSeats();
        this.initEventHandlers();

(2)监听从 GameNetMgr.js 传递过来的事件。

 initEventHandlers:function(){
        ...
        this.node.on('game_begin',function(data){
            self.refreshBtns();
            self.initSeats();
        });
        
        this.node.on('game_num',function(data){
            self.refreshBtns();
        });
 
        this.node.on('game_huanpai',function(data){
            for(var i in self._seats2){
                self._seats2[i].refreshXuanPaiState();    
            }
        });
        ...

(3)当有新玩家加入时,监听并处理 new_user 事件。

initEventHandlers:function(){
        var self = this;
        this.node.on('new_user',function(data){
            self.initSingleSeat(data);
        });

定缺

  1. 麻将场景中:Canvas –> Game –> dingque 节点。

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

2.DingQue.js 通过 this.addComponent("钉阙") 挂载到组件中;(在 MJGame.js 中添加)。

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

3.onQueYiMenClicked:当按钮被点击时,将定缺的类型发送至服务器。


//---------定缺按钮点击响应时间,----------
onQueYiMenClicked:function(event){
        var type = 0;
        if(event.target.name == "tong"){
            type = 0;
        }
        else if(event.target.name == "tiao"){
            type = 1;
        }
        else if(event.target.name == "wan"){
            type = 2;
        }
        
        for(var i = 0; i < this.selected.length; ++i){
            this.selected[i].active = false;
        }  
        this.selected[type].active = true;
        cc.vv.gameNetMgr.dingque = type;
        cc.vv.net.send("dingque",type);
        
        //this.setInteractable(false);
    },
//---------定缺消息发送服务器cc.vv.net.send("dingque",type);----------

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

self.dispatchEvent('game_dingque'):触发 game_dingque 事件进行广播。

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

4.服务器完成定缺操作后,会广播 game_dingque_finish_push 事件。


//-------------------服务器socket_service收到定缺消息socket.on('dingque',function(data){----------------------------
 
	socket.gameMgr.huanSanZhang(socket.userId,p1,p2,p3);
		});
 
		//定缺
		socket.on('dingque',function(data){
			if(socket.userId == null){
				return;
			}
			var que = data;
			socket.gameMgr.dingQue(socket.userId,que);
		});
 
		//出牌
		socket.on('chupai',function(data){
			if(socket.userId == null){
				return;
			}
			var pai = data;
			socket.gameMgr.chuPai(socket.userId,pai);
		});
		
		//碰
		socket.on('peng',function(data){
			if(socket.userId == null){
				return;
			}
			socket.gameMgr.peng(socket.userId);
		});
		
		//杠
		socket.on('gang',function(data){
 
//--------------------socket.gameMgr.dingQue(socket.userId,que)进行处理---------------------------

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

5.如果四名玩家都已完成定缺操作,则发送 game_playing_push 消息,正式开始游戏。

exports.dingQue = function(userId,type){
    var seatData = gameSeatsOfUsers[userId];
    if(seatData == null){
        console.log("can't find user game data.");
        return;
    }
 
    var game = seatData.game;
    if(game.state != "dingque"){
        console.log("can't recv dingQue when game.state == " + game.state);
        return;
    }
 
    if(seatData.que < 0){
        game.numOfQue++;    
    }
 
    seatData.que = type;
    
 
    //检查玩家可以做的动作
    //如果4个人都定缺了,通知庄家出牌
    if(game.numOfQue == 4){
        construct_game_base_info(game);
        var arr = [1,1,1,1];
        for(var i = 0; i < game.gameSeats.length; ++i){
            arr[i] = game.gameSeats[i].que;
        }
        userMgr.broacastInRoom('game_dingque_finish_push',arr,seatData.userId,true);
        userMgr.broacastInRoom('game_playing_push',null,seatData.userId,true);
 
        //进行听牌检查
        for(var i = 0; i < game.gameSeats.length; ++i){
            var duoyu = -1;
            var gs = game.gameSeats[i];
            if(gs.holds.length == 14){
                duoyu = gs.holds.pop();
                gs.countMap[duoyu] -= 1;
            }
            checkCanTingPai(game,gs);
            if(duoyu >= 0){
                gs.holds.push(duoyu);
                gs.countMap[duoyu] ++;
            }
        }
        
        var turnSeat = game.gameSeats[game.turn];
        game.state = "playing";
        //通知玩家出牌方
        turnSeat.canChuPai = true;
        userMgr.broacastInRoom('game_chupai_push',turnSeat.userId,turnSeat.userId,true);
        //检查是否可以暗杠或者胡
        //直杠
        checkCanAnGang(game,turnSeat);
        //检查胡 用最后一张来检查
        checkCanHu(game,turnSeat,turnSeat.holds[turnSeat.holds.length - 1]);
        //通知前端
        sendOperations(game,turnSeat,game.chuPai);
    }
    else{
        userMgr.broacastInRoom('game_dingque_notify_push',seatData.userId,seatData.userId,true);
    }
};

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

客户端接收到服务器的 game_dingque_finish_push 后,广播 self.dispatchEvent('game_dingque_finish', data);接着 DingQue.js 会处理该事件,通过 this.node.on('game_dingque_finish', function()) 来响应。

客户端还会接收到服务器通过 userMgr.broadcastInRoom('game_playing_push', null, seatData.userId, true) 发送的消息;GameNetMgr.js 随即监听并处理 game_playing_push 事件,之后正式开始游戏。

 cc.vv.net.addHandler("game_playing_push",function(data){
            console.log('game_playing_push'); 
            self.gamestate = "playing"; 
            self.dispatchEvent('game_playing');
        });

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

怎么去操作游戏

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

1.选择当前轮到的玩家。

2.广播出牌事件:game_chupai_push

3.对牌的操作进行检查,判断后续玩家是否能够进行胡牌、杠等操作。

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

4: 玩家出牌:

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

4.MJGame.js –> onMJClicked:通过 cc.vv.net.send('chupai', mjId) 发送出牌消息。

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

服务器接收消息后进行处理:

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

(1) 检查玩家是否具有出牌的权限,并验证出牌的合法性。
Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏
exports.chuPai = function(userId,pai){
 
    pai = Number.parseInt(pai);
    var seatData = gameSeatsOfUsers[userId];
    if(seatData == null){
        console.log("can't find user game data.");
        return;
    }
 
    var game = seatData.game;
    var seatIndex = seatData.seatIndex;
    //如果不该他出,则忽略
    if(game.turn != seatData.seatIndex){
        console.log("not your turn.");
        return;
    }
 
    if(seatData.hued){
        console.log('you have already hued. no kidding plz.');
        return;
    }
 
    if(seatData.canChuPai == false){
        console.log('no need chupai.');
        return;
    }
 
    if(hasOperations(seatData)){
        console.log('plz guo before you chupai.');
        return;
    }
 
    //从此人牌中扣除
    var index = seatData.holds.indexOf(pai);
    if(index == -1){
        console.log("holds:" + seatData.holds);
        console.log("can't find mj." + pai);
        return;
    }
    
    seatData.canChuPai = false;
    game.chupaiCnt ++;
    seatData.guoHuFan = -1;
    
    seatData.holds.splice(index,1);
    seatData.countMap[pai] --;
    game.chuPai = pai;
    recordGameAction(game,seatData.seatIndex,ACTION_CHUPAI,pai);
    checkCanTingPai(game,seatData);
   
    userMgr.broacastInRoom('game_chupai_notify_push',{userId:seatData.userId,pai:pai},seatData.userId,true);
 
    //如果出的牌可以胡,则算过胡
    if(seatData.tingMap[game.chuPai]){
        seatData.guoHuFan = seatData.tingMap[game.chuPai].fan;
    }
    
    //检查是否有人要胡,要碰 要杠
    var hasActions = false;
    for(var i = 0; i < game.gameSeats.length; ++i){
        //玩家自己不检查
        if(game.turn == i){
            continue;
        }
        var ddd = game.gameSeats[i];
        //已经和牌的不再检查
        if(ddd.hued){
            continue;
        }
 
        checkCanHu(game,ddd,pai);
        if(seatData.lastFangGangSeat == -1){
            if(ddd.canHu && ddd.guoHuFan >= 0 && ddd.tingMap[pai].fan <= ddd.guoHuFan){
                console.log("ddd.guoHuFan:" + ddd.guoHuFan);
                ddd.canHu = false;
                userMgr.sendMsg(ddd.userId,'guohu_push');            
            }     
        }
        checkCanPeng(game,ddd,pai);
        checkCanDianGang(game,ddd,pai);
        if(hasOperations(ddd)){
            sendOperations(game,ddd,game.chuPai);
            hasActions = true;    
        }
    }
    
    //如果没有人有操作,则向下一家发牌,并通知他出牌
    if(!hasActions){
        setTimeout(function(){
            userMgr.broacastInRoom('guo_notify_push',{userId:seatData.userId,pai:game.chuPai},seatData.userId,true);
            seatData.folds.push(game.chuPai);
            game.chuPai = -1;
            moveToNextUser(game);
            doUserMoPai(game);
        },500);
    }
};

(2) 广播玩家的出牌通知给所有其他玩家。

userMgr.broacastInRoom('game_chupai_notify_push',{userId:seatData.userId,pai:pai},seatData.userId,true);

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

GameNetMgr.js 发送 game_chupai_notify 消息至 MJGame.js 进行处理

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

(3) 检查是否有玩家能够进行胡、碰或杠的操作

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

(4) 轮转至下一位玩家继续执行相应的操作。

            moveToNextUser(game);
            doUserMoPai(game);

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

(5)开启新一轮游戏操作:

Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏

 
//开始新的一局
exports.begin = function(roomId) {
    var roomInfo = roomMgr.getRoom(roomId);
    if(roomInfo == null){
        return;
    }
    var seats = roomInfo.seats;
 
    var game = {
        conf:roomInfo.conf,
        roomInfo:roomInfo,
        gameIndex:roomInfo.numOfGames,
 
        button:roomInfo.nextButton,
        mahjongs:new Array(108),
        currentIndex:0,
        gameSeats:new Array(4),
 
        numOfQue:0,
        turn:0,
        chuPai:-1,
        state:"idle",
        firstHupai:-1,
        yipaoduoxiang:-1,
        fangpaoshumu:-1,
        actionList:[],
        hupaiList:[],
        chupaiCnt:0,
    };
 
    roomInfo.numOfGames++;
 
    for(var i = 0; i < 4; ++i){
        var data = game.gameSeats[i] = {};
 
        data.game = game;
 
        data.seatIndex = i;
 
        data.userId = seats[i].userId;
        //持有的牌
        data.holds = [];
        //打出的牌
        data.folds = [];
        //暗杠的牌
        data.angangs = [];
        //点杠的牌
        data.diangangs = [];
        //弯杠的牌
        data.wangangs = [];
        //碰了的牌
        data.pengs = [];
        //缺一门
        data.que = -1;
 
        //换三张的牌
        data.huanpais = null;
 
        //玩家手上的牌的数目,用于快速判定碰杠
        data.countMap = {};
        //玩家听牌,用于快速判定胡了的番数
        data.tingMap = {};
        data.pattern = "";
 
        //是否可以杠
        data.canGang = false;
        //用于记录玩家可以杠的牌
        data.gangPai = [];
 
        //是否可以碰
        data.canPeng = false;
        //是否可以胡
        data.canHu = false;
        //是否可以出牌
        data.canChuPai = false;
 
        //如果guoHuFan >=0 表示处于过胡状态,
        //如果过胡状态,那么只能胡大于过胡番数的牌
        data.guoHuFan = -1;
 
        //是否胡了
        data.hued = false;
        //是否是自摸
        data.iszimo = false;
 
        data.isGangHu = false;
 
        //
        data.actions = [];
 
        data.fan = 0;
        data.score = 0;
        data.lastFangGangSeat = -1;
        
        //统计信息
        data.numZiMo = 0;
        data.numJiePao = 0;
        data.numDianPao = 0;
        data.numAnGang = 0;
        data.numMingGang = 0;
        data.numChaJiao = 0;
 
        gameSeatsOfUsers[data.userId] = data;
    }
    games[roomId] = game;
    //洗牌
    shuffle(game);
    //发牌
    deal(game);
 
    
 
    var numOfMJ = game.mahjongs.length - game.currentIndex;
    var huansanzhang = roomInfo.conf.hsz;
 
    for(var i = 0; i < seats.length; ++i){
        //开局时,通知前端必要的数据
        var s = seats[i];
        //通知玩家手牌
        userMgr.sendMsg(s.userId,'game_holds_push',game.gameSeats[i].holds);
        //通知还剩多少张牌
        userMgr.sendMsg(s.userId,'mj_count_push',numOfMJ);
        //通知还剩多少局
        userMgr.sendMsg(s.userId,'game_num_push',roomInfo.numOfGames);
        //通知游戏开始
        userMgr.sendMsg(s.userId,'game_begin_push',game.button);
 
        if(huansanzhang == true){
            game.state = "huanpai";
            //通知准备换牌
            userMgr.sendMsg(s.userId,'game_huanpai_push');
        }
        else{
            game.state = "dingque";
            //通知准备定缺
            userMgr.sendMsg(s.userId,'game_dingque_push');
        }
    }
};

好了今天这篇文章到这结束,希望对大家有所帮助

收藏 打赏

感谢您的支持,我会继续努力的!

打开USDT(trc-20)扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

Ts:本站所有内容均为互联网收集整理和网友上传。仅限于学习研究,请必须在24小时内删除。否则由此引发的法律纠纷及连带责任本站概不承担。

如侵犯到您的合法权益,请联系我们删除侵权资源!

韩仔技术 搭建教程 Cocos Creator麻将源代码开发教程第六篇——情怀麻将游戏 https://www.hanzijs.com/dajian/2816.html

相关文章

发表评论
暂无评论