13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AWS & GameAdvent Calendar 2020

Day 12

Amazon GameLift FlexMatch でマッチメイキング“だけ”動かしてみる

Last updated at Posted at 2020-12-11

AWS & Game Advent Calendar 2020 の 12 日目の記事です。
本日は Amazon GameLift FlexMatch というマッチメイキングサービスを単独で動かしてみた話をしたいと思います。

マッチメイキングとは?

そもそも「マッチメイキング」という言葉を聞いて、どのようなイメージを抱くでしょうか。そのまま日本語にしてみると「試合(Match)を作る(Making)」、つまり「対戦する相手を決める」という意味合いになりそうです。
対戦相手を決めるには、挑戦者が対戦相手を希望することもあれば、どのような対戦相手がふさわしいか、第三者が対戦相手の能力を鑑みて決めることも考えられそうです。そもそも試合に参加するには、熟練度の制限があるかもしれません。
試合の挑戦者や対戦相手は、個人でなくチームということも考えられます。試合内容によっては、挑戦者や対戦相手は 2 人や 2 組でなく、それ以上の数の対戦相手になることも考えられますね。

このような考察から、マッチメイキングには、複数人でプレイするゲームに適用する「特定の評価方法」と「チーム構造」を定義することで、ゲームに参加するプレイヤーをグループ化させる役割がある、ということを導き出すことができます。
なお、上記では主に「対戦相手」を決めることに焦点を当てましたが、共通の目的を達成するための「協力者」を集めることにも同様に扱うことができそうです。

マッチメイキングシステムを作りたい!でも……

マルチプレイングゲームを開発する際、対戦相手や協力者をプレイヤー間で互いに引き合わせることを目的として、マッチメイキングを実現するためのシステム、すなわちマッチメイキングシステムの開発を伴う場合が多いと思います。
マッチメイキングシステムを作るために、どのようにマッチメイクするかルールを決めることはもちろんですが、マッチメイキングシステムの実行基盤をどのように構築するかも考察する必要があります。実行基盤を構築した後も、可用性やスケーラビリティの考慮、定期的なメンテナンス、保守や運用方法を含めた検討も必要でしょう。
そうしたマッチメイキングシステムの実行基盤に対して時間を使うよりも、マッチメイキングそのものの品質を高めて、よりプレイヤーに快適に遊んでもらいたい、という想いが先行するのではないでしょうか。少なくとも元ゲーム開発者の私はそう感じております……

Amazon GameLift FlexMatch とは?

Amazon GameLift FlexMatch は、ゲームの仕様に合わせてカスタマイズが可能なマッチメイキングサービスです。JSON 形式で表現する FlexMatch ルールセット を作成し、それを元にしたマッチメイキング設定マッチメーカーとも呼びます)を作成することで利用でき、マッチメイキングを実行する基盤を用意する必要はありません。最大 200 人までのプレイヤーを、大規模なマッチメイキングとして実施することができます。

2020 年 11 月のアップデートより、この GameLift FlexMatch を単独(Standalone)で利用することが可能になりました。アップデート前までは、 FlexMatch を使用してマッチメイキングされたプレイヤーは、GameLift 管理下のゲームサーバー上のゲームセッション(ルーム)に案内する利用が前提でした。今回のアップデートにより、マッチメイキングが成功した後の案内先を、開発者側で自由に選択することができます。既にゲームセッションをホスティングするゲームサーバーは所有しているために、マッチメイキングだけ GameLift FlexMatch を使用したい、といったユースケースに最適です。単独で使えるため、マッチメイキングの動作確認だけ実施したい場合にも、利用しやすくなったのではないでしょうか。

使ってみる

では、今回は単独で利用できるようになった GameLift FlexMatch の動作を確認しようと思います。簡単に確認するために、以下にシンプルな構成とルールを示します。

構成について

スクリーンショット 0002-12-11 9.48.49.png

GameLift FlexMatch には、マッチメイキングの状況が変化した際にイベントが発行される仕組みがあります。このイベントを Amazon Simple Notification Service(SNS) トピックに発行させます。この SNS トピックには、予め配信先として Lambda 関数をエンドポイントとして指定しておきます。Lambda 関数は、SNS から受信するメッセージペイロードの内容からどのような処理をするか判断します。特に、マッチメイキングが成功した際に、プレイヤー全員がプレイする同一の環境であるゲームセッション(ルーム)に案内してあげる処理が必要になります。今回は動作を確認する便宜上ログ出力だけ行い、CloudWatch Logs でマッチメイキングイベントの内容を確認してみます。

ルールについて

マッチメイキングのルールに関しては、今回はこのようなものを考えてみました。

  • 共通の強敵に挑むためにプレイヤー 4 人を引き合わせたい
  • マッチメイクする際、互いに職業が異なるようにしたい
    • ここでは、戦士(Warrior)、魔法使い(Wizard)、僧侶(Priest)、アーチャー(Archer)という職業のいずれかを選択できるものとします。

シンプルですが、FlexMatch の動作を確認するには十分です。

構築してみる

構築の順番としては下記になります。構築は東京リージョン内で実施しました。

  1. Lambda 関数を作成
  2. SNS トピックとサブスクリプションを作成
  3. GameLift FlexMatch マッチメイキングルールセットとマッチメイキング設定を作成

1. Lambda 関数を作成

ランタイムに Node.js 12.x を指定して Lambda 関数を作成します。その後、コードの編集画面で、SNS から受信するメッセージペイロードの内容をログとして出力するのみ行う処理を実装します。

index.js
exports.handler = async (event) => {
    var message = event.Records[0].Sns.Message;
    console.log(message);
    const response = {
        statusCode: 200,
        body: message,
    };
    return response;
};

2. SNS トピックとサブスクリプションを作成

2.1. SNS トピックの作成

トピックの作成画面より、下記項目を埋めて作成します。

項目 設定値
タイプ スタンダード
名前 SampleEventNoticeTopic

2.2. SNS サブスクリプションの作成

サブスクリプションの作成画面より、下記項目を埋めて作成します。

項目 設定値
トピック ARN 今回作成したトピックを選択
プロトコル AWS Lambda
エンドポイント 今回作成した Lambda 関数を選択

3. GameLift FlexMatch マッチメイキングルールセットとマッチメイキング設定を作成

この項目が肝です。

3.1. FlexMatch マッチメイキングルールセットの作成

GameLift のマネジメントコンソール画面から [マッチメーキングルールセットの作成] を選択します。
スクリーンショット 0002-12-08 10.24.30.png

マッチメーキングルールセットの作成画面で下記項目を埋めます。

項目 設定値
ルールセット名 SampleRuleSet
ルールセット 下記コードを貼付
sample-rule-set
{
    "name": "sample-rule-set",
    "ruleLanguageVersion": "1.0",
    "playerAttributes":[{ 
         "name": "characterRole",
         "type": "string"
    }, {
        "name": "targetOpponent",
        "type": "string"
    }],
    "teams": [{
        "name": "party",
        "maxPlayers": 4,
        "minPlayers": 4,
        "quantity": 1
    }],
    "rules": [{
        "type": "comparison",
        "name": "SameOpponent",
        "description": "Players challenge the same opponent.",
        "measurements": ["flatten(teams[*].players.attributes[targetOpponent])"],
        "operation": "="
        }, {
        "type": "comparison",
        "name": "DifferentRole",
        "description": "Characer roles are different each other.",
        "measurements": ["flatten(teams[*].players.attributes[characterRole])"],
        "operation": "!="
    }]
}

ここで、このルールセットの中身について解説したいと思います。

ルールセットには少なくとも、「チームの構造」を定義する teams プロパティと、マッチメイクの「評価方法」のコレクションを定義する rules プロパティを宣言する必要があります。ルールセットのスキーマの詳細については、こちらの公式ドキュメントをご参照ください。

  • teams プロパティについて
    • 今回は 4 人固定のパーティを 1 つ組むため、teams プロパティ内の maxPlayersminPlayers4 に設定しています。
  • rules プロパティについて
    • 今回はマッチメイキングをする際に 2 種類の条件が示されています。「挑む強敵が共通であること」と、「職業が全員異なること」です。そのため、rules プロパティには、2 種類の評価方法を定義する必要があります。rules で表現できる内容の詳細については、こちらの公式ドキュメントをご参照ください。
    • 「挑む強敵が共通であること」を表現するには、プレイヤーが選択した強敵 targetOpponent の文字列をプレイヤー間で比較し、全て一致するようにできれば要件を満たせます。今回のルールセットでは SameOpponent ルールで表現しています。
    • 「職業が全員異なること」を表現するには、プレイヤーの職業 characterRole の文字列をプレイヤー間で比較し、プレイヤー間で全て不一致であれば要件を満たせます。今回のルールセットでは DifferentRole ルールで表現しています。
    • このように、多くの場合に rules プロパティ内でゲーム固有の情報を参照する必要があります。そのために、プレイヤー属性を定義する playerAttributes プロパティで、属性の名前と型を宣言しています。この属性は、クライアントがリクエストする StartMatchmaking API のパラメータとして含めます。

少し脱線しましたので戻ります。
項目を埋めたら、[ルールセットの検証] をクリックして問題がないことを確認し、[ルールセットの作成] をクリックして作成します。

 スクリーンショット 0002-12-08 15.26.24.png

3.2. FlexMatch マッチメイキング設定の作成

FlexMatch ルールセットが作成できたら、最後はマッチメイキング設定を作成していきます。[マッチメーキング設定の作成] を選択します。

スクリーンショット 0002-12-11 9.25.38.png

マッチメーキング設定の作成画面で、下記項目を埋めます。
ポイントとしては、FlexMatch モードに STANDALONE を選択することと、通知先を指定することです。STANDALONE としての利用時には、マッチメイキングの成功後に、開発者側でプレイヤーをゲームセッションに誘導する必要があります。ここで指定する通知先が、マッチングされたプレイヤーをゲームセッションに案内する役目を担います。今回はログを出力する Lambda 関数がサブスクライブしている SNS トピックを指定しています。

項目 設定値
名前 SampleMatchMaker
FlexMatch モード STANDALONE
リクエストのタイムアウト 120
ルールセット名 今回作成したルールセット: SampleRuleSet
通知先 今回作成した SNS トピック: SampleEventNoticeTopic の ARN

スクリーンショット 0002-12-11 9.27.01.png

項目を埋めたら、[作成] をクリックします。

これで FlexMatch を利用する準備ができました。

確認してみる

本来は AWS SDK を組み込んだクライアントから、マッチメイキングを開始する StartMatchmaking API を呼び出すように実装します。今回は便宜上 AWS CLI を使って動作を確認してみます。
下記のようなコマンドを、表に記載のパラメータの組み合わせ毎に叩いてみます。

コマンド例
aws gamelift start-matchmaking --region ap-northeast-1 \
    --configuration-name SampleMatchMaker \
    --players '[{"PlayerId": "10001", "PlayerAttributes": {"targetOpponent": {"S": "Troll"}, "characterRole": {"S": "Warrior"}}, "Team": "party"}]'
PlayerId PlayerAttributes.targetOpponent PlayerAttributes.characterRoll
10001 Troll Warrior
10002 Troll Wizard
10003 Troll Priest
10004 Dragon Archer
10005 Dragon Wizard
10006 Troll Archer
10007 Dragon Warrior
10008 Dragon Wizard

コマンドを叩いた後は、このようなレスポンスが表示されます。

CLI結果
{
    "MatchmakingTicket": {
        "TicketId": "ac9e35fa-7014-4615-8f52-f480b788996b",
        "ConfigurationName": "SampleMatchMaker",
        "ConfigurationArn": "arn:aws:gamelift:ap-northeast-1:<account-id>:matchmakingconfiguration/SampleMatchMaker",
        "Status": "QUEUED",
        "StartTime": "2020-12-11T11:49:50.002000+09:00",
        "Players": [
            {
                "PlayerId": "10008",
                "PlayerAttributes": {
                    "characterRole": {
                        "S": "Wizard"
                    },
                    "targetOpponent": {
                        "S": "Dragon"
                    }
                },
                "Team": "party"
            }
        ],
    }
}

CloudWatch Logs に出力されたログを確認すると、Troll を倒しに行く 10001, 10002, 10003, 10006 のプレイヤー達で、マッチメイキングが成功した旨のイベントが発行されたことが確認できます。

マッチメイキング成功ログ
{
    "version": "0",
    "id": "9ca60e00-0e11-4fc5-8a7c-4fc934b4ac93",
    "detail-type": "GameLift Matchmaking Event",
    "source": "aws.gamelift",
    "account": "<account-id>",
    "time": "2020-12-11T03:00:54.627Z",
    "region": "ap-northeast-1",
    "resources": [
        "arn:aws:gamelift:ap-northeast-1:<account-id>:matchmakingconfiguration/SampleMatchMaker"
    ],
    "detail": {
        "tickets": [
            {
                "ticketId": "cd197a41-d43b-41ec-881f-a82124f890cb",
                "startTime": "2020-12-11T03:00:33.353Z",
                "players": [
                    {
                        "playerId": "10001",
                        "team": "party1"
                    }
                ]
            },
            {
                "ticketId": "f9a7b7c8-7919-4b10-965d-1d943aefeff3",
                "startTime": "2020-12-11T03:00:34.335Z",
                "players": [
                    {
                        "playerId": "10002",
                        "team": "party1"
                    }
                ]
            },
            {
                "ticketId": "3d902bc3-1f47-41c6-9326-09e91d7087ce",
                "startTime": "2020-12-11T03:00:41.249Z",
                "players": [
                    {
                        "playerId": "10003",
                        "team": "party1"
                    }
                ]
            },
            {
                "ticketId": "dde60330-4624-46f7-87af-22b512ccf0eb",
                "startTime": "2020-12-11T03:00:54.519Z",
                "players": [
                    {
                        "playerId": "10006",
                        "team": "party1"
                    }
                ]
            }
        ],
        "type": "MatchmakingSucceeded",
        "gameSessionInfo": {
            "players": [
                {
                    "playerId": "10001",
                    "team": "party1"
                },
                {
                    "playerId": "10002",
                    "team": "party1"
                },
                {
                    "playerId": "10003",
                    "team": "party1"
                },
                {
                    "playerId": "10006",
                    "team": "party1"
                }
            ]
        },
        "matchId": "1bc10959-c6d5-4856-a36b-7501f79aee6b"
    }
}

一方、Dragon を倒しに行くプレイヤー達は、残念ながら 1000510008 のプレイヤー間で職業が被り、マッチメイキングは成立せず、120 秒後にタイムアウトされた旨のイベントが PlayerId 毎に発行されていました。

PlayerId=10004のタイムアウトログ
{
    "version": "0",
    "id": "b0763d83-2c9b-44ef-aceb-c1a38b4de401",
    "detail-type": "GameLift Matchmaking Event",
    "source": "aws.gamelift",
    "account": "<account-id>",
    "time": "2020-12-11T03:02:42.205Z",
    "region": "ap-northeast-1",
    "resources": [
        "arn:aws:gamelift:ap-northeast-1:<account-id>:matchmakingconfiguration/SampleMatchMaker"
    ],
    "detail": {
        "reason": "TimedOut",
        "tickets": [
            {
                "ticketId": "f1b4d050-bbb8-4257-af57-446dbf29589b",
                "startTime": "2020-12-11T03:00:42.149Z",
                "players": [
                    {
                        "playerId": "10004",
                        "team": "party"
                    }
                ]
            }
        ],
        "ruleEvaluationMetrics": [
            {
                "ruleName": "SameOpponent",
                "passedCount": 6048,
                "failedCount": 0
            },
            {
                "ruleName": "DifferentRole",
                "passedCount": 5544,
                "failedCount": 504
            }
        ],
        "type": "MatchmakingTimedOut",
        "message": "Removed from matchmaking due to timing out.",
        "gameSessionInfo": {
            "players": [
                {
                    "playerId": "10004",
                    "team": "party"
                }
            ]
        }
    }
}

まとめ

GameLift FlexMatch でマッチメイキングを行い、マッチメイキングの内容を Lambda 関数で確認するところまで構築してみました。マッチメイキングのルールやロジックを考察する時間は楽しいですね。
GameLift FlexMatch がマルチプレイングゲームを開発する方々の一助となれば幸いです。

(免責) 本記事の内容はあくまでも個人の意見であり、所属する企業や団体とは関係がございません。

13
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?