Version: jp

faq

note

Unity マッチメイキングは現在、クローズドベータテスト中です。このサービスの詳細については、マッチメイキングに関するドキュメントを参照してください。チュートリアル、FAQ、API 情報が含まれます。

Multiplay を使用してマルチプレイヤーゲームをホストすることに興味をお持ちですか?詳細についてはお問い合わせください。

複数のプレイヤーから成るパーティやグループがマッチメーカーでどのようにサポートされるか#

多くのマルチプレイヤーゲームでは、プレイヤーのグループをまとめてマッチメイキングにエントリーできるようにするための方法が実装されています。たとえば、複数のプレイヤーを同じアクティビティにエントリーするための方法や、チームベースのゲームで複数のプレイヤーを同じチームにグループ化する方法などが用意されています。

Multiplay マッチメーカーでは、カスタム属性やカスタムプロパティをチケットに渡し、それらの値をマッチ関数内で使用することにより、マッチを探しているプレイヤーのグループをサポートすることができます。

note

属性はチケットデータベースを紹介するために使用できます。プロパティは、追加情報が必要な際にマッチ関数の内部でデシリアライズされます。詳細については、「チケット」を参照してください。

大まかに表すと、典型的な実装は次のプロセスのようになります。

1.プレイヤーをグループ化するための方法を実装します(たとえば、パーティサービス)。 2.そのグループ用のチケットを提出します。

  • これは通常、サービスまたは「リーダー」クライアントを通じて実行されます。
  • このチケットにはグループに関する情報が含まれています。これらの情報は、チケットの属性とプロパティに格納されます。 3.マッチ関数が、グループチケット内の情報を使用して、グループを他のグループやプレイヤーとマッチングします。 4.マッチ関数によってマッチ申請が作成されると、グループ固有のデータが申請のプロパティに書き込まれます。 5.チケットにマッチが割り当てられると、チケットを提出したサービスまたはクライアントによってマッチデータが消費されます。 6.決定したマッチに接続するための必要な情報を、グループ内のすべてのクライアントに伝達します。

このプロセスの間に、グループとは何かを定義し、マッチメーカーの外部にグループを作成して管理します。グループソリューションをマッチメーカーと統合するには、チケットの一部として含めるグループデータを提出した後、そのデータをマッチ関数内で使用します。

マッチメーカーシステム内でグループを表すためにどのような情報が必要かを決める作業は、デザイン上の重要な意思決定です。マッチ関数ロジックが複雑になってきたら、それがプレイヤーグループデータの表現方法にどう影響するかを考慮するようにしてください。

以下のセクションでは、マッチメーカーでのグループの表現に関するデザイン上の一般的な考慮事項について説明します。

プレイヤー数#

マッチメイキングアルゴリズムがきわめてシンプルな場合は、1 つのチケットでのプレイヤー数を把握するだけで十分な場合もあります。マッチ関数のサンプルにある以下の例では、この種の実装(playercount 属性でのプレイヤー数のエンコード)について説明しています。

  • SimpleFunction
  • TeamQosFunction
  • ClanAlliance / ClanAllianceExtended

グループのサービス品質#

クライアントのサービス品質(QoS)を使用してサーバーの割り当て元のリージョンを決定する場合は、相互に互換性がない可能性がある、特異な QoS 結果を示すプレイヤーやクライアントのグループに対する QoS を決定するための最善の方法を特定する必要があります。

たとえば、ロサンゼルスにいるプレイヤーがロンドンにいる友人とグループを作り、マッチを検索したとします。その場合、この 2 人のプレイヤーの QoS 結果は大きく異なり、まったく互換性を持たない可能性があります(たとえば、一方のプレイヤーの待ち時間が長すぎたり、敗戦率が高すぎるなど)。

これに対処する 1 つの方法は、グループチケットをマッチメイキングに提出する前に、QoS のフィルタリングと集計を実行することです。アルゴリズムの例としては、次のようなプロセスが考えられます。

1.まったく互換性のない結果や無効な結果をすべて削除する。 2.グループに対する最善の結果を推定するアルゴリズムによって、残りの結果をソートする。

  • たとえば、各 QoS エンドポイントの待ち時間を示す値の二乗平均平方根。 3.グループチケットの提出時に、上位 X 位の QoS 結果だけを含める。

このロジックはマッチメーカーにチケットを提出しているプレイヤーまたはサービスによって実行される必要があります。このロジックをマッチ関数の内部に配置することもできますが、一度事前計算を行う場合と比べて、はるかにパフォーマンスが低下します。

プレイヤー固有のデータ#

マッチ関数でプレイヤー固有の情報を使用する場合は、グループチケット上のすべてのプレイヤーを対象にそれらのデータを含めるのか、それとも集計値で代用できるのかを検討するようにしてください。

たとえば、スキル値を使用してプレイヤーをマッチングする場合、実装できる集計戦略は複数考えられます。

  • グループ内の各プレイヤーのスキルをプロパティデータとして提出する
  • グループ全体の平均スキル属性を使用する
  • グループの最小スキル、最大スキル、平均スキル、およびその他の関連属性を組み合わせて使用する
  • 集計値の提出には属性を使用し、プレイヤーごとの値の提出にはプロパティを使用する

もう 1 つの例は、クラスベースのゲームです。これは、マッチメイキングを開始する前にプレイヤーがクラスを選択できるようにするとともに、マッチ内で許可されるクラスの組み合わせの数に制限を設けるというものです。これは、グループチケット内のプレイヤーごとの情報で表すこともできますし、各クラスのプレイヤー数に対する集計値で表すこともできます(たとえば、DPS:1、戦車:2、ヒーラー:1 など)。

使用するデータ表現にかかわらず、属性を使用した場合とプロパティを使用した場合のそれぞれの長所と短所を考慮するようにしてください。

  • 属性:マッチメイキングロジック内でチケットを照会するのに適した値です(現在は double 値に制限されています)
  • プロパティ:チケットに関する詳細な情報です。最適なマッチを探したり、マッチ関数でマッチプロパティを決定できるようにするのに役立ちます

詳細については、「チケット」を参照してください。

プレイヤー ID#

プレイヤーごとのデータに基づいて意思決定を行ったり、プレイヤーごとのデータをマッチ結果に埋め込むようにマッチ関数を設定する場合は、グループチケットとマッチ関数で何らかのプレイヤー ID を埋め込む必要があるかどうかについて検討してください。

たとえば、マッチ関数でマッチ内のプレイヤーに特定の役割を割り当てるようにする場合は、どの役割を使用すればよいかをグループチケット上の各プレイヤーに伝える手段が必要になるかもしれません。

1 のグループ#

実装の内容によっては、すべてのプレイヤーが 1 つのグループに属しているかのように扱うロジックを記述したほうが簡単な場合があります。このモデルでは、複数の単独プレイヤーがサイズ 1 のグループに属することになります。この方法は、個人とグループとで大きく異るロジックやデータフィールドを実装しなければならなくなる事態を回避するのに役立ちます。

オプションのクロスプラットフォームプレイ#

クロスプレイ体験をオプションで提供することは、プラットフォームホルダーに求められる一般的な要件です。マッチメイキングシステムではクロスプレイに対応するための複数のアプローチ(人数が少ない場合はすべてを 1 つのマッチ関数で設定するなど)が利用できますが、推奨されるアプローチは、ビルトインのマッチメーカーセグメンテーション機能を使用してプラットフォームの複数の組み合わせを処理する方法です。これを行うには、マッチメイキングチケットに一部の attribute 情報と property 情報を追加する必要があります。

チケットにプラットフォーム情報をを追加するには、platform enum をチケット属性に追加することを検討してください。これにより、セグメンテーション中にチケットをプラットフォームで検索できるようになります。また、crossplay に Boolean(現在は double で表されています)を使用すると、マッチメイキング中に非クロスプレイのプレイヤーをそれぞれのプールで自動的に分離する操作が簡単になります。

次のチケット例では、playerCountteamSkillmodecrossplay、および platform 属性が使用されています。

{
"id": "6fd1e230-8032-4a29-bd96-a686716e1593",
"attributes": {
"mode": 1,
"playerCount": 2,
"teamSkill": 1500
},
"properties": {
"qosConnector": "...",
"partyInfo": "eyJwbGF5ZXJzSW5QYXJ0eSI6IHsidXNlcjEiOiB7ImtleXMiOiA0fSwgInVzZXIyIjogeyJrZXlzIjogMX19LCAid2luU3RyZWFrIjogNH0="
}
}

目標は、個別のマッチ関数の実行中に次のシナリオを作成することです。

  • クロスプレイ対応のチケットに対して実行されるマッチ関数では、プラットフォームにかかわらず照会が実行されますが、クロスプレイに対応したチケットのみが照会されます。
  • クロスプレイ非対応のチケットに対して実行されるマッチ関数では、プラットフォームごとのチケットが排他的に照会されます。この場合、プラットフォーム固有のマッチ関数実行が複数生成されますが、対象となるチケットは、クロスプレイがオプトインされているかどうかに関係しません。つまり、クロスプレイに対応した Platform A のチケットは Platform A のゲームに対して排他的にマッチングされますが、プレイヤーが 2 つのグループに分けられることはありません。

次のセクションでは、マルチプラットフォームゲームのクロスプレイをユーザーがオプションで利用できるようにする設定の例を示します。これらの例では、プラットフォーム属性に他の属性を組み合わせる方法を示すために、teamSkill が使用されています。

クロスプレイ関数の実行#

"pools": {
"default": [
{
"attribute": "mode",
"min": 1,
"max": 3,
"segmentation": { "DistributionType": "Uniform", "SegmentBehaviorType": "BucketCount", "Value": 3 }
},
{
"attribute": "teamSkill",
"min": 0,
"max": 3000,
"segmentation": { "DistributionType": "Uniform", "SegmentBehaviorType": "BucketCount", "Value": 3 }
},
{
"attribute": "crossplay",
"min": 1,
"max": 1,
},
]
}

この設定では、9 個の同時関数実行が生成されます:mode1_teamSkill0-1000mode1_teamSkill1000-2000mode1_teamSkill2000-3000mode2_teamSkill0-1000mode2_teamSkill1000-2000 など。

クロスプレイフィルターにより、クロスプレイをオプトインしたチケットだけが一緒にマッチングされます。なお、platform 属性は無視されます。

プラットフォーム固有の関数実行#

"pools": {
"default": [
{
"attribute": "mode",
"min": 1,
"max": 3,
"segmentation": {"DistributionType": "Uniform", "SegmentBehaviorType": "BucketCount", "Value": 3 }
},
{
"attribute": "teamSkill",
"min": 0,
"max": 3000,
"segmentation": { "DistributionType": "Uniform", "SegmentBehaviorType": "BucketCount", "Value": 3 }
},
{
"attribute": "platform",
"min": 0,
"max": 6,
"segmentation": { "DistributionType": "Uniform", "SegmentBehaviorType": "BucketCount", "Value": 6 }
}
]
}

この設定では、54 個の同時関数実行が生成されます:

  • mode1_teamSkill0-1000-platform1
  • mode1_teamSkill0-1000-platform2
  • mode1_teamSkill0-1000-platform3
  • mode1_teamSkill0-1000-platform4
  • mode1_teamSkill0-1000-platform5
  • mode1_teamSkill0-1000-platform6
  • mode1_teamSkill1000-2000-platform1
  • mode1_teamSkill1000-2000-platform2
  • mode1_teamSkill1000-2000-platform3
  • mode1_teamSkill1000-2000-platform4
  • mode1_teamSkill1000-2000-platform5
  • mode1_teamSkill1000-2000-platform6
  • ...
  • mode3_teamSkill2000-3000-platform5
  • mode3_teamSkill2000-3000-platform6

プラットフォームセグメントフィルターにより、同じプラットフォームのチケットだけが一緒にマッチングされます。ただし、クロスプレイフィルターがないので、この関数のクエリでは、クロスプレイが厳密にはオプトインされていないチケットも対象となります。このシナリオの場合、クロスプレイ以外のプレイヤーもクロスプレイ対応のプレイヤーとマッチングされる可能性があるため、自分のプラットフォームに居続けたいプレイヤーを対象に、より多くのマッチが返されることになります。

マッチング時間の理解と最適化#

「マッチング時間」は、プレイヤーがマッチメイキングチケットを提出してからマッチ割り当てのレスポンスを受けるまでの所要時間を測るメトリックです。これはプレイヤー体験を評価するための重要な測定基準であり、プレイヤーのリテンションにも影響します。

理想的な条件でマッチメイキングシステムを理想的に運用すれば、高品質のマッチを迅速に生成することができます。ただし、現実世界の条件下では、マッチング時間とマッチ品質に影響を与えうるさまざまな要因があります。したがって、マッチ品質とマッチング時間のバランスを図るための戦略的な方法を特定する必要があります。

以下のセクションでは、マッチング時間に影響する要因について説明します。

マッチメイキングサービスでの最短ラウンドトリップ#

次のプロセスは、マッチング時間に影響するマッチメイキングフローの各部分を簡単に説明したものです。

1.クライアントがチケットを提出します(チケットエンドポイントへの POST)。 2.マッチ関数が実行され、提出されたチケットを使って申請が作成されます。 3.申請が承認され、新しいサーバーが割り当てられます(またはバックフィルサーバーが選択されます)。 4.クライアントが、完了したマッチ割り当てを取得します(チケットエンドポイントへの GET)。

現実世界におけるチケットの最短マッチング時間は、一般的に 1 秒程度です。このマッチング時間を達成するには、次の条件を満たす必要があります。

  • マッチメーカーをホストしているデータセンターまでのプレイヤーの待ち時間が短い。
  • プレイヤーへのマッチを即座に作成できる。
  • バックフィルされたマッチにプレイヤーが割り当てられる。
  • プレイヤーがチケットの GET エンドポイントをポーリングして、割り当て後すぐにマッチを選択する。

なお、現実世界における実際のマッチング時間はこの理想値とは大きく異なる可能性があり、通常はさまざまな不確定要素(下記のリンクで説明されているものを含む)によって数秒を要します。

  • クライアントがチケットを POST するのにかかる時間
    • これは、クライアントからデータセンターまでの待ち時間 + チケットの取り込みにかかるシステム時間として計算されます
  • マッチ関数の実行にかかる時間
    • これは、関数の実行時間 + チケットの照会時間 + システムオーバーヘッドとして計算されます。
  • 関数実行のウィンドウ時間
    • スケジュールされているすべての関数が実行されるのをシステムが待機する、不確定な時間枠です。最短時間は現在およそ 300 ミリ秒で、最長時間はおよそ 3000 ミリ秒です
    • チケットは関数間での決定論的クエリを保証するためにウィンドウ間でキャッシュされます。新しいチケットは、自身が含められるまで次のウィンドウを待機する必要があります
  • マッチを即座に作成するための関数の要件を満たす、十分なチケットが提供されているかどうか
    • たとえば、8 人のプレイヤーが必要なのに 5 人しかいない場合は、マッチを作成するのに十分なプレイヤーが集まるまで待機する必要があります
  • 作成されたマッチに接続またはサーバーを割り当てるのにかかる時間
    • バックフィルには約 0 秒かかり、新しいサーバーに割り当てられるマッチについては、サーバーの割り当てにさらに 1 ~ 2 秒ほどかかります
  • 作成されたマッチがクライアントに割り当てられるのにかかる時間
    • これは、マッチの準備ができてからクライアントがマッチの準備状況をチェックするまでの遅延時間とも呼ばれます。

これらの不確定要素の一部は制御することができます(次のリンクで説明されているものを含む)。

通常、マッチを理想的なシナリオで作成することはできないので、要件とマッチング時間のバランスを図る方法について検討する必要があります。シナリオによっては、最短マッチング時間を意図的に増やすことが合理的な場合もあります。

マッチ関数の実行時間#

マッチ関数の実行にかかる時間は、マッチ関数ロジックの複雑さや、マッチ関数内で実行される操作の負荷の大きさによって変わります。実行に最大 250 ミリ秒以上かかるマッチ関数は、マッチング時間に影響を及ぼす可能性があります。実行におよそ 3000 ミリ秒以上かかっているマッチ関数は停止していると見なされて強制終了され、結果は無視されます。

一般的に、マッチ関数は O(n)O(n^2) の間でのビッグオーのパフォーマンスになる傾向があります(n はシステム内のチケットの数です)。高負荷な状況でマッチ関数の効率性を維持することは、マッチング時間を短くするための重要な要因となります。詳細については、「関数のパフォーマンスの最適化」を参照してください。

マッチを作成するための最小要件#

多くの開発者にとって、マッチを作成するための最小要件は、マッチング時間を圧倒的に左右する要因と言えます。マッチメイキングのこの部分がどれほどの時間的影響をもたらすかは、マッチを形成するための互換性のあるプレイヤーの数をどれくらい容易に特定できるかによって決まります。

つまり、選択できる互換プレイヤー数(プレイヤープール)が大きくなるほど、マッチを形成するのに必要なプレイヤー数(ゲームサイズ)は小さくなり、現実世界のトラフィックでマッチメイキングをより迅速に処理できるようになります。

プレイヤーを相互にマッチングするための基準が非常に厳密な場合は、高品質なマッチを特定するのに長時間を要する可能性があります。マッチング時間が長すぎると、プレイヤーがプレイをあきらめたり、タイムアウトになる恐れがあります。ただし、プレイヤーをマッチングするための要件が少なすぎても、マッチが「低品質」になり、プレイヤーがストレスを感じる恐れがあります。

例:QoS とプレイヤースキルを使った 4 対 4 のゲーム#

現実世界での例で考えてみましょう。サービス品質(QoS)リージョンとプレイヤースキルに基づいてプレイヤーをゲームにマッチングし、「公平な」チームによる 4 対 4 のマッチを作成したい場合の、シンプルなセットアップについて考えてみます。このシナリオの場合、すべてのプレイヤーが最善の QoS リージョン内にいて、どちらのチームも約 50/50 の確率で勝利できるようにスキル評価された、理想的な 4 対 4 のゲームが準備できるまで待つことも可能です。

ただし、十分な数の互換プレイヤーがいない場合はどうなるでしょうか?どれくらいの時間なら、マッチが作成されるのを途中であきらめずに(またはタイムアウトせずに)待てるでしょうか?どれくらい経つと、プレイヤーは待つのをやめてしまうでしょうか?より早くプレイを開始できるという理由だけで、最適性に欠くゲームにプレイヤーを導いてもよいものでしょうか?マッチング時間とマッチ品質のバランスをとるにはどうすればよいのでしょうか?これらの問いは、マッチメイキングを設定する際にもっとも重要な意味を持つ設計上の確認事項です。これらの問題に対してどのようなソリューションを設計するかが、マッチ品質とマッチング時間に大きな影響を及ぼすのです。

これらのシナリオに対処するための一般的なアプローチは、マッチメイキング時におけるチケットの処理時間に基づいて要件を変更するという方法です。これには、「最適な」マッチメイキング戦略から「最適ではない」戦略に切り替えるという手段も含まれます。4 対 4 の例の場合、次の要因について検討する必要があります。

  • 「最適な」マッチの場合は、チケットの推奨 QoS リージョン内にいる、プレイヤーが満員である、スキルマッチ度が高いなどといった基準を使用することになります。
  • 「最適ではない」マッチの場合は、チケットの推奨 QoS リージョン外である、定員に達しない、またはスキルマッチ度が低いといった条件でも、マッチを成立させる場合があります。どの基準を重視するかは、開発者が決定します。

このシナリオでは、複数のマッチ関数または複数の関数設定を使用して、最適と非最適の両方のマッチングアルゴリズムを同時に実行することが推奨されます。「最適な」マッチ関数では、常に最善のマッチをできる限り生成するように設定し、「非最適の」関数では、N 秒以上経過しているチケットを照会して、非最適(ただし許容範囲内)のマッチを生成するように設定することができます。チケットが重複する場合には、マッチ申請の「スコア」フィールドを使用して最善の申請が選ばれるようにすることができます。

tip

マッチ関数(1 つまたは複数)のセットアップによって最適なマッチと非最適マッチのバランスを図るには、さまざまな方法が考えられます。2 フェーズシステム以外の方法も検討するようにしましょう。たとえば、追加のフェーズを設けたり、マッチパラメーターを変えてそれぞれに異なるレートを設定するといった方法もあります。

クライアントの GET ポーリングレート#

クライアントがマッチ割り当てをポーリングする頻度(ポーリングレート)については、バランスを考慮することが重要です。間隔は短すぎると、システムに大きな負荷がかかったり、レート制限がかけられたりする恐れがあります。反対に間隔が長すぎると、マッチング時間が長くなります。

推奨のポーリングレートとして、次のレートを検討してください。

  • チケットを POST してから 1 ~ 2 秒後にポーリングする(一般的に、チケットが提出されてからマッチが生成されるまでには最短で 1 秒かかるため)
  • GET ポーリングの間隔を 1 秒に設定する

最悪のシナリオとして、直近のポーリングでマッチ割り当てがなかった場合、マッチング時間は GET ポーリングの実行間隔の分だけ長くなります。平均的なシナリオの場合は、最悪のシナリオの約半分の時間になります。たとえば、ポーリング間隔が 2 秒の場合、最悪のシナリオではマッチング時間が約 2 秒長くなり、平均的なシナリオではマッチング時間が約 2 秒長くなります。

最短マッチング時間を意図的に増やす#

シナリオによっては、最短マッチング時間を意図的に増やすことが効果的な場合もあります。

たとえば、最短マッチング時間を増やすことで、次のシナリオに対応することもできます。

  • 新しいマッチの代わりに、より多くのチケットがバックフィルされるようにする
  • フルマッチを生成できるだけのプレイヤーが集まるまで待機する
  • 高品質なマッチを生成するための十分な互換プレイヤーが集まるまで待機する

バックフィルの補完#

現在のバックフィルシステム(v1)には特有の競合状態があり、バックフィルプレイヤーが必要な際、関数の登録ウィンドウ中にシステム内にバックフィルリクエストが存在しない場合があります。その結果、バックフィルリクエストが履行されかったり、最悪の場合、必要のない新しいサーバーが割り当てられることがあります。

これらの競合状態を回避するには、さまざまな方法があります。たとえば、新しいチケットがバックフィル専用と判断される最小限の時間を設定し、それが経過した後は「新規サーバー」のゲームと判断されるようにすることもできます。この待機時間を追加すると、最悪のシナリオではマッチング時間が長くなる場合もありますが、サーバーが頻繁にバックフィルをリクエストする場合は、多くのチケットがバックフィルされ、待ち時間は発生しません。

これは通常、「新規マッチが作成されるまでの最小待機時間」変数を関数設定に追加することで実装されます。チケットの created 時刻に対するフィルターを追加して、チケットデータベースからチケットを照会する際にこの値を使うようにすれば、now - timeToWait よりも古いチケットだけがこの関数によってマッチングされるようになります。バックフィルを実行するサーバーはこの値を無視するか、この値を 0 に設定して、すべてのチケットを処理できるようにします。

フルマッチのための待機#

フルマッチにするのが望ましいものの、マッチの生成に長時間を要する可能性がある場合(少人数ゲームモードでプレイしている場合や、開発者または QA テストでプレイする場合など)は、マッチメイキングの時間を意図的に長くして、フルマッチが形成されるようにすることもできます。必要な最小プレイヤー数最小限のプレイヤーマッチが生成されるまでの待機時間チケットがタイムアウトするまでの待機時間 などといった基準を設定できるようにすることで、通常よりも長時間待機し、フルマッチの準備が整うのを待つマッチメイキング設定を作成することができます。

高品質マッチのための待機#

開発者は、状況によっては品質よりもスピードを重視することもありますし、スピードよりも品質を重視することもあります。どちらの戦略にも、それぞれの利用価値があります。場合によっては、「良質なマッチ」を生成するために、待機時間を意図的に長くすることが求められることもあります。

「ランクなし」の対戦モードと「ランクあり」の対戦モードの両方をサポートしたゲームで考えてみましょう。ランクなしの対戦マッチメイキングをスピーディに処理できるよう設定したい場合は、プレイヤーがマッチメイキングをどれくらい待てるかに応じてマッチ品質を下げ、ゲームを早く開始できるようにします。ただし、ランクありの対戦モードでは、公平なマッチにすることが最も重要であるため、不公平なマッチは却下されるようにします。この場合、ランクありのマッチは通常、ランクなしのマッチよりも生成に長時間を要することになります。

さらに、実際の商品や賞金を賭けた、公式オンライントーナメントのケースも考えてみましょう。この種のイベントでは、不正行為が横行する可能性もあるため、十分な時間を費やして良質なマッチを提供することが、プレイヤーの不満を回避することにつながります。

関数のパフォーマンスの最適化#

多くのマッチメイキングアルゴリズムは、その複雑さが O(n) を超えてしまう傾向にあり、O(n^2) かそれ以上になってしまうことも少なくありません。

たとえば、きわめてシンプルなマッチメイキングアルゴリズムで考えてみましょう。まず、利用可能な全チケットのリストから最初のチケットをピックアップし、リスト内をイテレーションしながら最初のチケットと互換性のあるチケットを選んでいき、最終的にフルマッチを生成するとします。このアルゴリズムは O(n^2) になります。なぜなら、最悪の場合、互換性のあるチケットが見つからず、すべてのチケットを相互に比較していくことになるからです。

目的のマッチを生成するために O(n^2) のアルゴリズムを使用する必要がある場合は、最適化が非常に重要となります。次のリストは、現実世界の計算要件を軽減するために実行できるタスクを説明したものです。

  • n をできるだけ小さく抑える
  • 必要な比較の数を減らして、アルゴリズムをできるだけ O(n) に近づける
  • n あたりに必要とされる計算の全体的な複雑さや量を減らす

最適化の例については、「マッチ関数のサンプルプロジェクト」を参照してください。

以下のセクションでも、最適化の方法に関する詳しい情報を紹介していきます。

関数のベンチマーク#

マッチ関数のシミュレーションプロジェクトには、大量のトラフィールドを生成し、そのトラフィックに対して関数を実行するための基本的なテストハーネスが含まれます。

関数を開発する際には、さまざまなレベルでシミュレートされたトラフィックロードに対して関数をテストし、パフォーマンスへの影響を確認するようにしましょう。たとえば、O(n) になるのか O(n^2) になるのかを確認したり、チケットがどれくらいの数になると、関数の実行時間が一貫して 2 ~ 3 秒以上になるのかを確認するようにしましょう。

ローンチ前に行うマッチメーカーのスケールテスト#

ローカルでのテストは、さまざまなアプローチのパフォーマンスを比較するための良い方法です。ただし、ローカル PC のテスト環境は、実際のマッチメーカー環境を完全に反映するものではありません。スケール目標を達成できる状態になっているかどうかを確認するには、Multiplay チームと連携し、カスタムマッチ関数を使用して、実際のマッチメーカーに対するスケジュール型のスケールテストを実行するのが最も効果的な方法です。

必須条件の属性とプロパティによってチケットをバケット化する#

各マッチ関数が実行されるチケットの数(O(n)O(n^2)n)を減らすことは、全体的なパフォーマンスを改善するための最も速い方法です。

まずは、チケットを事前にバケット化して、マッチング可能な小さいグループにまとめるための、必須条件フィルターを特定しましょう。「バケット」とは、特定の基準にすべて一致するチケットのグループのことです。たとえば、ゲームモード、環境、およびビルドによってチケットをバケットすることもできます。設計によっては、属性、プロパティ、またはその両方によってバケット化することもできます。

バケット化に使える属性があり、それらを事前にセグメント化するためのバケットが決まっている場合は、設定セグメンテーションシステムを使用することを検討しましょう。これを使用すれば、異なるセグメントを渡しながら関数を複数回実行することで、バケット化を自動的に実行することができます。セグメントを定義したフィルターが複数ある場合、関数は定義されたすべてのセグメントのマトリックスに基づいて実行されます。たとえば、セグメンテーションで 4 つのゲームモード、3 つの環境、および 2 つのビルドが指定されている場合、関数は(4 3 2 = 24)回実行されます(セグメント値の組み合わせごとに 1 回ずつ)。設定セグメンテーションシステムはシステムレベルの最適化なので、これを使用した場合、マッチ関数のコード内で手動のセグメント化を行う場合よりも、実行を効果的に並列化できます。ただし、バケット化に使う属性の値を実行時に確認する必要がある場合は、マッチ関数のコードを使用する必要があります。また、チケットプロパティから作成したいバケットがある場合も、マッチ関数のコードを使用する必要があります。

なお、セグメンテーションシステムを使用できない場合でも、属性やプロパティをバケット化する価値はあります。なぜなら、マッチアルゴリズムの対象となる検索領域(O(n)n)を小さくすることができるからです。

アルゴリズムの最適化戦略#

一連のチケットに対して考えうるすべてのマッチを総当たりでチェックし、どれが最善かを特定するやり方は、計算処理の観点から言って最悪のシナリオ(O(n!))になります。たとえば、100 個のユニークチケットがある場合、8 人のプレイヤーによるマッチをすべて評価するとなると、マッチの数は 7,503,063,898,176,000 件にも達します。1 秒あたりに評価できる組み合わせが 100 万通りだとすると、すべての組み合わせを評価するには 238 年かかることになります。

最適なマッチを総当たり方式で見つけることは現実的ではないので、プレイヤーの快適性を損なわない範囲で処理を簡略化するには、どのようなアルゴリズムがよいかを考えるようにしましょう。マッチメイキングプロセスの工夫によっては、常に完全な「50/50」の公平性を提供するよりも、もっと面白いゲーム体験を提供することができます。

マッチメイキングアルゴリズムを設定する際には、次のベストプラクティスを考慮するようにしましょう。

  • ゲームに関する知識を活用し、チケットをバケット化して検索領域を減らしましょう。
    • 必ず、必須条件の属性とプロパティによってチケットをバケット化するようにしましょう。
    • 追加のバケットを使用して、共通の特徴を持つチケットをグループ化することを検討しましょう。たとえば、ゲームに複数のキャラクタークラスがある場合、各クラスの X 人のプレイヤーによってバランスのとれたチームを構成する必要があるのであれば、クラスごとにプレイヤーをバケット化することで、必要なクラスのプレイヤーだけを簡単に検索できるようにするのもよいでしょう。
  • 特定の属性によってチケットを事前にソートすることで、アルゴリズムをスピード化できるかどうかを検討しましょう。
    • たとえば、O(n log n) によるソートの後に O(n) のマッチングアルゴリズムを実行したほうが、O(n^2) の場合よりも全体のスピードは速くなります。
  • 範囲や許容範囲を使用して、プレイヤーとチームの間の許容可能な不一致性を定義しましょう。
    • たとえば、「チーム間のスキル差が X 未満であれば、そのマッチは許容範囲内とする」など。
    • そうすれば、「許容可能」なマッチの生成に重点を置き、「考えられる最善」のマッチを生成するよりも処理を迅速化することができます。

例:許容範囲を使用したスキルベースのマッチングアルゴリズムにするためのシンプルな最適化戦略#

1.まずは、必須条件の属性とプロパティ(サービス品質(QoS)やゲームモード(O(n))など)によってチケットをバケット化します。 2.残りのチケットをプレイヤー数(O(n))でバケット化し、その後、それらのバケットをスキル(O(n log n))によってソートします。 3.マッチをビルドする際には、まずプレイヤー数とスキルの値が最も大きいチケットから処理を開始し、その後、マッチのビルド試行を開始します。マッチのビルド中には、ソートされた各プレイヤーバケットをイテレーションしながら、互換性のあるプレイヤーを生成中のマッチに取り込んでいきます(O(n))。 4.マッチが完成したら、それを申請に変換し、次のマッチのビルドを開始します。

このアルゴリズムは完璧なものではありませんが、O(n log n) によるソートの後に O(n) のマッチングアルゴリズムを実行しているので、O(n log n) アルゴリズムの例として参考にしてください。

運用時におけるログの制限#

ロギングは、計算処理の観点から見て全体的に高負荷です。このことは、システムやテクノロジーのいずれを問わず同様です。

詳細ロギングは、開発時、テスト時、および小規模なプレイテスト時においては有用ですが、運用レベルのチケットロードに対して実行する場合、特に O(n^2) のマッチメイキングロジックの内部でロギングを行う場合は、パフォーマンス上の大きなボトルネックになる可能性があります。

したがって、詳細ログはなるべく、関数や関数設定で切り替え可能なフラグの背後に配置することをお勧めします。

関数実行あたりのログが少ない場合は一般的に害はないのですが、チケットやチケット比較(O(n^2) 以上の規模になる処理)ごとにロギングを複数回実行すると、関数のパフォーマンスやマッチング時間にきわめて大きな影響を及ぼす可能性があります。

O(n) の n を制限する#

マッチメイキングアルゴリズムの実行時に処理する必要があるチケットの数をバケット化戦略によって減らすだけでなく、チケット数に対してハードリミットを設けることも、場合によっては有効です。

関数のパフォーマンスを著しく低下させる(たとえば、実行時間が一貫して 3000 ミリ秒以上になる)ようなチケット数がおおよその値として把握できている場合は、その関数が処理できるチケット数について、ハードリミットを設定したほうがよいかもしれません。

関数の実行時間を制限する#

関数の実行時間が 3000 ミリ秒を超えた場合、関数の結果は破棄されます。この制限があることを考慮すると、開発者は関数ロジックの内部にタイマーを追加して、少なくともその期間に生成できたマッチは把握できるようにするのがよいでしょう。これを行うには、関数の最大実行時間をおよそ 2500 ミリ秒に設定するようにしてください。なお、この措置は、その時間枠内に少なくとも一定数のマッチを一貫して生成できる場合にのみ効果を発揮します。

com.unity.ucg.qos パッケージを使用してコンパイルエラーが返された場合に Visual Studio 2019 のデバッガーを使用する方法#

Unity Quality of service(QoS)パッケージを Visual Studio Tools for Unity および Visual Studio 2019 と組み合わせて使用している場合、Unity Editor では発生しないコンパイルエラーが Visual Studio 2019 で返されることがあります。具体的には、次のエラーが表示されます:CS8385 "The given expression cannot be used in a fixed statement"

この問題は、VS/C# コンパイラー統合のバグとして確認されています。プロジェクトはエディターでは正常にビルドされますが、Visual Studio 2019 ではこのエラーが返されます。この問題に遭遇した場合は、バグが解決されるまでの間、次のいずれかの次善策を講じることをお勧めします。

1.「再生」ボタンを使用する代わりに、Visual Studio 2019 の「デバッグ / Attach Unity Debugger」を使用します。その際、デバッガーにアタッチする前に、Visual Studio 2019 でプロジェクトが正常にビルドされる必要はありません。 2.Visual Studio 2019 で、「ツール / オプション / Tools for Unity / Disable the full build of project」を「false」に設定します。この場合、コードにはエラーが表示されますが、プロジェクトは正常にビルドされます。