基於Laravel構建遊戲即時匹配系統

前言

最近在項目中一直在開發即時消息的應用場景,本次將經過Laravel來構建一個即時的匹配系統,可適用於基本的即時對戰小遊戲,須要用到的知識點以下:php

  1. Laravel
  2. Redis
  3. Laravel-echo
  4. VueJs

下面開始實戰操做:css

安裝項目依賴

composer install predis //安裝redis擴展包

npm install laravel-echo //安裝laravel-echo客戶端


npm install -g laravel-echo-server //安裝laravel-echo-server 服務端

具體部署教程可參照以前寫的博客:
打造你的Laravel即時應用前端

建立比賽系統

在咱們的這個基礎系統中,須要用戶(user)和比賽(game),比賽主要是用於將匹配到的用戶放到一塊兒,下面將經過3張表來完成這個系統的的初始設計。ios

1.比賽表設計laravel

$table->unsignedInteger('user_id')->index();
$table->unsignedInteger('rival_id')->index()->comment('對手ID');
$table->unsignedInteger('winner_id')->default(0)->comment('勝利者ID');
$table->unsignedInteger('reward')->default(0)->comment('獎勵');
$table->json('data')->nullable()->comment('比賽數據');
$table->tinyInteger('status')->default(0)->comment('比賽狀態');
$table->timestamps();
$table->timestamp('end_at')->nullable()->comment('結束時間');

2.用戶匹配表設計redis

$table->increments('id');
$table->unsignedInteger('user_id')->unique();
$table->tinyInteger('online_status')->default(0);
$table->timestamp('online_at')->nullable()->index();
$table->unsignedInteger('in_game_id')->default(0)->comment('在房間ID');
$table->timestamps();

構建視圖

咱們經過Vue來實現一個簡單的視圖,運用到了props、axios等基礎組件來實現.npm

<template>
  <div class="game-status">
    <div class="user-info">
      <div class="text-left">您的遊戲信息以下:</div>
      <div class="clearfix">
        <div class="float-left" v-if="me"">您的暱稱:{{ me.name }}</div>
        <div class="float-right" v-if="rival">對手暱稱:{{ rival.name }}</div>
      </div>
    </div>
    <div class="alert alert-success" role="alert">{{ gameStatus }}</div>
    <button type="button" class="btn btn-success">開始匹配</button>
  </div>
</template>

<script>
import Echo from "laravel-echo";
export default {
  props: ["user"],
  data() {
    return {
      gameStatus: "等待匹配中",
      isMatching: true,
      meId: null,
      me: null,
      rival: null
    };
  },

  mounted() {
    this.me = JSON.parse(this.user);
    console.log(this.me);
    this.meId = this.me.id;
  },
};
</script>

若是你使用的css框架是bootstrap,構建出來的應該會是這樣。json

image

完善後端匹配邏輯

提升一個matchUser的接口給前端來進行調用,首先會去查詢user_games的記錄,不存在則建立一條,稍後再查詢當前是否有正在進行中的比賽,存在的話,則拋出異常提醒用戶,不然進入匹配隊列。bootstrap

//查找或建立用戶
$userGame = UserGame::firstOrCreate(['user_id' => $user->id]);
//是否有遊戲中,防止重複請求
$game = $userGame->hasPlaying();
throw_if(!is_null($game), GameException::class, '您已在PK中,請進入遊戲!');
//用戶上線
$userGame->online();
//分發到匹配隊列中
dispatch(new MatchGame($this))->onQueue('game-match');

當用戶成功進入匹配隊列後,咱們會將全部匹配中的用戶進行對手分配,每次獲取兩個用戶爲一局比賽,邏輯以下:axios

UserGame::with('user')->onlined()->orderBy('online_at')->chunk(100, function ($collect) {
    foreach ($collect->chunk(2) as $userGames) {
        if (count($userGames) < 2) {
            continue;
        }

        $user1 = $userGames->first()->user;
        $user2 = $userGames->last()->user;

        $game = $this->gameService->createGame($user1, $user2);
        //發送socket通知雙方用戶開始
        $this->gameService->sendSocket($game, 'NewGame');
    }
});

完善前端匹配接口 && 掛載Echo

當匹配接口和匹配隊列完成後,就開始對視圖進行綁定及掛載Echo socket通訊。

<template>
  <div class="game-status">
    <div class="user-info">
      <div class="text-left">您的遊戲信息以下:</div>
      <div class="clearfix">
        <div class="float-left" v-if="me"">您的暱稱:{{ me.name }}</div>
        <div class="float-right" v-if="rival">對手暱稱:{{ rival.name }}</div>
      </div>
    </div>
    <div class="alert alert-success" role="alert">{{ gameStatus }}</div>
    <button type="button" class="btn btn-success" @click="matchGame">開始匹配</button>
  </div>
</template>

<script>
import Echo from "laravel-echo";
export default {
  props: ["user"],
  data() {
    return {
      gameStatus: "等待匹配中",
      isMatching: true,
      meId: null,
      me: null,
      rival: null
    };
  },

  mounted() {
    this.me = JSON.parse(this.user);
    console.log(this.me);
    this.meId = this.me.id;
  },

  methods: {
    matchGame: function() {
      let _this = this;
      this.isMatching = false;
      //1.發送匹配其餘用戶請求
      this.$axios.get("/match?user_id=" + this.meId).then(res => {
        _this.gameStatus = "正在匹配中";
      });

      this.mountedUserEventListen();
    },
    mountedUserEventListen: function() {
      //監聽當前用戶Channel && NewGame event
      let echo = this.initEcho();
      let _this = this;
      echo.private("App.User." + this.meId).listen("NewGame", function(e) {
        let game = e.game;
        _this.gameStatus = "用戶匹配成功";
        _this.rival = _this.meId == game.user_id ? game.rival : game.user;
      });
    },
    initEcho: function() {
      if (window.Echo == null) {
        window.io = require("socket.io-client");
        window.Echo = new Echo({
          broadcaster: "socket.io",
          host: window.location.hostname + ":6001",
          auth: {
            headers: {
              Authorization: "Bearer " + this.me.api_token
            }
          }
        });

        return window.Echo;
      }
    }
  }
};
</script>

效果展現

運行指令:

laravel-echo-server start   //開啓socket服務

php artisan queue:work //開啓隊列消費

php artisan queue:work --queue=game-match //開啓匹配隊列消費

image

結尾

本篇主要經過後端角度出發,來使用隊列技術,實現了用戶的即時匹配系統,核心知識點主要在laravel隊列和laravel-echo的使用,經過隊列將匹配中的用戶進行組合,建立一局新比賽,經過服務端廣播NewGame,客戶端監聽NewGame事件來達到即時匹配的效果。

相關文章
相關標籤/搜索