用Swoole来写个联机对战游戏呀!(六)游戏匹配机制

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

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

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

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

上一章的答案:

index.html

var app = new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue!',
        websock: null,
    },
    created() {
        this.initWebSocket();
    },
    destroyed() {
        this.websock.close() //离开路由之后断开websocket连接
    },
    methods: {
        initWebSocket() { //初始化websocket
            const wsuri = "ws://192.168.3.41:8811";
            this.websock = new WebSocket(wsuri);
            this.websock.onmessage = this.websocketonmessage;
            this.websock.onopen = this.websocketonopen;
            this.websock.onerror = this.websocketonerror;
            this.websock.onclose = this.websocketclose;
        },
        websocketonopen() { //连接建立之后执行send方法发送数据
            let actions = {"test": "12345"};
            this.websocketsend(actions);
        },
        websocketonerror() {//连接建立失败重连
            this.initWebSocket();
        },
        websocketonmessage(e) { //数据接收
            let message = JSON.parse(e.data);
        },
        websocketsend(Data) {//数据发送
            this.websock.send(JSON.stringify(Data));
        },
        websocketclose(e) {  //关闭
            console.log('断开连接', e);
        },
    }
})
  • 记得将 192.168.3.41 改为你的 IP 地址哦。

Server 类:

public function onWorkerStart($server, $workerId)
{
    echo "server: onWorkStart,worker_id:{$server->worker_id}\n";
}
public function onOpen($server, $request)
{
    DataCenter::log(sprintf('client open fd:%d', $request->fd));
}
public function onMessage($server, $request)
{
    DataCenter::log(sprintf('client open fd:%d,message:%s', $request->fd, $request->data));
    $server->push($request->fd, 'test success');
}
public function onClose($server, $fd)
{
    DataCenter::log(sprintf('client close fd:%d', $fd));
}

写完以上代码后,在虚拟机中重新运行 Server ,在浏览器访问前端页面并打开 F12 开发者模式。

用Swoole来写个联机对战游戏呀!(六)游戏匹配机制

前端成功地发出了数据,服务端返回的数据也都接收到了,我们成功打通了游戏前后端。

游戏数据管理

游戏的数据主要有两个存储方式:

Redis

我们首先在 DataCenter 类中新增一个静态变量 $global ,所有的对战数据都将存储在这个变量中。

DataCenter 类:

class DataCenter
{
    public static $global;
    ...
}

目前我们的游戏还没有 Redis 连接方式,需要编写一个 Redis 驱动。

  1. app 目录下新建 Lib 目录,并新建一个 Redis.php 文件。
  2. 使用单例模式编写 Redis 驱动。
  3. DataCenter 类中,新增一个 redis() 方法返回 Redis 实例。

Redis 类:

<?php
namespace App\Lib;

class Redis
{
    protected static $instance;
    protected static $config = [
        'host' => '127.0.0.1',
        'port' => 6379,
    ];

    /**
     * 获取 redis 实例
     *
     * @return \Redis|\RedisCluster
     */
    public static function getInstance()
    {
        if (empty(self::$instance)) {
            $instance = new \Redis();
            $instance->connect(
                self::$config['host'],
                self::$config['port']
            );
            self::$instance = $instance;
        }
        return self::$instance;
    }
}

DataCenter 类:

class DataCenter
{
    ...
    public static function redis()
    {
        return Redis::getInstance();
    }
    ...
}

到这里为止所有准备都做好了,下面开始正式进入到游戏功能开发。

进入匹配队列

我们首先要做的第一个功能就是游戏匹配。

赵童鞋的想法是:

  • 前端发送一个匹配消息到服务端。
  • 服务端将玩家的ID放入一个 Redis 队列里。
  • 当队列里人数满足条件时,创建一个游戏房间。
  • 根据 player_id 获取连接 fd ,发送游戏数据。

WebSocket 并不像普通的 HTTP 请求那样,一个功能对应一个接口,那我们要怎么区分发送和接收的消息呢?很简单,我们只要固定发送和接收数据的格式,其中加入一个参数 code 作为功能协议标识,所有的操作都根据发送和接收的 code 来进一步处理。

是不是看上去还挺好理解呢?我们先从前端入手。

  1. 新增输入框,绑定 Vue 数据属性 playerId ,并默认生成一个随机ID。
  2. 新增按钮,绑定 Vue 方法 matchPlayer ,点击按钮时发送 code600 的数据。
  3. WebSocket 连接的时候,发送玩家 playerId 到服务端。

index.html

...
<div id="app">
    <label>
        玩家ID:
        <input type="text" :value="playerId">
    </label>
    <button @click="matchPlayer">匹配</button>
</div>

<script>
    var app = new Vue({
        ...
        data: {
            playerId: 'player_' + Math.round(Math.random() * 1000),
            ...
        },
        ...
        methods: {
            //匹配玩家
            matchPlayer() {
                let actions = {"code": 600};
                this.websocketsend(actions);
            },
            initWebSocket() { //初始化websocket
                const wsuri = "ws://192.168.3.41:8811?player_id=" + this.playerId;
                ...
            },
            ...
        }
    })
</script>
...

前端代码不多,下面轮到服务端实现。

服务端将会涉及到三个类,也就是 ServerLogicDataCenter ,这三个类的调用顺序是:

  • Server 将用户信息如 playerId 保存到 DataCenter
  • Server 接收到数据,调用 Logic 中的逻辑。
  • Logic 中的逻辑调用 DataCenter 进行数据操作。
Server    →    Logic

↓            ↓

→        DataCenter

Server

我们采用自顶向下的方法来编写试试,不存在的方法也可以先写出来调用。

先从 Server 开始。从前端代码可以看到,在 WebSocket 建立连接的时候,前端会发送 player_id 到服务端,这个时候我们需要把 player_id 和连接 fd 保存在 DataCenter 中。普通的消息会带有一个 code 用来标识协议码,我们需要根据 code600 时,调用 Logic 匹配。

  1. Server 类初始化的时候,初始化 Logic 对象保存在私有变量 $logic ,用于调用 Logic 类中的方法。
  2. onOpen 事件中,保存用户的 player_idfdDataCentersetPlayerInfo() 方法中。
  3. onMessage 事件中,根据当前连接的 fd 获取 player_id ,当前端发送的消息中的 code600 时,调用 Logic 中的 matchPlayer() 方法

Server 类:

<?php
...
class Server
{
    const CLIENT_CODE_MATCH_PLAYER = 600;
    ...
    private $logic;

    public function __construct()
    {
        $this->logic = new Logic();
        ...
    }
    ...
    public function onOpen($server, $request)
    {
        DataCenter::log(sprintf('client open fd:%d', $request->fd));

        $playerId = $request->get['player_id'];
        DataCenter::setPlayerInfo($playerId, $request->fd);
    }

    public function onMessage($server, $request)
    {
        DataCenter::log(sprintf('client open fd:%d,message:%s', $request->fd, $request->data));

        $data = json_decode($request->data, true);
        $playerId = DataCenter::getPlayerId($request->fd);
        switch ($data['code']) {
            case self::CLIENT_CODE_MATCH_PLAYER:
                $this->logic->matchPlayer($playerId);
                break;
        }
    }
    ...
}
...

Logic

下面到 Logic 类, Logic 其实代码不多,他需要实现的就是接收 Server 传递的消息并执行具体的逻辑。

  1. 新增 matchPlayer() 方法,将 Server 传递过来的 player_id 放入 DataCenter 的匹配队列中。
<?php
...
class Logic
{
    public function matchPlayer($playerId)
    {
        DataCenter::pushPlayerToWaitList($playerId);
    }
}

DataCenter

有了上述两个类的调用,我们的 DataCenter 需求就清晰很多了,需要实现用户信息的存取,需要实现一个队列的进出和长度查询,用于玩家匹配。

  1. 新增常量 PREFIX_KEY ,作为所有 Rediskey 前缀,区别于其他应用缓存值。
  2. playerIdfd 编写 settergetterdelete 方法,需要实现 playerIdfd 可以互相查找。
  3. 实现匹配队列的 pushpopgetLength 方法。
  4. 完成 Server 调用的 setPlayerInfo() 方法,保存 player_idfd

本章作为第一个游戏功能开发,请童鞋们尽量完成Homework哦,其他功能如邀请、观战也将会是这个调用流程。

当前目录结构:

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

用Swoole来写个联机对战游戏呀!(六)游戏匹配机制


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

查看所有标签

猜你喜欢:

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

Ruby for Rails

Ruby for Rails

David Black / Manning Publications / 2006-05-11 / USD 44.95

What's Inside * How Ruby and Rails work, separately and together * Extensive Ruby language tutorial * Ruby techniques for Rails applications * Explore the Rails framework source code A new level of pr......一起来看看 《Ruby for Rails》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具