联机游戏结束
联机逻辑开发进度:■■■■■■■■■■■□
本章结束开发进度:■■■■■■■■■■■■■
上一章的答案
服务端只需要获取玩家本房间的Game对象,再根据前端发送的方向,调用Game对象的playerMove()方法,最后发送游戏地图数据就完成了。
<?php...class Server{...const CLIENT_CODE_MOVE_PLAYER = 602;...public function onMessage($server, $request){...switch ($data['code']) {...case self::CLIENT_CODE_MOVE_PLAYER:$this->logic->movePlayer($data['direction'], $playerId);break;}}...}...
但是获取房间Game对象前,需要拿到玩家的room_id,我们先来解决这个问题。在DataCenter中新增玩家room_id的增删查方法,在bindRoomWorker()的时候把player_id和room_id进行一次绑定。
DataCenter类:
<?php...class DataCenter{...public static function setPlayerRoomId($playerId, $roomId){$key = self::PREFIX_KEY . ':player_room_id:' . $playerId;self::redis()->set($key, $roomId);}public static function getPlayerRoomId($playerId){$key = self::PREFIX_KEY . ':player_room_id:' . $playerId;return self::redis()->get($key);}public static function delPlayerRoomId($playerId){$key = self::PREFIX_KEY . ':player_room_id:' . $playerId;self::redis()->del($key);}...public static function initDataCenter(){...//清空玩家房间ID$key = self::PREFIX_KEY . ':player_room_id*';$values = self::redis()->keys($key);foreach ($values as $value) {self::redis()->del($value);}}...}
Logic类:
<?php...class Logic{...public function movePlayer($direction, $playerId){if (!in_array($direction, Player::DIRECTION)) {echo $direction;return;}$roomId = DataCenter::getPlayerRoomId($playerId);if (isset(DataCenter::$global['rooms'][$roomId])) {/*** @var Game $gameManager*/$gameManager = DataCenter::$global['rooms'][$roomId]['manager'];$gameManager->playerMove($playerId, $direction);$this->sendGameInfo($roomId);}}...private function bindRoomWorker($playerId, $roomId){...DataCenter::setPlayerRoomId($playerId, $roomId);...}}
这下移动功能应该就可以用了,我们赶紧来重启一下服务器,尝试玩一下。

如果代码没毛病的话,相信童鞋们已经可以玩起来了。
但是我们的游戏还没结束,还差了最后一个基础功能:联机结束判断。
联机结束判断
做题时间
- 玩家每次移动后,调用
Game对象的isGameOver()方法,检测是否已结束。 - 满足结束条件的游戏房间则通知客户端并发送胜利玩家ID。
- 关闭游戏房间。
Sender类:
<?php...class Sender{...const MSG_GAME_OVER = 1005;const CODE_MSG = [...self::MSG_GAME_OVER => '游戏结束啦~'];...}
Logic类:
<?php...class Logic{public function movePlayer($direction, $playerId){...if (isset(DataCenter::$global['rooms'][$roomId])) {...$this->checkGameOver($roomId);}}...private function checkGameOver($roomId){/*** @var Game $gameManager* @var Player $player*/$gameManager = DataCenter::$global['rooms'][$roomId]['manager'];if ($gameManager->isGameOver()) {$players = $gameManager->getPlayers();$winner = current($players)->getId();foreach ($players as $player) {Sender::sendMessage($player->getId(), Sender::MSG_GAME_OVER, ['winner' => $winner]);DataCenter::delPlayerRoomId($player->getId());}unset(DataCenter::$global['rooms'][$roomId]);}}}
index.html:
...<script>var app = new Vue({...methods: {...websocketonmessage(e) { //数据接收...switch (message.code) {...case 1005://游戏结束this.winner = responseData.winnersetTimeout(function () {alert('游戏结束~胜者是:' + responseData.winner)}, 200)break;}},...}})</script>...
重启游戏,我们再试一次。

童鞋们发现了吗?胜利者是player_161,但是地图上却留下了player_355的大名,没错,这是一个bug,请童鞋们找到并修复它,答案在本章结束处。
结语
恭喜你走到了课程的最后,如果有认真做完每章后面的Homework,有认真去思考每一个做题时间的实现方法,应该还是有一丢丢收获的吧?~
基本功能虽然已经开发完毕,但是游戏的可扩展性还是很强的。例如可以加入聊天系统,或者加入一些地雷之类的陷阱。还有许多的功能在第一章中已列举出来,并附上了难度等级。后面的路就需要各位童鞋自己去开拓了。
小册编写过程中部分代码有可能会产生master分支和teach分支不一致的情况,如果会影响到主流程的Bug请直接告诉我,如果只是游戏体验方面的Bug请童鞋们自行修复,尝试开发额外功能遇到问题时,也可以直接跑来问我要怎么解决。
如果觉得阅读本小册收获异常丰富,仅仅请赵童鞋喝一杯奶茶远远不够,欢迎给我发红包,你的赞赏会让我更有动力写下一篇๑乛◡乛๑
小册资料
本小册所有出现过的资料链接都在这里,大家有需要的可尽情查阅。
PHP
- Windows一键安装包:www.apachefriends.org/index.html
- Linux一键安装包:lnmp.org/install.htm…
Composer
- 安装:pkg.phpcomposer.com/#how-to-ins…
- 国内镜像一:
composer config -g repo.packagist composer https://packagist.phpcomposer.com - 国内镜像二:
composer config -g repo.packagist composer https://packagist.laravel-china.org
Swoole
- Swoole安装教程:wiki.swoole.com/wiki/page/6…
- Swoole Server:wiki.swoole.com/wiki/page/p…
- Swoole Task:wiki.swoole.com/wiki/page/1…
- Swoole Websocket Server:wiki.swoole.com/wiki/page/3…
- Swoole Client fd:wiki.swoole.com/wiki/page/5…
- 多进程共享数据:wiki.swoole.com/wiki/page/8…
- Server bind:wiki.swoole.com/wiki/page/3…
- Swoole WebSocket onMessage: wiki.swoole.com/wiki/page/4…
Redis
- Redis安装教程:redis.io/download
- php-redis扩展包:pecl.php.net/package/red…
Vue
- Vue官方文档:cn.vuejs.org/v2/guide/
- v-if:cn.vuejs.org/v2/guide/co…
- v-for:cn.vuejs.org/v2/guide/li…
<template>:cn.vuejs.org/v2/guide/li…- 阮一峰WebSocket教程:www.ruanyifeng.com/blog/2017/0…
- Vue生命周期:cn.vuejs.org/v2/guide/in…
本章BUG答案:
Logic类:
<?php...class Logic{...private function sendGameInfo($roomId){...//必须倒序输出,因为游戏设定数组第一个是寻找者,第二个是躲藏者,叠加时赢的是寻找者。foreach (array_reverse($players) as $player) {$mapData[$player->getX()][$player->getY()] = $player->getId();}...}}
