邀请玩家对战

这一章我们来新增一个对战类游戏很常见的功能:邀请其他玩家进行开房对战。

整个功能其实不太复杂,我们先来看一下流程图:

16 邀请玩家对战 - 图1

玩家A发起挑战,玩家B会有两种处理方式,分别是接受挑战和拒绝挑战,接受的时候就按正常游戏逻辑来创建房间,拒绝的时候则通知玩家A对手已拒绝。

我们可以把整个功能划分为两块:玩家发起挑战、对手处理挑战。

玩家发起挑战

发起挑战的逻辑很简单,在前端页面中输入对手ID,将对手ID发送至服务端。

做题时间

  1. 前端新增一个输入框,用于输入对手ID。
  2. 前端新增一个“挑战”按钮,绑定makeChallenge()方法,用于发送对手ID以及挑战消息到服务端
  3. 服务端接收到挑战消息后,发送消息给对手进行挑战确认。

小提示:如果玩家不在线怎么办?

index.html

  1. ...
  2. <div id="app">
  3. ...
  4. <div v-if="matching" style="display: inline">
  5. 匹配中……
  6. </div>
  7. <div v-else>
  8. <div v-if="!roomId" style="padding-top: 5px;">
  9. 对手ID
  10. <input type="text" v-model="opponentId">
  11. <button @click="makeChallenge">挑战</button>
  12. </div>
  13. </div>
  14. ...
  15. </div>
  16. <script>
  17. var app = new Vue({
  18. ...
  19. data: {
  20. ...
  21. opponentId: '',
  22. },
  23. ...
  24. methods: {
  25. makeChallenge() {
  26. if (!this.opponentId) {
  27. alert('请输入对手ID');
  28. return;
  29. }
  30. let actions = {
  31. "code": 603,
  32. "opponent_id": this.opponentId
  33. };
  34. this.websocketsend(actions);
  35. },
  36. ...
  37. websocketonmessage(e) { //数据接收
  38. ...
  39. switch (message.code) {
  40. ...
  41. case 1007:
  42. alert("对手不在线")
  43. break;
  44. }
  45. },
  46. ...
  47. }
  48. })
  49. </script>
  50. ...

Server类:

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

Logic类:

  1. <?php
  2. ...
  3. class Logic
  4. {
  5. ...
  6. public function makeChallenge($opponentId, $playerId)
  7. {
  8. if (empty(DataCenter::getOnlinePlayer($opponentId))) {
  9. Sender::sendMessage($playerId, Sender::MSG_OPPONENT_OFFLINE);
  10. } else {
  11. $data = [
  12. 'challenger_id' => $playerId
  13. ];
  14. Sender::sendMessage($opponentId, Sender::MSG_MAKE_CHALLENGE, $data);
  15. }
  16. }
  17. ...
  18. }

Sender类:

  1. <?php
  2. ...
  3. class Sender
  4. {
  5. ...
  6. const MSG_OPPONENT_OFFLINE = 1007;
  7. const MSG_MAKE_CHALLENGE = 1008;
  8. const CODE_MSG = [
  9. ...
  10. self::MSG_OPPONENT_OFFLINE => '对手不在线',
  11. self::MSG_MAKE_CHALLENGE => '发起挑战',
  12. ];
  13. ...
  14. }

服务端接收到挑战消息后,要先判断玩家B是否在线状态,当玩家B离线时可以立即通知玩家A挑战失败,下面我们就来测试一下这部分代码是否正常。

16 邀请玩家对战 - 图2

成功发送了一个code603的消息,并附带了对手ID:player_563

16 邀请玩家对战 - 图3

另一边也成功接收到了消息,并且附带了挑战者ID:player_939

对手处理挑战

像上面所说,对于挑战有两种处理方式,分别是接受挑战和拒绝挑战。简单点的方式只需要使用JavaScriptconfirm()方法就能满足这一需求。

做题时间

  1. 收到挑战消息后,弹出选择框,让玩家选择接受与拒绝。
  2. 当玩家接受挑战后,服务端开始一局游戏。
  3. 当玩家拒绝挑战后,服务端通知发起挑战玩家消息:对手已拒绝。

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 1008:
  12. var challengerId = responseData.challenger_id;
  13. var msg = "玩家 " + challengerId + " 邀请你进行对战,是否接受";
  14. let actions = {
  15. "code": 604,
  16. "challenger_id": challengerId
  17. };
  18. if (!confirm(msg)) {
  19. actions = {
  20. "code": 605,
  21. "challenger_id": challengerId
  22. };
  23. }
  24. this.websocketsend(actions);
  25. break;
  26. case 1009:
  27. alert("对方拒绝了你的挑战");
  28. break;
  29. }
  30. },
  31. ...
  32. }
  33. })
  34. </script>
  35. ...

Server类:

  1. <?php
  2. ...
  3. class Server
  4. {
  5. ...
  6. const CLIENT_CODE_ACCEPT_CHALLENGE = 604;
  7. const CLIENT_CODE_REFUSE_CHALLENGE = 605;
  8. ...
  9. public function onMessage($server, $request)
  10. {
  11. ...
  12. switch ($data['code']) {
  13. ...
  14. case self::CLIENT_CODE_ACCEPT_CHALLENGE:
  15. $this->logic->acceptChallenge($data['challenger_id'], $playerId);
  16. break;
  17. case self::CLIENT_CODE_REFUSE_CHALLENGE:
  18. $this->logic->refuseChallenge($data['challenger_id']);
  19. break;
  20. }
  21. }
  22. ...
  23. }
  24. ...

Logic类:

  1. <?php
  2. ...
  3. class Logic
  4. {
  5. ...
  6. public function acceptChallenge($challengerId, $playerId)
  7. {
  8. $this->createRoom($challengerId, $playerId);
  9. }
  10. public function refuseChallenge($challengerId)
  11. {
  12. Sender::sendMessage($challengerId, Sender::MSG_REFUSE_CHALLENGE);
  13. }
  14. ...
  15. }

Sender类:

  1. ...
  2. class Sender
  3. {
  4. ...
  5. const MSG_REFUSE_CHALLENGE = 1009;
  6. const CODE_MSG = [
  7. ...
  8. self::MSG_REFUSE_CHALLENGE => '对方拒绝了你的挑战',
  9. ];
  10. ...
  11. }

得益于我们前面开发游戏时良好的封装,接受挑战仅仅需要一行代码:调用createRoom()方法,就能让程序走回原本的创建房间逻辑。

下面我们来测试一下代码:

16 邀请玩家对战 - 图4

发起挑战后,对手成功接收到了消息。

选择拒绝挑战的时候:

16 邀请玩家对战 - 图5

发起挑战方成功收到了拒绝消息。

选择接受挑战的时候:

16 邀请玩家对战 - 图6

成功进入了游戏界面,功能正常。

Homework

本章节我们开发了一个邀请对战功能,虽然基本流程是走通了,但其实还是会出现不少Bug的。

如:

  • 发起挑战时,对手在游戏中怎么办?
  • 对手接受挑战后,如果发起方断线了怎么办?
  • 对手接受挑战后,如果发起方在游戏中怎么办?

请同学们思考一下并尝试自行解决以上问题。

本章对应Github Commit扩展六:邀请玩家对战