大屏与手机互动cocos 2d游戏的后端设计与实现(基于GateWayWorker)

背景:这个项目,我只参与项目的一部分业务代码开放(摇一摇游戏业务),只是简单来使用封装好的一些类和方法,核心实现并不是我写的,并且核心代码设计思路并没有文档,当然代码中有些注释,他人也并没有太多时间给我好好讲讲他的设计思路。

目的:分析他人的代码,完全掌握他人的设计思路,抓住重点,提取好的思路,供之后类似的项目开放 做参考!

解读他人代码/分析代码 的方法

多维度分析代码:

一: 看代码组织结构,类的继承关系

二:程序的执行流程

三: 数据设计(redis 设计,gatewayWorker 的组 UID 设计)

四:从gatewayWorker 框架 用到的方法,统计都使用了哪些功能

五:还原当时的需求,遗忘的不清晰的需求,可以自己提供合理的需求

说明: 方法一二 是大多数人常用的分析他人代码的方法。包括我也是。但是仅仅使用这两种方法,还是会云里雾里的,原因在于这里边涉及需求和他人的设计思路。方法三:是最容易忽视的,包括我也是,最初看代码的时候,没想到这个维度。数据设计也是最能体现他人设计思路的。方法五中需求:要看情况分析,如果是我正在维护的开放的,那么猜测的需求最好求证明白,如果只是拿来学习,又没人给你讲,或者是上个公司的项目,这种情况就靠自己提供合理的需求的。即便是真的和当时的需求不一样也没关系,因为我的目的是学习他人整体设计思路,这些不重要的细节就不重要了。

从 三 和 五 的维度 来分析他人代码 能 看出 他人 写的不好的地方,甚至找出bug

学习目的/想要的成果

一: 代码的组织结构,类的继承关系。 如:是不是 用到了好的设计模式?

二: 从需求 到 数据设计,如:他抽象的是否合理,数据设计的是否合理

三: 从需求 到 技术选型:  选择的技术 是否合适?

四: 代码的规范: 是否规范,易懂,有学习的地方吗?

项目需求(包含业务 ,和 技术方面的)

业务方面的: 大屏端 展示游戏界面(分为 游戏进入界面,游戏进行界面,游戏结束展示排名界面),使用手机里边的微信扫码大屏端的二维码,关注公众号后,后端会推送一个游戏链接,点击链接即可加入游戏。游戏开始时,手机端可以进行操作,以赛车游戏来说,可以向左,向右,加速,减速控制大屏中自己控制的赛车。

后端的语言框架选择,要能支持 websocket 协议, 与其他项目的联系 使用 curl 方式

先看看目录结构:

大屏与手机互动cocos 2d游戏的后端设计与实现(基于GateWayWorker)_第1张图片

说明: 与从官网下载下来的gateWayWorker 相比目录上 只是多了一个Classes目录 ,其中Bases是基类,其他的都是子类,每一个文件对应一款游戏

那么如何加载class下面的这些类呢?可以参考下他的实现方法,代码如下:

Common/Events.php

    private static function _loadGameClass(){
        $fileDir = dirname(__FILE__) . '/Classes/';
        $classFiles = scandir($fileDir);
        foreach($classFiles AS $fKey => $fVal){
            if(strpos($fVal, '.php') !== false){
                require_once $fileDir . $fVal;
            }
        }
    }

有没有更好的方法来加载呢? 当然有了,使用vendor 的加载方法,或者自己实现一个遵循PSR-4规范的方法。

todo

那么如何 路由,也就是不同的游戏来了执行不同的 类和方法?

他的思路是: 通过 消息中的 game_name字段来 ,加载不同的类;消息中的 type 来执行不同的方法。//这个处理方式可以借鉴

代码:

Events.php

	public static function onMessage($client_id, $message)
	{
        self::_loadGameClass();

        // 处理json数据
        $messageData = json_decode($message, true);
        if(!$messageData){
            return ;
        }
        $gameName = (isset($messageData['game_name'])) ? ucfirst($messageData['game_name']) : 'Error';
        if(class_exists($gameName, false)){
            $gameName::setDefault($client_id, $messageData);
            $gameName::init($client_id, $messageData);
            $gameName::$messageData['type']($messageData);
        } else {
            self::onClose($client_id);
        }
	}

Bases.php 类分析:

数据设计

属性:

maxUserCount = 20   //最大用户数量  由子类初始化, 指的是可以同时参与游戏的最大玩家数量。

maxWaitUserCount = 200  //最大等待用户数量  

针对gateway 定义的组名:

Online[room_id]     //正在玩游戏的这个组  包括( 多个手机端)

RoomBird[room_id] //等待进入游戏的这个组。( 多个手机端)

clientUserGroup[room_id]               //包含所有的client 的这个组(不管是正在玩游戏的还是 等待玩游戏的 都要加入这个组)

针对gateway 定义的uid:

Screen[room_id] //大屏端 绑定的uid

redis 的使用:

key         类型               可能的值

gameState[room_id]          string             1   ;    2 ;             //游戏的状态: 0 游戏未开始/大屏断连接了  1 游戏在进人状态;  2游戏进行中; 3游戏结束/游戏正在显示排名

wxOpenIdOnline[room_id]  string            json串;   //游戏在线 openid  list

gameQueueSort[room_id]         int                  //号码牌计数 从1开始 ,大屏排 1,其他手机端 从 2开始, 依此 3, 4 。。。。

属性:

包含几类属性:

一: 大屏端 UID 名称  //值为 大屏端UID 名称

一: 各类 组名称 :// 值为 组名称

二: 各类 redis key 名称   // 值 为redis key 名称

三: 连接的 client_id

方法

init($client_id, $message)

//初始化静态属性

//获取API token

screen_login($message)   大屏登录

判断大屏是否在线 Gateway::isUidOnline

—如果已经在线  return  Gateway::sendToCurrentClient   “登录失败”

— 如果没有在线

—— 给该连接 绑上Screen[room_id]   Gateway::bindUid

—— return  Gateway::sendToCurrentClient   “登录成功“

close_client()  踢掉已在线 大屏端   实际上 游戏端的js 并没有用到

scoreAdd() 同步分数   由 大屏端 发给 某一个 手机端

判断 message 中的 client_id 是否在线 Gateway::isUidOnline

—   是: 发送 消息 给该 cliend_id, 告诉他 当前这一刻他的分数。  Gateway::sendToUid 

gameState()  设置游戏状态  大屏端发起:开始游戏 发一次state=1; 进入游戏场景再发一次 state=2; 进入结束页面 再发一次 state=3

// redis 操作 设置 游戏状态

//判断 state == 1 // 扫码进入阶段

// 是 

// —  操作redis 删除 wxOpenIdOnline[room_id] ;  清空  “Online[room_id]” 组 , 和 RoomBird[room_id] 组

// else state ==2  //游戏立刻开始

// —  没干啥 特别有意义的事

// 发送消息 给 大屏端 说 “游戏状态 已经设置好了

gameTime 将 大屏端的  时间  组发

将 时间 发给组“Online[room_id]”   Gateway::sendToGroup

login() 客户端登录

//获取游戏状态  从gameState[room_id]

//设置session

绑定UID  使用 open_ID   ;  Gateway::bindUid

将此client_id 加入到  组“clientUserGroup[room_id]”   GateWay::joinGroup

发送消息给当前用户 ,告诉他 登录成功 Gateway::sendToCurrentClient

判读用户openid 是否在线

即说明 客户端是 重连 操作redis  wxOpenIdOnline[room_id]   取出 当前用户的isdead type=reGame信息(包括 游戏状态,isDead, time())发送给 当前client

发送消息  给当前用户  Gateway::sendToCurrentClient  ;信息={type:gameState, state:redisGameState; time: time()}

peopleNUm 加入游戏

_setRoomGroup()           // 当前用户加入 要么 Online[room_id],要么 “RoomBird[room_id]” 组

//判断 如果 正在等待游戏的人数  > 0 or  游戏正在 进行中 or  游戏结束正在显示排名

—   redis 获取 queueSort (int类型)  更新 session  sort 信息;  redis操作 queueSort 自增 1 发送消息给当前手机端“当前的排队数量”

// if  如果 没有玩家正在排队(roomCnt=0)  && 游戏正在进人状态

 —  :发送消息is Ok 当前client_id; 发送消息 ready 给当前大屏端。

gameFinish 游戏结束:由大屏端发送的消息

// curl 获取优惠券。。。 不关心此部分逻辑

// message 中获取 to_client_id ;

//判断 to_client_id  是否 在线

//— 发送 消息 给此 用户 告诉他“他的 排名,分数,获得的奖项等” 

//发送 消息给 大屏端,告诉他  这个(to_client_id)  用户获得的 奖项。

gameDead  用户死掉  由大屏端 发起的消息

//判读此 message 中指定的  to_client_id  是否在线 Gateway::isUidOnline

//—  是: redis 操作 wxOpenIdOnline[room_id]   ;取出信息并 json_decode 下 将该用户对应的 isDead 标记为 “死亡” 含义;  代码中isDead=0 代表死亡含义。

//         发送消息给这个用户(to_client_id)   告诉他,他已经死亡了。

addNewPlayer()  从等待队列 拉一个用户   由大屏端发起;   只有 364,367游戏会发起

//获取“排队等待的/RoomBird[room_id]  ”组 内的 玩家的总数: Gateway::getClientCountByGroup

//判断 如果是 玩家总数 大于  0

//—是: 获取 “RoomBird[room_id]” 组内 所有成员的详细信息(指的是存在session里的)

//—   按照 每个成员中的 信息中的 sort  对这些成员信息 进行排序

//—   循环遍历 成员信息

//—    —  对第一个 成员 离开 等待组,加入 在线游戏组。给他发送消息,你进入游戏了。给 大屏发消息 这个人 成功拉进来了

//—   —  对第二个 及以后的成员 ,给他发消息  你前面还有 i 个人 在排队。

op 手机端 发送给大屏端; 用于 该游戏特有的 消息,

_setRoomGroup  用户进入游戏时,将人加到正在游戏组或 等待组中

// 获取游戏状态

// 获取当前正在参与游戏中的 玩家数量

//判断 如果 正在参与游戏中的 玩家数量 没有超过最大用户数(maxUserCount)  && 游戏正处于 进人状态

—  将当前 socketClientId  加入到  Online[room_id]” 中   

  redis数据操作 将  wxopenID , isDead=1  追加到  key =  wxOpenIdOnline[room_id]  中

            && return true

// 获取  当前正在排队 的 用户的数量

//判断: 如果 当前正在排队的 数量    小于 最大等待游戏的 数量

— 是 : 将当前 socketCliendId 加入到  组 “RoomBird[room_id]”    && return true

return false

Events.php onClose() 中 做的事

调用 Base:: socketClose()

//socketClose()  

// 获取游戏的状态

// 判断 如果是 手机端(手柄端); 依据  isset($_SESSION['client_open_ID’])

//— 是 : 继续判断 游戏状态 是  == 1 正在进入状态吗

—-           —   是: 操作redis 将此用户(wxOpenID) 从 wxOpenIdOnline[room_id] 中删除掉; &&   发送 type=logout 的消息给 大屏端 告诉他 这个用户 断开连接了。 

// — 否: 就认为是 大屏端:           

操作redis 将gameState[room_id] 设置为 0  ; 发送消息给 组“clientUserGroup[room_id]” 告诉他 ,大屏掉线了 gameStatus = 0

 

关于消息type=addNewPlayer 的使用方式的 详细考证如下:


367: bird  愤怒的小鸟 - 大屏端
this.schedule(this.addQueuePlayer,2,262144,0)

addQueuePlayer:function(){
	if(this.m_players.length

上面细节不重要,总结就是:addNewPlayer 由大屏端发起,在 小鸟和贪吃蛇的游戏中 每隔两秒就 拉人就来,一次拉一个人。

 

用到的GateWayWorker 的方法

Gateway::isUidOnline

Gateway::sendToCurrentClient

Gateway::bindUid

Gateway::sendToUid

Gateway::getClientIdByUid

Gateway::closeClient

Gateway::getClientCountByGroup

Gateway::getClientInfoByGroup

Gateway::leaveGroup

Gateway::joinGroup

Gateway::sendToClient

Gateway::sendToGroup

Gateway::sendToCurrentClient

Gateway::getSession

Gateway::updateSession

$_SESSION['room_id'] = 

 

 

优化的方向:

组名,消息名的 命名规范!

 

 

 

 

 

 

你可能感兴趣的:(项目案例)