联机逻辑开发进度:■■■■■□□□□□□□
本章结束开发进度:■■■■■■■□□□□□
上一章的答案:
DataCenter
类:
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_id
和 player_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 -1
1) "player_177"
可以看到,匹配队列 game:player_wait_list
中已经成功存入了 player_177
。
目前我们的匹配机制已经完成了:
- 前端连接时发送
player_id
- 服务端连接时保存玩家信息
- 前端发送
code
为 600
的指令
- 服务端将
player_id
放入匹配队列
剩下的操作就是:
- 检测匹配队列长度,当长度大于等于2时,创建游戏房间
异步检测匹配队列
我们大部分游戏逻辑都是运行在 worker
里的,异步的玩家匹配可以减轻主程序 worker
的负担。关于 Task
机制不了解的童鞋,请先熟悉一下官方文档。
-
Swoole Task
文档: https://wiki.swoole.com/wiki/...
- 根据官方文档,在
Server
类中完成 Task
机制的初始化。
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
类:
task(xxx);
}
}
Server
类:
可以发现, onTask
方法只是接收传递的 $data
,当我们有多种 Task
任务 (匹配玩家、在线检测、游戏状态检查)
时,我们的 worker
怎么区分每一个 Task
任务呢?其实就和客户端与服务端通信一样,我们可以根据一个 code
来区分。
Logic
类:
task(['code'=>'xxx']);
}
}
Server
类:
xxx();
break;
case 'yyy':
//task->yyy();
break;
}
}
...
}
...
从代码可以看出,我们现在缺了两种机制:
- 全局获取Server对象:在
Logic
中获取 swoole_server
从而调用 task()
方法。
- 增加Task管理类:需要一个类管理
Task
的 code
和 Task
需要执行的逻辑方法。
全局获取Server对象
第一个比较好处理,我们在 onWorkerStart
的时候就能获取到 swoole_server
。
- 有童鞋可以会问,为什么不在
onStart
的时候获取?这是因为 onStart
回调的是 Master
进程,而 onWorkerStart
回调的是 Worker
进程,只有 Worker
进程才可以发起 Task
任务。有兴趣的童鞋请查阅文档: https://wiki.swoole.com/wiki/...
- 在
DataCenter
中新增静态变量 $server
。
- 在
onWorkerStart
回调函数中,将 $server
保存到 DataCenter
中。
DataCenter
类:
Server
类:
这样就解决了第一种问题,下面轮到第二个问题。
增加Task管理类
在项目 Manager
文件夹下,创建 TaskManager
类文件。
TaskManager
类:
后续所有跟 task
有关的常量、方法都归于这个类来管理。
- 设置一个常量
TASK_CODE_FIND_PLAYER
,用于发起寻找玩家 task
任务。
- 新增静态方法
findPlayer()
,当匹配队列长度大于等于 2
时,弹出队列前两个玩家的 player_id
并返回。
TaskManager
类:
= 2) {
$redPlayer = DataCenter::popPlayerFromWaitList();
$bluePlayer = DataCenter::popPlayerFromWaitList();
return [
'red_player' => $redPlayer,
'blue_player' => $bluePlayer
];
}
return false;
}
}
现在前置准备就绪,可以将上面写过的伪代码改成真实代码啦~
- 在
Logic
类的 matchPlayer()
方法中,发起一个 Task
任务尝试匹配。
- 在
Server
类中根据传入的 code
,执行 TaskManager
的 findPlayer()
方法。
- 当
findPlayer()
方法有值返回的时候,返回执行结果并携带上 code
到 worker
进程。
本章就到这里结束了,这次留的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