php创建游戏房间思路,用Swoole来写个联机对战游戏呀!(八)创建游戏房间
Logic 类:
...
class Logic
{
public function matchPlayer($playerId)
{
...
//发起一个Task尝试匹配
DataCenter::$server->task(['code' => TaskManager::TASK_CODE_FIND_PLAYER]);
}
}
复制代码
Server 类:
...
class Server
{
...
public function onTask($server, $taskId, $srcWorkerId, $data)
{
DataCenter::log("onTask", $data);
$result = [];
switch ($data['code']) {
case TaskManager::TASK_CODE_FIND_PLAYER:
$ret = TaskManager::findPlayer();
if (!empty($ret)) {
$result['data'] = $ret;
}
break;
}
if (!empty($result)) {
$result['code'] = $data['code'];
return $result;
}
}
...
}
...
复制代码
童鞋们的作业完成情况如何呢?
我们来再次梳理一下目前的匹配功能进度:
前端连接时发送 player_id
服务端连接时保存玩家信息
前端发送 code 为 600 的指令
服务端将 player_id 放入匹配队列
服务端发起一个 task 进行玩家匹配,当寻找到两个玩家时返回两个 player_id 到 worker 进程
那下一步就很明显了,就是创建游戏房间。
创建房间分析
在 Server 类的 onFinish() 方法中,根据传入的 code ,执行 Logic 的 createRoom() 方法。
Server 类:
...
class Server
{
...
public function onFinish($server, $taskId, $data)
{
DataCenter::log("onFinish", $data);
switch ($data['code']) {
case TaskManager::TASK_CODE_FIND_PLAYER:
$this->logic->createRoom($data['data']['red_player'],
$data['data']['blue_player']);
break;
}
}
}
...
复制代码
显然,下一步就是完成这个 createRoom() 方法匹配机制就大功告成了。但是真的这么简单吗?下面我们要思考一件事情。
我们的匹配队列是存放在 Redis 中的,无论哪个 worker 都可以读取,但游戏数据是存放在内存中的,在启动 Swoole Worker 时设置了 'worker_num' => 4 , worker 是多进程的,这会产生什么效果呢?就是进程内存隔离。
比如, A玩家 进入了 worker_1 ,数据保存在 worker_1 进程内存中,而 B玩家 进入了 worker_2 ,数据保存在 worker_2 进程内存中。他们的匹配队列用的却是同一个 Redis List ,假如我们选择了 worker_1 进行游戏数据存放,那么 B玩家 将会读取不到内存中的游戏数据。
要解决这个问题有几个容易的方法:
Redis
worker
显然, A 方法过于粗暴,没想到竟说出如此 粗鄙之语 !而 B 方法扩展性不好,当有成千上万玩家的时候,我们的 Redis 分分钟就挂给你看。这样下来只能选择 C 方法来实践。
Swoole 为我们提供了一个 bind() 方法,就可将连接绑定到固定的一个 worker 来处理。不了解 bind() 方法的童鞋请先阅读一下官方文档,尤其是 时序问题 。
那么我们创建房间的流程就是:
生成一个房间 room_id
将 task 寻找到的两位玩家连接的 fd 绑定到 room_id 算出的同一个 int 值
通知玩家 room_id
前端获取到 room_id 后,发起开始游戏请求
绑定玩家连接
想要使用 bind() 方法,需先将 dispatch_mode 设置为 5 。
完成 Logic 的 createRoom() 方法,生成一个 room_id ,绑定连接 fd 。
获取 $server 对象,向两个玩家分别发送房间 room_id 。
Server 类:
...
class Server
{
...
const CONFIG = [
...
'dispatch_mode' => 5,
...
];
...
}
...
复制代码
Logic 类:
...
class Logic
{
...
public function createRoom($redPlayer, $bluePlayer)
{
$roomId = uniqid('room_');
$this->bindRoomWorker($redPlayer, $roomId);
$this->bindRoomWorker($bluePlayer, $roomId);
}
private function bindRoomWorker($playerId, $roomId)
{
$playerFd = DataCenter::getPlayerFd($playerId);
DataCenter::$server->bind($playerFd, crc32($roomId));
DataCenter::$server->push($playerFd, $roomId);
}
}
复制代码
童鞋们发现问题了吗?
没错,我们的 push() 方法直接就把 room_id 发过去了。又是这种问题:接收方无法识别该消息是何种消息。那么我们要如何处理呢?还是老套路,加 code 协议码。一个更好的办法是,找一个类来专门管理发送相关的变量和方法。
在 Manager 文件夹下,新建 Sender 类文件。
Sender 类:
namespace App\Manager;
class Sender
{
}
复制代码
在 Sender 类中新增 MSG_ROOM_ID 常量,作为发送 room_id 的 code 。
新增方法 sendMessage($playerId, $code, $data = []) ,通过传入的 $playerId 发送固定格式的消息到客户端。比较常规的内容需要有: code 、 msg 、 data 。
将 bindRoomWorker() 中发送房间 room_id 的代码改为使用 Sender 发送。
Sender 类:
...
class Sender
{
const MSG_ROOM_ID = 1001;
const CODE_MSG = [
self::MSG_ROOM_ID => '房间ID',
];
public static function sendMessage($playerId, $code, $data = [])
{
$message = [
'code' => $code,
'msg' => self::CODE_MSG[$code] ?? '',
'data' => $data
];
$playerFd = DataCenter::getPlayerFd($playerId);
if (empty($playerFd)) {
return;
}
DataCenter::$server->push($playerFd, json_encode($message));
}
}
复制代码
Logic 类:
...
class Logic
{
...
private function bindRoomWorker($playerId, $roomId)
{
$playerFd = DataCenter::getPlayerFd($playerId);
DataCenter::$server->bind($playerFd, crc32($roomId));
Sender::sendMessage($playerId, Sender::MSG_ROOM_ID, ['room_id' => $roomId]);
}
}
复制代码
这下我们的前端就能通过接收的 code 来判断,究竟这条 message 是 房间ID 或者是 游戏数据 。
我们来测试一下目前为止的代码有没有问题。重启 Server 服务器,在浏览器打开两个游戏前端页面并点击匹配按钮。
[root@localhost app]# php Server.php
master start (listening on 0.0.0.0:8811)
server: onWorkStart,worker_id:4
server: onWorkStart,worker_id:5
server: onWorkStart,worker_id:6
server: onWorkStart,worker_id:7
server: onWorkStart,worker_id:0
server: onWorkStart,worker_id:1
server: onWorkStart,worker_id:2
server: onWorkStart,worker_id:3
[2019-04-21 15:59:46][INFO]: client open fd:3
[2019-04-21 15:59:50][INFO]: client open fd:3,message:{"code":600}
[2019-04-21 15:59:50][INFO]: onTask {"code":1}
[2019-04-21 15:59:50][INFO]: onFinish {"data":{"red_player":"player_177","blue_player":"player_181"},"code":1}
PHP Warning: Swoole\WebSocket\Server::push(): the connected client of connection[9] is not a websocket client or closed. in /mnt/htdocs/HideAndSeek_teach/app/Manager/Sender.php on line 31
复制代码
显然,程序报错了。这是因为我们启动 服务器 时,没有清除之前的残余玩家信息,所以 push() 时报错了。
初始化玩家数据
在 DataCenter 中新增 initDataCenter() 方法清除 Redis 中的残余数据。
在 onStart 的时候调用 initDataCenter() 方法。
DataCenter 类:
...
class DataCenter
{
...
public static function initDataCenter()
{
//清空匹配队列
$key = self::PREFIX_KEY . ':player_wait_list';
self::redis()->del($key);
//清空玩家ID
$key = self::PREFIX_KEY . ':player_id*';
$values = self::redis()->keys($key);
foreach ($values as $value) {
self::redis()->del($value);
}
//清空玩家FD
$key = self::PREFIX_KEY . ':player_fd*';
$values = self::redis()->keys($key);
foreach ($values as $value) {
self::redis()->del($value);
}
}
...
}
复制代码
Server 类:
...
class Server
{
...
public function onStart($server)
{
...
DataCenter::initDataCenter();
}
...
}
...
复制代码
现在再来一次,重启 Server 服务器,在浏览器打开两个游戏前端页面并点击匹配按钮。
可以看到,服务端成功发送 room_id 。
发送开始游戏指令
在 Vue 的数据属性中新增 roomId ,用于保存服务端发送的 room_id 。
新增方法 startRoom() ,当服务端发来 room_id 消息时,发送 code 以及 room_id 到服务端开始游戏。
本章留的Homework是前端功能,但是比较简单,请童鞋们尽力完成哦。
当前目录结构:
HideAndSeek
├── app
│ ├── Lib
│ │ └── Redis.php
│ ├── Manager
│ │ ├── DataCenter.php
│ │ ├── Game.php
│ │ ├── Logic.php
│ │ ├── Sender.php
│ │ └── TaskManager.php
│ ├── Model
│ │ ├── Map.php
│ │ └── Player.php
│ └── Server.php
├── composer.json
├── composer.lock
├── frontend
│ └── index.html
├── test.php
└── vendor
├── autoload.php
└── composer
复制代码
php创建游戏房间思路,用Swoole来写个联机对战游戏呀!(八)创建游戏房间相关推荐
- 一个新游戏的思路;大家来说说看,觉得好的话,我做成游戏
在一个40x40的棋盘上,分布7种颜色或图形的标记 每次3秒中随机选出其中其中4个颜色标记到 n,m 的空白位置,用鼠标选择某个位置的颜色标记,可以移动到鼠标选择的目标位:当目标位的横或竖的排列达到4 ...
- Cocos Creator 3D使用腾讯云游戏联机对战引擎(MGOBE)
腾讯云在2019年2月上线新品--游戏联机对战引擎MGOBE,为游戏提供房间管理.在线匹配.网络通信.云开发等服务,帮助开发者快速搭建多人交互游戏. 在2021年2月,实现兼容 Cocos 引擎 v3 ...
- 腾讯云为小游戏开发者升级工具箱 小游戏联机对战引擎免费用
由微信小游戏举办的"微信小游戏创意大赛"正在火热进行中.12月23日,腾讯云宣布,除了给参赛者提供基础云资源,还将提供更多工具支持.开发者在通过初赛后,可免费使用腾讯云" ...
- 怎么开发联机小游戏_微信小游戏创意大赛火热进行中,小游戏联机对战引擎免费用...
腾讯云为小游戏开发者升级工具箱 小游戏联机对战引擎免费用 由微信小游戏举办的"微信小游戏创意大赛"正在火热进行中.12月23日,腾讯云宣布,除了给创意大赛的参赛者提供基础云资源,还 ...
- 战双帕弥什登入显示服务器错误,战双帕弥什游戏进不去怎么办 卡在初始界面解决方法...
战双帕弥什游戏进不去怎么办.<战双帕弥什>是一款末世科幻题材的3D动作手游,游戏马上要公测了,不过在之前的测试很多玩家都或多或少的出现过进不去游戏的情况,这个该怎么办呢?下面小编就为大家带 ...
- iOS简易蓝牙对战五子棋游戏设计思路之一——核心蓝牙通讯类的设计
iOS简易蓝牙对战五子棋游戏设计思路之一--核心蓝牙通讯类的设计 一.引言 本系列博客将系统的介绍一款蓝牙对战五子棋的开发思路与过程,其中的核心部分有两个,一部分是蓝牙通讯中对战双方信息交互框架的设计 ...
- 在通知栏上玩游戏,Steve iOS 游戏实现思路
在通知栏上玩游戏,Steve iOS 游戏实现思路 最近有一款游戏特别的火爆,叫做Steve ,一种可以在通知中心直接玩的游戏.作者的脑洞也是非常的大,实在让人佩服.其实实现起来也简单,就是用到了iO ...
- python逢7跳过_python实现逢七拍腿小游戏的思路详解
逢七拍腿游戏 几个小朋友在一起玩逢七拍腿的游戏,从1开始数数,当数到7的倍数或者尾号是7时,拍一下腿.现在从1数到99,假设每个人都没有错,计算一下共要拍腿几次? 第一种实现思路:通过在for循环语句 ...
- 【有趣的Python小程序】Python多个简单上手的库制作WalkLattice 走格子游戏 (思路篇)上
篇写上一个思路篇,那么今天我们就来完成这一项工作 源代码和配套文件 链接: https://caiyun.139.com/m/i?135ClY1yWrSKX 提取码:e4pq 复制内容打开中国移动云盘 ...
- iOS简易蓝牙对战五子棋游戏设计思路之二——核心棋盘逻辑与胜负判定算法
2019独角兽企业重金招聘Python工程师标准>>> iOS简易蓝牙对战五子棋游戏设计思路之二--核心棋盘逻辑与胜负判定算法 一.引言 上一篇博客我们介绍了在开发一款蓝牙对战五子棋 ...
最新文章
- python 如何自学-如何系统地自学 Python?
- 【Oracle】触发器最系统入门学习指导
- Python3 编程示例:斐波纳契数列
- 下拉推广系统立择火星推荐_下拉词优化不仅仅优化百度,其实还可以优化抖音、京东和阿里巴巴...
- oracle删除orcl库_oracle删除数据文件
- 报错, org.hibernate.PropertyAccessException
- 使用c 对mysql数据库_c对mysql数据库的操作
- mysql in varchar_MySQL中char和varchar有啥区别?优缺点是啥?
- linux rm 命令删除文件恢复_rm删除文件空间就释放了吗?天真!
- Arduino框架下合宙ESP32C3 +1.8“TFT液晶屏通过TFT_eSPI库驱动显示
- 3种夸克有多少组合?
- iOS - 常用的iOS Mac框架和库以及常用的中文开发博客
- 积累的VC编程小技巧之打印相关
- MATLAB初学教程(一)
- String是基本数据类型吗?
- js正则匹配以{开头,以}结尾怎么写?
- 有什么靠谱的Python培训机构
- 用Go语言编程的利与弊
- 按键精灵官网版 v9.63.12960 绿色版
- ICC配置文件与photoshop颜色管理