联机逻辑开发进度:■■■■■□□□□□□□

本章结束开发进度:■■■■■■■□□□□□

上一章的答案:

DataCenter类:

<?php...class DataCenter{    const PREFIX_KEY = "game";    ...    public static function getPlayerWaitListLen()    {        $key = self::PREFIX_KEY . ":player_wait_list";        return self::redis()->lLen($key);    }    public static function pushPlayerToWaitList($playerId)    {        $key = self::PREFIX_KEY . ":player_wait_list";        self::redis()->lPush($key, $playerId);    }    public static function popPlayerFromWaitList()    {        $key = self::PREFIX_KEY . ":player_wait_list";        return self::redis()->rPop($key);    }    public static function getPlayerFd($playerId)    {        $key = self::PREFIX_KEY . ":player_fd:" . $playerId;        return self::redis()->get($key);    }    public static function setPlayerFd($playerId, $playerFd)    {        $key = self::PREFIX_KEY . ":player_fd:" . $playerId;        self::redis()->set($key, $playerFd);    }    public static function delPlayerFd($playerId)    {        $key = self::PREFIX_KEY . ":player_fd:" . $playerId;        self::redis()->del($key);    }    public static function getPlayerId($playerFd)    {        $key = self::PREFIX_KEY . ":player_id:" . $playerFd;        return self::redis()->get($key);    }    public static function setPlayerId($playerFd, $playerId)    {        $key = self::PREFIX_KEY . ":player_id:" . $playerFd;        self::redis()->set($key, $playerId);    }    public static function delPlayerId($playerFd)    {        $key = self::PREFIX_KEY . ":player_id:" . $playerFd;        self::redis()->del($key);    }    public static function setPlayerInfo($playerId, $playerFd)    {        self::setPlayerId($playerFd, $playerId);        self::setPlayerFd($playerId, $playerFd);    }}

我们先来测试一下,前面所写的代码有没有问题,重新运行Server.php,并在浏览器打开游戏前端页面。

查看Redis中的键值:

127.0.0.1:6379> keys *1) "game:player_fd:player_177"2) "game:player_id:9"

可以看到,player_idplayer_fd都已经保存下来了。

发送一个匹配请求,并查看Redis中的键值:

127.0.0.1:6379> keys *1) "game:player_fd:player_177"2) "game:player_wait_list"3) "game:player_id:9"127.0.0.1:6379> lrange game:player_wait_list 0 -11) "player_177"

可以看到,匹配队列game:player_wait_list中已经成功存入了player_177

目前我们的匹配机制已经完成了:

  • 前端连接时发送player_id
  • 服务端连接时保存玩家信息
  • 前端发送code600的指令
  • 服务端将player_id放入匹配队列

剩下的操作就是:

  • 检测匹配队列长度,当长度大于等于2时,创建游戏房间

异步检测匹配队列

我们大部分游戏逻辑都是运行在worker里的,异步的玩家匹配可以减轻主程序worker的负担。关于Task机制不了解的童鞋,请先熟悉一下官方文档。

  • Swoole Task文档:https://wiki.swoole.com/wiki/...
  1. 根据官方文档,在Server类中完成Task机制的初始化。
<?php...class Server{    ...    const CONFIG = [        ...        'task_worker_num' => 4,        ...    ];    public function __construct()    {        ...        $this->ws->on('task', [$this, 'onTask']);        $this->ws->on('finish', [$this, 'onFinish']);        ...    }    ...    public function onTask($server, $taskId, $srcWorkerId, $data)    {    }    public function onFinish($server, $taskId, $data)    {    }}...

我们什么时候会用Task进行匹配队列检测呢?其实就是把玩家放入匹配队列后。

Logic类:

<?php...class Logic{    public function matchPlayer($playerId)    {        //将用户放入队列中        DataCenter::pushPlayerToWaitList($playerId);        //发起一个Task尝试匹配        //swoole_server->task(xxx);    }}

Server类:

<?php...class Server{    ...    public function onTask($server, $taskId, $srcWorkerId, $data)    {        DataCenter::log("onTask", $data);        //执行某些逻辑    }    ...}...

可以发现,onTask方法只是接收传递的$data,当我们有多种Task任务(匹配玩家、在线检测、游戏状态检查)时,我们的worker怎么区分每一个Task任务呢?其实就和客户端与服务端通信一样,我们可以根据一个code来区分。

Logic类:

<?php...class Logic{    public function matchPlayer($playerId)    {        //将用户放入队列中        DataCenter::pushPlayerToWaitList($playerId);        //发起一个Task尝试匹配        //swoole_server->task(['code'=>'xxx']);    }}

Server类:

<?php...class Server{    ...    public function onTask($server, $taskId, $srcWorkerId, $data)    {        DataCenter::log("onTask", $data);        switch ($data['code']) {            //执行task方法            case 'xxx':                //task->xxx();                break;            case 'yyy':                //task->yyy();                break;        }    }    ...}...

从代码可以看出,我们现在缺了两种机制:

  • 全局获取Server对象:在Logic中获取swoole_server从而调用task()方法。
  • 增加Task管理类:需要一个类管理TaskcodeTask需要执行的逻辑方法。

全局获取Server对象

第一个比较好处理,我们在onWorkerStart的时候就能获取到swoole_server

  • 有童鞋可以会问,为什么不在onStart的时候获取?这是因为onStart回调的是Master进程,而onWorkerStart回调的是Worker进程,只有Worker进程才可以发起Task任务。有兴趣的童鞋请查阅文档:https://wiki.swoole.com/wiki/...
  1. DataCenter中新增静态变量$server
  2. onWorkerStart回调函数中,将$server保存到DataCenter中。

DataCenter类:

<?php...class DataCenter{    ...    public static $server;    ...}

Server类:

<?php...class Server{    ...    public function onWorkerStart($server, $workerId)    {        ...        DataCenter::$server = $server;    }    ...}...

这样就解决了第一种问题,下面轮到第二个问题。

增加Task管理类

在项目Manager文件夹下,创建TaskManager类文件。

TaskManager类:

<?phpnamespace App\Manager;class TaskManager{}

后续所有跟task有关的常量、方法都归于这个类来管理。

  1. 设置一个常量TASK_CODE_FIND_PLAYER,用于发起寻找玩家task任务。
  2. 新增静态方法findPlayer(),当匹配队列长度大于等于2时,弹出队列前两个玩家的player_id并返回。

TaskManager类:

<?phpnamespace App\Manager;class TaskManager{    const TASK_CODE_FIND_PLAYER = 1;    public static function findPlayer()    {        $playerListLen = DataCenter::getPlayerWaitListLen();        if ($playerListLen >= 2) {            $redPlayer = DataCenter::popPlayerFromWaitList();            $bluePlayer = DataCenter::popPlayerFromWaitList();            return [                'red_player' => $redPlayer,                'blue_player' => $bluePlayer            ];        }        return false;    }}

现在前置准备就绪,可以将上面写过的伪代码改成真实代码啦~

  1. Logic类的matchPlayer()方法中,发起一个Task任务尝试匹配。
  2. Server类中根据传入的code,执行TaskManagerfindPlayer()方法。
  3. findPlayer()方法有值返回的时候,返回执行结果并携带上codeworker进程。

本章就到这里结束了,这次留的Homework可能有点难度,请童鞋们尽力完成。

当前目录结构:

HideAndSeek├── app│   ├── Lib│   │   └── Redis.php│   ├── Manager│   │   ├── DataCenter.php│   │   ├── Game.php│   │   ├── Logic.php│   │   └── TaskManager.php│   ├── Model│   │   ├── Map.php│   │   └── Player.php│   └── Server.php├── composer.json├── composer.lock├── frontend│   └── index.html├── test.php└── vendor    ├── autoload.php    └── composer