联机游戏结束

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

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

上一章的答案

服务端只需要获取玩家本房间的Game对象,再根据前端发送的方向,调用Game对象的playerMove()方法,最后发送游戏地图数据就完成了。

  1. <?php
  2. ...
  3. class Server
  4. {
  5. ...
  6. const CLIENT_CODE_MOVE_PLAYER = 602;
  7. ...
  8. public function onMessage($server, $request)
  9. {
  10. ...
  11. switch ($data['code']) {
  12. ...
  13. case self::CLIENT_CODE_MOVE_PLAYER:
  14. $this->logic->movePlayer($data['direction'], $playerId);
  15. break;
  16. }
  17. }
  18. ...
  19. }
  20. ...

但是获取房间Game对象前,需要拿到玩家的room_id,我们先来解决这个问题。在DataCenter中新增玩家room_id的增删查方法,在bindRoomWorker()的时候把player_idroom_id进行一次绑定。

DataCenter类:

  1. <?php
  2. ...
  3. class DataCenter
  4. {
  5. ...
  6. public static function setPlayerRoomId($playerId, $roomId)
  7. {
  8. $key = self::PREFIX_KEY . ':player_room_id:' . $playerId;
  9. self::redis()->set($key, $roomId);
  10. }
  11. public static function getPlayerRoomId($playerId)
  12. {
  13. $key = self::PREFIX_KEY . ':player_room_id:' . $playerId;
  14. return self::redis()->get($key);
  15. }
  16. public static function delPlayerRoomId($playerId)
  17. {
  18. $key = self::PREFIX_KEY . ':player_room_id:' . $playerId;
  19. self::redis()->del($key);
  20. }
  21. ...
  22. public static function initDataCenter()
  23. {
  24. ...
  25. //清空玩家房间ID
  26. $key = self::PREFIX_KEY . ':player_room_id*';
  27. $values = self::redis()->keys($key);
  28. foreach ($values as $value) {
  29. self::redis()->del($value);
  30. }
  31. }
  32. ...
  33. }

Logic类:

  1. <?php
  2. ...
  3. class Logic
  4. {
  5. ...
  6. public function movePlayer($direction, $playerId)
  7. {
  8. if (!in_array($direction, Player::DIRECTION)) {
  9. echo $direction;
  10. return;
  11. }
  12. $roomId = DataCenter::getPlayerRoomId($playerId);
  13. if (isset(DataCenter::$global['rooms'][$roomId])) {
  14. /**
  15. * @var Game $gameManager
  16. */
  17. $gameManager = DataCenter::$global['rooms'][$roomId]['manager'];
  18. $gameManager->playerMove($playerId, $direction);
  19. $this->sendGameInfo($roomId);
  20. }
  21. }
  22. ...
  23. private function bindRoomWorker($playerId, $roomId)
  24. {
  25. ...
  26. DataCenter::setPlayerRoomId($playerId, $roomId);
  27. ...
  28. }
  29. }

这下移动功能应该就可以用了,我们赶紧来重启一下服务器,尝试玩一下。

10 联机游戏结束 - 图1

如果代码没毛病的话,相信童鞋们已经可以玩起来了。

但是我们的游戏还没结束,还差了最后一个基础功能:联机结束判断。

联机结束判断

做题时间

  1. 玩家每次移动后,调用Game对象的isGameOver()方法,检测是否已结束。
  2. 满足结束条件的游戏房间则通知客户端并发送胜利玩家ID。
  3. 关闭游戏房间。

Sender类:

  1. <?php
  2. ...
  3. class Sender
  4. {
  5. ...
  6. const MSG_GAME_OVER = 1005;
  7. const CODE_MSG = [
  8. ...
  9. self::MSG_GAME_OVER => '游戏结束啦~'
  10. ];
  11. ...
  12. }

Logic类:

  1. <?php
  2. ...
  3. class Logic
  4. {
  5. public function movePlayer($direction, $playerId)
  6. {
  7. ...
  8. if (isset(DataCenter::$global['rooms'][$roomId])) {
  9. ...
  10. $this->checkGameOver($roomId);
  11. }
  12. }
  13. ...
  14. private function checkGameOver($roomId)
  15. {
  16. /**
  17. * @var Game $gameManager
  18. * @var Player $player
  19. */
  20. $gameManager = DataCenter::$global['rooms'][$roomId]['manager'];
  21. if ($gameManager->isGameOver()) {
  22. $players = $gameManager->getPlayers();
  23. $winner = current($players)->getId();
  24. foreach ($players as $player) {
  25. Sender::sendMessage($player->getId(), Sender::MSG_GAME_OVER, ['winner' => $winner]);
  26. DataCenter::delPlayerRoomId($player->getId());
  27. }
  28. unset(DataCenter::$global['rooms'][$roomId]);
  29. }
  30. }
  31. }

index.html

  1. ...
  2. <script>
  3. var app = new Vue({
  4. ...
  5. methods: {
  6. ...
  7. websocketonmessage(e) { //数据接收
  8. ...
  9. switch (message.code) {
  10. ...
  11. case 1005://游戏结束
  12. this.winner = responseData.winner
  13. setTimeout(function () {
  14. alert('游戏结束~胜者是:' + responseData.winner)
  15. }, 200)
  16. break;
  17. }
  18. },
  19. ...
  20. }
  21. })
  22. </script>
  23. ...

重启游戏,我们再试一次。

10 联机游戏结束 - 图2

童鞋们发现了吗?胜利者是player_161,但是地图上却留下了player_355的大名,没错,这是一个bug,请童鞋们找到并修复它,答案在本章结束处

结语

恭喜你走到了课程的最后,如果有认真做完每章后面的Homework,有认真去思考每一个做题时间的实现方法,应该还是有一丢丢收获的吧?~

基本功能虽然已经开发完毕,但是游戏的可扩展性还是很强的。例如可以加入聊天系统,或者加入一些地雷之类的陷阱。还有许多的功能在第一章中已列举出来,并附上了难度等级。后面的路就需要各位童鞋自己去开拓了。

小册编写过程中部分代码有可能会产生master分支和teach分支不一致的情况,如果会影响到主流程的Bug请直接告诉我,如果只是游戏体验方面的Bug请童鞋们自行修复,尝试开发额外功能遇到问题时,也可以直接跑来问我要怎么解决。

如果觉得阅读本小册收获异常丰富,仅仅请赵童鞋喝一杯奶茶远远不够,欢迎给我发红包,你的赞赏会让我更有动力写下一篇๑乛◡乛๑

小册资料

本小册所有出现过的资料链接都在这里,大家有需要的可尽情查阅。

PHP

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

Redis

Vue

本章BUG答案:

Logic类:

  1. <?php
  2. ...
  3. class Logic
  4. {
  5. ...
  6. private function sendGameInfo($roomId)
  7. {
  8. ...
  9. //必须倒序输出,因为游戏设定数组第一个是寻找者,第二个是躲藏者,叠加时赢的是寻找者。
  10. foreach (array_reverse($players) as $player) {
  11. $mapData[$player->getX()][$player->getY()] = $player->getId();
  12. }
  13. ...
  14. }
  15. }