Amazon GameLift FlexMatch であのゲームやこのゲームと同じマッチメイキングをやってみた

2022-12-22
デベロッパーのためのクラウド活用方法

Author : 秋山 周平

この記事は AWS for Games Advent Calendar 2022 の 12 月 22 日の記事になります。

ゲームを自分でプレイするのが好きなみなさま、人のプレイをみるのが好きなみなさま、作るのが好きなみなさま、こんにちは ! Game Solutions Architect の秋山周平です。

本日は複数人で遊ぶゲームでは重要な要素になる「マッチメイキング」に関する Amazon GameLift の機能の紹介と実践をやっていきたいと思います ! 具体的には、Amazon GameLift FlexMatch (以下 FlexMatch )を使用し、本記事を通して以下の 4 パターンのマッチメイキングを実現します。

  • 1 vs 1 を実現する 2 チーム 2 人マッチ
  • 4 vs 4 を実現する 2 チーム 8 人マッチ
  • 4 vs 4 vs 4 を実現する 3 チーム 12 人マッチ
  • 40 人以上によるバトルロワイヤル形式を実現する 巨大マッチ

今回は初めて FlexMatch に触れる方にも一目でわかるよう結果を視覚的に確認できるスクリプトを用意してますので、ぜひ実際に手を動かして試していただけると嬉しいです !

なお、マッチメイキングだけではなくマルチプレイヤーゲーム全体の作成に興味がある人は是非 こちらの記事 (Amazon GameLift を使って「最短」でマルチプレイヤーゲームを作る) も確認してみてください。


1. 注意事項

本記事では以下の操作が行えることと、環境が整っていることを前提としています。

  • AWS のマネジメントコンソール
  • 対話型シェル
  • 本記事で使用するサービスを使用可能な IAM ユーザーもしくは IAM ロールの準備

また、本記事で紹介したサンプルを実行することで AWS サービスの利用料が発生する可能性があります。
作成したリソースは必ず最後に削除するよう気をつけましょう。

本記事で使用するサンプルコードは以下よりダウンロードが可能です。

サンプルコードをダウンロード »


2. Amazon GameLift FlexMatch の紹介 : 概要

FlexMatch は JSON ベースのファイルでロジックを短時間で作成、変更可能なマルチプレイヤーゲーム用マッチメイキングサービスです。

もしマッチメイキングを完全にゼロから実装しようとした場合、マッチメイキングのロジックだけではなく、ロジックで使用するユーザー情報の取得と整形、マッチメイキング待機中ユーザーのテーブル、結果のアウトプットなどの実装が必要です。加えて、別のタイトルで使用するミドルウェアや言語が変わった際に既存のコードを書き直す必要が発生する可能性があります。

一方で、FlexMatch では、マッチメイキングのロジックや入力するユーザーデータの形式 JSON で記述することが可能で、バージョン管理が容易になります。また、インプットについては AWS の API 経由で行い、ユーザーの情報も統一的なスキーマで記述することが可能です。

また、FlexMatch は単独で使用することができます。つまり、GameLift のホスティングサービスを利用せずとも、FlexMatch にユーザーを登録しマッチメイキングを実行し、その結果を直接受け取ることが可能です。

実際にマッチメイキングを行う際、ユーザー視点ではユーザーはまず自身を FlexMatch にマッチメイキング対象として登録します。この際にユーザーは「チケット」を受け取ります。FlexMatch (実際にマッチを検索する主体を FlexMatch マッチメーカーと呼んでいます) は取得したチケットの一覧、「プール」に対してマッチが成立するか確認し、マッチが成立したらその結果を外部へ出力します。


3. FlexMatch ルールセット の紹介

サンプルコードの使用を始める前に、マッチメイキングロジックのコアとなる ルールセット の仕組みを解説します。

ルールセットの実体は JSON 形式で記述されたテキストです。FlexMatch では評価方法が事前定義されており、 JSON のキーでこちらを指定することでロジックを組み合わせることで、マッチメイキング全体のロジックを構成することができます。

今回使用するサンプルルールを理解する上で最低限の説明以下に記載します。

teams :

マッチを構成するプレイヤーの人数やプレイヤーの対立関係を定義します。ここで決めたチームの構造とサイズをベースにマッチメイキングの条件を作成するため、一番最初に設計することを推奨します。

playerAttributes : 

マッチを構成する際の条件で使用するプレイヤーの属性をここに記載します。

rules : 

マッチを構成する条件を定義します。例えばマッチ中のチームの実力差を一定の範囲に収める、などの条件を記載します。複数記載可能ですが、条件を絞りすぎるとマッチが成立するまでにかかる時間が増加するため気をつける必要があります。

expansions(optional) :

時間経過で「rule」に記載した条件を緩めるために使用することができます。「rule」に厳しい条件を設定しマッチがなかなか成立しないことが想定される場合に併用することができます。

algorithm(algorithm) : 

マッチメイキング全体の挙動を変更する際に使用することができます。


4. サンプルパッケージの解説 : AWS CloudFormation Template

まずは、本記事冒頭の サンプルコード の zip ファイルをダウンロードしてください。zip ファイルを解凍すると、以下の通り複数のファイルが展開されます。

そのうち、template.yaml が本ハンズオンで使用する AWS リソースを自動生成する AWS CloudFormation 用サンプルテンプレートです。サンプルテンプレートをデプロイすると、こちらの構成図の通りリソースが作成されます。

今回の記事では FlexMatch のみを使用します。ゲームサーバーのホスティングを行わない代わりに、マッチ結果を効果的に取得するため、Amazon EventBridge でマッチイベントを取得し Amazon SQS Queue に格納する構成としています。

4-1. サンプルテンプレートのデプロイ

権限を持ったユーザーで以下コマンドを実行し、 template.yaml テンプレートをデプロイしましょう。
ローカルのマシンでも可能ですが、本記事ではなるべく全員が使用可能な共通の環境として AWS Cloud9 で実施します。

以下コマンドで AWS CloudFormation スタックを作成します。

AWS Cloud9 が利用できない場合、こちらのブログ をご参考に AWS IDE Toolkits または AWS CloudShell をご利用ください。

AWS Cloud9 から AWS IDE Toolkits または AWS CloudShell に移行する方法 »

JSONNAME=<ファイル名(1v1.json や 4v4.json など)>
aws cloudformation create-stack --stack-name BuildersFlashSample \
--template-body file://template.yaml \
--parameters ParameterKey=RuleSetJsonBody,ParameterValue=\'$(cat $JSONNAME | tr -d ' ' | tr -d '\n' )\' ParameterKey=RuleSetJsonName,ParameterValue=BuildersFlash-$(uuidgen)

作成したスタックのステータスが完了となれば、サーバー側の設定は完了です。

なお、スタックを更新することでもマッチメイキングのロジックとなるルールセットを変更することができますが、チーム形を変える場合には更新が失敗するため、作成したスタックを削除し再作成することをお勧めします。


5. サンプルパッケージの解説 : main.py

次に、パッケージ中の Python スクリプトを確認します。こちらのスクリプトでは、サンプルユーザーの生成と FlexMatch への登録、並びに Amazon SQS Queue からの結果の収集を毎秒実行します。今回使用するスクリプトは全体で 200 行ほどになります。主要な部分についてのみ以下で解説します。

まず、スクリプトの概要は以下に記述されています。 FlexMatch へのユーザー登録と、マッチメイキングの結果の取得、取得した結果の表示と更新を一定間隔で行います。

def main():
    players_attrs_def = get_player_attr_definition()
    with ThreadPoolExecutor(10) as executor:
        while True:
            executor.submit(start_matchmaking(players_attrs_def))
            executor.submit(get_matchmaking_result())
            executor.submit(monitor_players_status(players_attrs_def))
            time.sleep(REQUEST_INTERVAL)
            clear_matched_queue()

プレイヤーを FlexMatch に登録する箇所では、ユーザー名にランダムな文字列を設定し、数値型のユーザーの属性に乱数を設定することで、多様なプレイヤーが接続する状況を再現しています。

@retry(stop_max_attempt_number=3)
def start_matchmaking(players_attrs_def, target=None):
    gamelift = boto3.client("gamelift")
    Player = {
        "PlayerId": "".join(random.choices(string.ascii_lowercase, k=6)),
        "PlayerAttributes": {},
    }

    for attr in players_attrs_def:
        if attr["type"] == "number":
            Player["PlayerAttributes"][attr["name"]] = {
                "N": random.randint(0, 100)}
    gamelift.start_matchmaking(ConfigurationName=CONFIG_NAME, Players=[Player])
    add_waiting_players_list(Player)

マッチメイキングの結果は Amazon SQS Queue にエンキューされるため、定期的にキューの状況を確認します。メッセージが存在した場合には、ユーザー生成時の情報と突き合わせを行った後スクリプトで用意したキューにデータを保存し、キューのメッセージを削除します。同時に Amazon CloudWatch Metrics にデータポイントを Put します。

def get_matchmaking_result():
    sqs = boto3.resource("sqs")
    queue = sqs.get_queue_by_name(QueueName="MatchResultQueue")

    for message in queue.receive_messages(MaxNumberOfMessages=10):
        match = json.loads(message.body)["detail"]
        integrate_results_players(
            match["gameSessionInfo"]["players"], match["matchId"])
        message.delete()


@retry(stop_max_attempt_number=3)
def integrate_results_players(players, matchId):
    match = {"matchId": matchId, "players": {}}
    for player in players:
        pid = player["playerId"]
        if pid in _waiting_players.keys():
            match["players"][pid] = _waiting_players.pop(pid)
            match["players"][pid]["Elapsed"] = {
                "N": elapsed_time() - match["players"][pid]["StartTime"]["N"]
            }
            del match["players"][pid]["name"]

    enqueue_matched_players(match)
    put_metrics(match)

スクリプトと各 AWS サービスの関係はこちらのようになります。

クリックすると拡大します

最後に、実際にスクリプト実行するとこちらのような動作となります。

画面左にスクリプトで登録したユーザー情報が、画面右に取得したマッチ結果が表示されることが確認できるかと思います。

クリックすると拡大します

それでは、上記スクリプトを使用し、いろいろなマッチメイキングを実践してみましょう !


6. 検証 1 : 1 vs 1 マッチの動作を確認する

まずは基本となる 1 vs 1 の 2 人マッチを結成するルールセットを適用し、動作を確認してみましょう。
なお、今回はターミナル画面の録画と gif イメージ作成に asciinemaagg (asciinema gif generator を使用しています。

6-1. JSON (1v1.json)

{
    "name": "rankmatch",
    "ruleLanguageVersion": "1.0",
    "playerAttributes": [{
        "name": "LEVEL",
        "type": "number"
    }],
    "teams": [{
        "name": "teama",
        "maxPlayers": 1,
        "minPlayers": 1
    },{
        "name": "teamb",
        "maxPlayers": 1,
        "minPlayers": 1
    }],
     "rules": [{
         "type": "distance",
         "name": "level",
         "measurements": ["avg(teams[teama].players.attributes[LEVEL])"],
         "referenceValue": "avg(teams[teamb].players.attributes[LEVEL])",
         "maxDistance": 10
     }]
}

このマッチメイキングでは、一つのマッチに対立する 2 チームが存在し、それぞれのチームは 1 人のプレイヤーで構成されます。また、プレイヤーのレベルに関するルールが存在します。ルールのセクションでは、
teams[teama].players.attributes[LEVEL] 箇所で、各プレイヤーの LEVEL を List<number> といったリストの型 で取得し、こちらに avg を指定することで LEVEL を値として取得します。ルールの種類としては distance ルール を用いており、両チームごとの値を比較することで、プレイヤーのレベル差が 10 以下となることを条件とします。

6-2. 結果 (gif)

マッチ結果をスクリプトで確認した結果は以下の通りです。2 人のマッチが大量に作られていることが確認できます。

また、本マッチではマッチ中のプレイヤーのレベル差を 10 以下としていますが、その通りとなっていることが確認できるかと思います。

クリックすると拡大します


7. 検証 2 : 4 vs 4 マッチの動作を確認する

次に、4 vs 4 の 8 人マッチを形成するルールセットを作成し、本当に 8 人のマッチとなるか確認してみます。

7-1. JSON (4v4.json)

{
    "name": "rankmatch",
    "ruleLanguageVersion": "1.0",
    "playerAttributes": [{
        "name": "LEVEL",
        "type": "number"
    }],
    "teams": [{
        "name": "teama",
        "maxPlayers": 4,
        "minPlayers": 4
    },{
        "name": "teamb",
        "maxPlayers": 4,
        "minPlayers": 4
    }],
     "rules": [{
         "type": "distance",
         "name": "level",
         "measurements": ["avg(teams[teama].players.attributes[LEVEL])"],
         "referenceValue": "avg(teams[teamb].players.attributes[LEVEL])",
         "maxDistance": 10
     }]
}

先ほどと同じような JSON テキストとなりますが、各チームのプレイヤーの数が 4 人となっていることがわかります。

また、今回は各チームの平均が 10 以内となるルールのため、マッチ全体で見た時にはレベルに差があるプレイヤーが同一マッチに含まれていることが確認できるかと思います。

7-2. 結果 (gif)

結果はこちらになります。無事 8 人のマッチが作成されることがわかります。

クリックすると拡大します


8. 検証 3 : 4 vs 4 vs 4 マッチの動作を確認する

次に、4 vs 4 vs 4 の 12 人マッチを形成するルールセットを作成し、本当に 12 人のマッチとなるか確認してみます。

8-1. JSON (4v4v4.json)

{
    "name": "rankmatch",
    "ruleLanguageVersion": "1.0",
    "playerAttributes": [{
        "name": "LEVEL",
        "type": "number"
    }],
    "teams": [{
        "name": "teama",
        "maxPlayers": 4,
        "minPlayers": 4
    }, {
        "name": "teamb",
        "maxPlayers": 4,
        "minPlayers": 4
    }, {
        "name": "teamc",
        "maxPlayers": 4,
        "minPlayers": 4
    }],
    "rules": [{
        "name": "level",
        "type": "batchDistance",
        "batchAttribute": "LEVEL",
        "maxDistance": "20"
    }]
}

これまでの JSON テキストは 2 チームによるマッチでしたが、3 チームによるマッチに変化しています。また、それに伴い条件式も変更となっています。今回は batchdistance ルール を使用しており、マッチに参加するメンバー全員のレベル差が 20 に収まることを条件としています。

8-2. 結果 (gif)

結果はこちらの通りです。こちらも想定通り、12 人のマッチが作成されていることが確認できるかと思います。

また、BatchDistance ルールの効果により各マッチのメンバーのレベル差が 20 以内に収まっていることがわかるかと思います。

クリックすると拡大します


9. 検証 4 : 最大 50人のラージマッチの動作を確認する

最後に、最大 50 人のマッチを形成するルールセットを作成し結果を確認してみます。

JSON (50v.json)

{
    "name": "rankmatch",
    "ruleLanguageVersion": "1.0",
    "playerAttributes": [{
        "name": "LEVEL",
        "type": "number"
    }],
    "teams": [{
        "name": "team",
        "maxPlayers": 50,
        "minPlayers": 41
    }],
    "algorithm": {
        "balancedAttribute": "LEVEL",
        "strategy": "balanced",
        "batchingPreference": "largestPopulation"
    }
}

FlexMatch では、41 人以上 ~ 200 人までのプレイヤーを構成するマッチを「ラージマッチ」と呼び、通常のマッチメイキングとは異なるキーを設定する必要があります。今回はチーム数を 1 チーム 41 人 ~ 50 人と定義し、加えてラージマッチ向けに algorithm キーに対して以下の 3 つのパラメータを設定します。以下に簡単に説明を記載しますが、詳細は こちらの資料 をご覧ください。

algorithm.strategy

41 人以上のプレイヤーで構成されるマッチではデフォルトの値ではなく balanced に設定する必要があります。

algorithm.balancedAttribute

41 人以上のプレイヤーで構成されるマッチでは、特定の属性をマッチ形成の基準にするため、このパラメータで使用する属性を指定する必要があり、今回は LEVEL 属性を指定しています。

algorithm.batchingPreference 41 人以上のプレイヤーで構成されるマッチで、マッチ形成前にレイテンシーベースでソートするかを指定する必要があり、今回はソートをしないよう設定しています。

9-2. 結果 (gif)

結果はこちらの通りです。人数が多いため下に見切れてしまっていますが、多人数のマッチが実現されたことがわかるかと思います。(大量のプレイヤーの投入に時間がかかるため、途中経過を飛ばしています。)

クリックすると拡大します


10. Amazon CloudWatch Metrics を確認する

最後に、本スクリプトは Amazon CloudWatch にメトリックをアップロードするので、アップロードされたものをコンソールで確認してみます。今回のスクリプトでは名前空間として MatchMakingResults を指定しているので以下 URL より直接 CloudWatch Metrics のコンソール画面を開くことが可能です。

https://ap-northeast-1.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-1#metricsV2:graph=~();namespace=~'MatchMakingResults

上記 URL では https://ap-northeast-1.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-1#metricsV2:graph=~(); で CloudWatch Metrics のコンソールページを開くことが可能です。また、末尾に namespace=~'MatchMakingResults を記載することで、指定した名前空間を表示した状態で CloudWatch Metrics のコンソール画面を開くことが可能です。

今回のスクリプトでは、マッチ中のプレイヤー人数と、マッチに参加したプレイヤーの属性の値のばらつきを表示しています。

例えばこちらのグラフでは、検証 2 と検証 3 におけるマッチにおけるプレイヤーの LEVEL の最大値最小値の差を表示しています。

検証 2 では平均を条件としたルールを作成していたため LEVEL の差が大きくなっている一方、検証 3 では batchdistance ルールにより、最大 LEVEL 差が 20 以内となるルールを追加しているため、概ね 20 以内に収まっていることが確認できます。

クリックすると拡大します

みなさまは自分の環境でうまく確認できましたでしょうか ? Amazon CloudWatch に各種メトリクスを Put することで、例えばマッチにかかる時間の傾向を確認しユーザー体験を考察したり、マッチにおけるレベル差が実際にどのようになっているかというところを確認、分析することができます。

本日の FlexMatch サンプルはこれで以上になります。最後に、リソースを片付けましょう。


11. リソースを削除する

使ったリソースは片付けないと使い続けることになり、その結果無駄なコストが発生するため、毎回確実に片付ける癖をつけましょう。

今回は全てのリソースを AWS CloudFormation で作成しているため、最初に作成した AWS CloudFormation スタック を選択して削除すれば完了です。検証の際に AWS Cloud9 環境を利用した人は、 AWS Cloud9 の環境の削除も忘れず行いましょう。


12. まとめ

本記事では、マルチプレイ環境をできる限り最小の手順で構築する方法をご紹介しました。 API にリクエストを投げるだけだと、マッチメイクが行われているという実感が得られないため、簡単な Python スクリプトによる結果の表示と Amazon CloudWatch Metrics へのデータポイント発行をご紹介しました。本記事でマッチメイキングを実感することができましたでしょうか ?

マッチメイキングはゲームロジックそのものではないものの、マルチプレイにおけるユーザー体験を決定づける重要な要素の一つです。本記事をきっかけにぜひ色々な種類のマッチメイキングを FlexMatch でお試しいただければと思います。

なお、FlexMatch は単独で利用可能なため、今回のように Amazon SQS Queue からマッチメイキング結果を自力で取得することも可能ですが、GameLift のホスティングサービスと連携し、マッチング結果を直接 GameLift キューに格納することが可能です。この場合、マッチメイキングからゲームルームへのユーザーの配置までを全て GameLift 上で完結することができます。

とにかく高速で開発のサイクルを回したい方やマッチメイキングを体験してみたい方にとって、本記事が参考になれば幸いです。


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます


筆者プロフィール

秋山周平
アマゾン ウェブ サービス ジャパン合同会社
技術統括本部 ソリューションアーキテクト

以前は AWS サポートにて広範のお客様をご支援していました。現在は特にゲーム業界のお客様に対して、ソリューションアーキテクトとして技術的なご支援をしています。
好きなサービスは Amazon GameLift と Amazon Cognito。週末は大自然の中にいます。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する