用Swoole来写个联机对战游戏呀!(七)异步匹配玩家

栏目: PHP · 发布时间: 5年前

内容简介:联机逻辑开发进度:■■■■■□□□□□□□本章结束开发进度:■■■■■■■□□□□□

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

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

上一章的答案:

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 -1
1) "player_177"

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

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

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

剩下的操作就是:

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

异步检测匹配队列

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

  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 类:

<?php
namespace App\Manager;

class TaskManager
{
}

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

  1. 设置一个常量 TASK_CODE_FIND_PLAYER ,用于发起寻找玩家 task 任务。
  2. 新增静态方法 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;
    }
}

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

  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

用Swoole来写个联机对战游戏呀!(七)异步匹配玩家


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构与算法分析

数据结构与算法分析

维斯 / 人民邮电 / 2006-10 / 59.00元

《数据结构与算法分析:C++描述》秉承Weiss著全一贯的严谨风格,同时又突出了实践。书中充分应用了现代C++语言特性,透彻地讲述了数据结构的原理和应用,不仅使学生具备算法分析能力,能够开发高效的程序,而且让学生掌握良好的程序设计技巧。一起来看看 《数据结构与算法分析》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具