联机逻辑开发进度:■■■■■□□□□□□□
本章结束开发进度:■■■■■■■□□□□□
上一章的答案:
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_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
机制的初始化。
<?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管理类:需要一个类管理
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
类:
<?php
...
class DataCenter
{
...
public static $server;
...
}
Server
类:
<?php
...
class Server
{
...
public function onWorkerStart($server, $workerId)
{
...
DataCenter::$server = $server;
}
...
}
...
这样就解决了第一种问题,下面轮到第二个问题。
增加Task管理类
在项目Manager
文件夹下,创建TaskManager
类文件。
TaskManager
类:
<?php
namespace App\Manager;
class TaskManager
{
}
后续所有跟task
有关的常量、方法都归于这个类来管理。
- 设置一个常量
TASK_CODE_FIND_PLAYER
,用于发起寻找玩家task
任务。 - 新增静态方法
findPlayer()
,当匹配队列长度大于等于2
时,弹出队列前两个玩家的player_id
并返回。
TaskManager
类:
<?php
namespace 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;
}
}
现在前置准备就绪,可以将上面写过的伪代码改成真实代码啦~
- 在
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
发表回复