/tech:チャンピオンの表現に柔軟性を与える

RiotAaronMike, Lucidaによる

/techはリーグ・オブ・レジェンドに使われているテクノロジーについて解説する、ネクサスの新シリーズです。ここにある内容に興味を持たれた方はライアットゲームズ・エンジニアリングブログ(英語ページ)もご覧いただければ、ゲームを支える各種テクノロジーについてさらに詳しい解説を読むことができます。

皆さん、こんにちは!今回は、LoLのコア・ゲームプレイチームのソフトウェアエンジニアである、AaronMikeとLucidaがお送りします!今日はチャンピオンたちの個性に一層の深みを与える、Contextual Action Component(コンテクスチュアル・アクション・コンポーネント:コンテクスト(文脈や状況)に応じたアクションの構成要素(コンポーネント)、略してCAC)についてお話しします。CACは、開発者がチャンピオンの反応をカスタマイズできるようにするシステムで、ゲーム内で起きる出来事に対して、チャンピオンたちにより自然な反応をさせることが可能になります。


システムの解説

まずは例として、ポッピーが一人でタウント(挑発)のエモート(コマンド)を使った場合と、黄金の翼を持つ味方のガリオの近くにいる時に使った場合の違いを比較してみましょう。


ポッピーのタウント、ガリオ不在

ポッピーのタウント、ガリオの前で

ポッピーが一人でいるときや関係性のないチャンピオンと一緒にいるときにタウントを行った場合は、彼女のタウントのVO(ボイスオーバー:セリフ)には汎用的に用いられるセリフの一つが選ばれます。しかし、ポッピーが味方のガリオの近くにいる時は冗談を言ってガリオをからかい、さらにポッピーのセリフが終わったあとにまだガリオが側にいると、ガリオもそれに反応して冗談を言い返します。今では当たり前のように感じますが、LoLがリリースされた2010年当時は、このようなやりとりを実現することは不可能でした。セリフにバリエーションを持たせる唯一の方法は、サウンドエンジンを使ってセリフをリストからランダムに選択することだけでした。そのため、特定の状況下で流れたセリフが違和感を生むことがないように、オーディオデザイナーは当たり障りのないセリフだけを使うように強いられていました。


たとえば、ラックスの“ きょうだい喧嘩? 久しぶりね!”というセリフは、彼女がガレンの側にいる時なら意味が通じますが、カタリナの側にいる時はどうでしょうか?意味が通じないですよね。LoLの古い音声再生システムでは適切な状況で特定のセリフを選択するという方法がなかったので、このようなセリフを使うことは禁止されていました。そのため、各チャンピオンの個性や他のチャンピオンとの関係性を表現できる貴重な機会が失われていたのです!

そこで登場したのがCACです。こうしたやり取りの中心をなすシステムとして、CACはポッピーやラックスが、「もっとも近くにいる味方は誰か?」、「どのアイテムを購入したか?」、「倒したチャンピオンは誰か?」といったリアルタイムの情報を“状況に応じて認識”できるようにします。さらには、パルスファイアケイトリンでペンタキルを獲得した時に特別なセリフを喋らせたり、ザヤとラカンにマップ内で甘い言葉を交わさせることもできるようになります。


内部の仕組み

CACはゲーム内の様々な場面に応じて異なるアクションを実行する、というシンプルな用途を目的としてデザインされています。システムの構造は以下のように表すことができます。

  • A situation(場面)
    • Rule(規則) 1
      • Conditions(状況)
      • Action(アクション)
    • Rule(規則) 2
      • Conditions(状況)
      • Action(アクション)
    • More rules(さらなる規則)

上記のsituation(場面)はゲーム内であらかじめ定義された構文のことで、KillChampion(チャンピオンをキル)、AttackBuilding(建造物を攻撃)、BuyItem(アイテムを購入)などがあります。ここ数年で、私たちは多くのsituationをLoLに追加しました。それぞれのsituationの中には複数のrule(規則)のリストを含むことが可能で、一つのruleの中には複数のcondition(状況)と一つのaction(アクション)が含まれています。situationが発生すると、その中にある複数のruleの中から、conditionを満たしているruleが選択され、対応するactionが実行されます。conditionを満たしているruleが複数ある場合は、リスト内の順番で先に来るものを優先するか、複数の中からランダムに選択するかを指定できます。Conditionとはあらかじめ定義された特定の状況を意味し、“自身の体力残量範囲”、“対象のチャンピオン名”、“マップ内の位置”、“スキルのレベル”などがあります。Audio Action(オーディオアクション)はself(自身)、allies(味方)、enemies(敵)、spectators(観戦者)などの聞き手に応じてセリフを選択することができます。

以下はプログラムカミールスキンのカミールがPROJECT ASHEスキンのアッシュをタウントする場合の例です。


LoLのコードベースの大部分がそうであるように、このシステムもC++で記述されていて、コンフィギュレーションには独自のGame Data Server (GDS) (英語ページ)を使用しています。チャンピオンが別のチャンピオンを倒した時に呼び出されるコードの一部を要約したものを以下に紹介します。


// チャンピオンが別のチャンピオンを倒すと呼び出される
void HandleChampionKillSituation(Champion* killer, Champion* victim, 
  uint8_t killerMultikill = 0)
{
  ContextualActionComponent& cac = killer->GetContextualActionComponent();

  // killer(倒した方のチャンピオン)がKillChampionのsituationを持っているかを確認
  const ContextualSituation* situation = cac.FindSituation(kKillChampion);
  if (situation != nullptr) {
    // 関連するfacts(条件)を設定
    ContextualFacts& facts = cac.GetFacts();
    facts.mKiller = killer;
    facts.mVictim = victim;
    facts.mKillerMultiKillSize = killerMultikill;

    // これらのキルのfactsに一致するruleを探す
    const ContextualRule* rule = situation->PickRule(facts);
    if (rule) {  // a qualified rule has been found
      if (rule->ExecuteAudioAction(facts)) {
        // 他のCACに、このkillerがこのイベントを実行したことを知らせる
        cac.NotifyAllCacsOfPlayedAction(rule->GetAudioSituationTrigger());
      }

      // 使用済みのfactsをリセット
      facts.mKiller = nullptr;
      facts.mVictim = nullptr;
      facts.mKillerMultiKillSize = 0;
    }
  }
}

上記のコードでは、場面に応じて実行すべきアクションをシステムがどうやって判断しているのかが示されています。PickRule関数は、すべてのconditionを満たすruleが見つかるまで、KillChampion situation内のruleを一つずつ確認していき、その後、対応するactionを実行します。


オーサリング

下のスクリーンショットでは、パルスファイアケイトリンを使ってペンタキルを獲得できる、高いスキルを持つ(またはラッキーな)プレイヤーのために私たちが設定したruleを示しています。


パルスファイアケイトリンが敵チャンピオンをキルするたびに、CACがこのKillChampion situation内のruleを確認していきます。このruleには、「もしキリングスプリーの5番目のキルなら、KillChampion3DPentakillのボイスラインをプレイヤー自身と敵に対して再生する」と指定されています。このruleには“occurences”(発生回数)の上限が3と指定されているところに注目です――occurrencesのスペルが間違ってますね(今度直します!)――これはつまり、ペンタキルは最初の3回しかセリフが再生されないことを意味しています。だって、さすがに4回目は少しうるさく感じますもんね。


改善されたこと

以前なら、オーディオはゲーム内の様々なシステムのイベントから直接実行されていました。イベントにはパーティクルの発生、アニメーション、スキルの使用、ユーザー入力などがありました。たとえば、プレイヤーがチャンピオンを動かすと、ゲームクライアントは“Champion_VO_MoveCommand”といった名前が付けられたオーディオイベントを発生させ、対応するオーディオクリップ(音声)を再生しようとします。以前のシステムではゲーム内のコンテクストを認識できなかったので、チャンピオンの反応をカスタマイズすることは不可能でした。

その時点で起こっているイベントに直接反応するだけでは、CACの上っ面をなでる程度のことしかできません。situationruleを組み合わせることで、キャラクターの反応をきめ細かくプログラムできるようになるのです。このシステムを導入する前でもある程度は特別な反応をさせることはできましたが、それはその反応を適当な周期でランダムに発生させていただけでした。たとえば、ザックには“ビッグになってみろよ! オイラみたいにな!”と“パワーだけじゃない、ルックスも大切だぜ?”の2つの汎用タウント音声が存在しています。彼がタウントを行った時は、このうちのどちらかがランダムに再生されていました。現在は、そのセリフがふさわしい状況でのみ再生されるように細かな調整が可能です。つまりランダムな抽選に任せるのではなく、それぞれの状況に対応したより細かなやり取りを実現することができるようになったのです。


ザヤとラカン

2016年の初頭、私たちはリーグ・オブ・レジェンドで初となるカップル・チャンピオンの開発を始めました。狙いは、ゲーム内でこの二人のチャンピオンにありきたりで特徴のないやり取りをさせるのではなく、恋仲らしい会話をさせることでした。ダンスの最中にラカンがザヤを抱えて持ち上げるのはどうだろう?ザヤが(甘えるように)ラカンを軽く小突くのは?ザヤに危機が迫るとラカンが警告を発するのは?

これらを実現するために、私たちはCACをアップグレードして、2つの新たなactionsituationを追加する必要がありました。


ザヤとラカン


アニメーション

当時のアニメーションシステムには、アニメーターが求めていたような二人がシンクロするダンスや、リコールのダンスを作成するために必要となる一部のコンテクストが実装されていませんでした。これを実現するために、私たちはチャンピオンのアニメーションをコントロールする、新たなタイプのactionをCACに追加しました。ザヤが何かをすると(または何もしていない放置時でも)、要求するアニメーション名を指定して、アニメーションシステムにPlayAnimation(アニメーションを実行)リクエストが行われます。私たちはこのフロー(プログラムの実行手順)に変化を加え、CACがこのリクエストを横からチェックして、コンテクストに対応するconditionが存在しないかを確認するようにしました。もし存在すれば、よりコンテクストにふさわしいアニメーションを選択します。その後、リクエストは通常どおりに送信されて、アニメーションシステムによって実行されます。


インタラクティブCAC

次のチャレンジはダンスでした。ザヤとラカンの二人が一緒にダンスを始めるタイミングをどうやって判断すればいいのでしょうか?これは、自分以外のチャンピオンがCACのactionを行うたびに実行される、新たなsituationを追加することで実現しました。actionが完了する、それがゲーム内の全CACに現在のゲーム内のコンテクストとともに通知され、それによってCACはリアクションが必要かどうかを判断します。


左から右へ:両者ともに放置状態。ザヤがソロでダンスを開始。ラカンがダンスを受け入れる。二人がシンクロしたダンスを披露。


状況に呼応するピン

他にも、ピンのリクエストの際にCACを通すことで、新たに素晴らしい機能が実現可能になりました。これによって、通常のピンだけでなく、恋人同士なら「危険!」ピンで“ ダーリン、気を付けて!”とか、「敵を見失った!」ピンなら“ いねぇぞ”といったセリフを喋らせることができるようになりました。


技術的な懸念


チート対策

CACのような新システムをリーグ・オブ・レジェンドに導入する際には、チートやハッキングが大きな懸念となります。ハッキングの中には、プレイヤーが対戦で有利になれるように、ゲームシステムが提供している以上の情報を収集しようとするものがあります。チーターはシステムを悪用して、このような情報を手に入れようとするかもしれません。たとえば、あなたのチームが近くの茂みの中に隠れていると、エリスが“蜘蛛の本能が疼いているわ…”というセリフを喋ると想像してみてください。こういう悪用を防ぐために、CACはあなたのクライアントが把握している情報のみにしか反応しないようにデザインされています(言い換えれば、プレイヤーのあなたが見えているものしか見えません)。


パフォーマンス

私たちは、開発者たちがLoLのキャラクターに命を吹き込めるようにできる限りの自由度とユーザビリティーを提供したいと考えていますが、同時に、ゲームをプレイしているのが液体窒素冷却のモンスターPCでも、低スペックのノートPCでも、パフォーマンスの低下は避けたいと考えています。私たちが常に気にかけているのは、できる限りシステムを軽くすることと、プロセスのどの時点でも十分なパフォーマンスを得られるようにすることです。私たちはこれを、以下のようにコーディングの選択とベストプラクティスを組み合わせることで実現しています。

  1. Situationはストリングハッシュをキータイプとしたハッシュマップに格納されています。この構造なら、situationをCACオブジェクトから素早く取得できます。チャンピオンが特定のsituationのデータを持っていないなら、ハンドル関数はシンプルにそれを返します。各チャンピオンは関連するsituationを少ししか持っていないので、ほとんどのsituationは処理コストがほとんどかかりません。
  2. CACに関しては、汎用性の高いsituationよりも専用性の高いsituationを優先します。普通なら、一度に複数の問題を解決できる「汎用性の高い再利用可能な解決策」が好まれますが、CACは特殊なケースです。より汎用性の高いsituationには、より多くのruleが含まれ、その一つひとつにCPUが処理する必要のある複数のconditionが含まれます。汎用性の高い一つのsituationを複数の専用situationに分割すれば、ruleの数が減ってパフォーマンスを向上させられます。situationがruleなしで直接結果を返すことも可能です。たとえば、私たちはKillChampion(チャンピオンをキル)、KillTurret(タワーを破壊)、KillNeutralMinion(中立モンスターをキル)、KillWard(ワードを破壊)の4つの専用のkill situationを用意しています。通常は、KillChampionがもっともバリエーションが多いですが、一試合に数回しか発生しません。KillNeutralMinionはもっともバリエーションが少ないものの、一番たくさん発生します。もしすべてのkill situationにKillTarget(対象をキル)のような汎用性の高いsituationを使用したなら、これら4つの対象のうちの1つが倒されるたびに、大量のruleのリストをパース(分析処理)していく必要が生まれます。
  3. シンプルでありながら重要なfactsまたはconditionを先に優先してチェックします。これらのconditionのうち一つも合致するものがなければ、プロセス内の他の複雑なチェックをスキップすることができます。
    1. チャンピオンがセリフを喋っているならオーディオのsituationをスキップします。LoLでは同一のチャンピオンの複数のセリフが同時に再生されないようになっています。ここに大きな最適化の余地があります。もしキャラクターが喋っているとCACが気付けば、新たなオーディオのsituationは無視できます。エモートを連続使用した場合でも、一旦チャンピオンが喋りだすとプロセスのほとんどがスキップされるので、CACは非常に効率的です。
    2. 他にも重要なconditionとして「situationのクールダウン」があります。チャンピオンが特定のsituationを実行後は少しの間そのsituationに反応すべきではないので、それを処理する必要はありません。
  4. 可能なら発生頻度の高いsituationは避けます。もし、あるsituationが頻繁に発生するものである場合、パフォーマンス低下を避ける方法がいくつか存在しています。
    1. situationにクールダウンを設けて、そのsituationが発生するたびにゲームクライアントが毎回チェックすることを避ける
    2. 頻度の高いsituationはruleを少なくして、より速く実行できるようにする

最後に

CACのおかげで、オーディオやアニメーションといったシステムが、ゲーム内のコンテクストをより強く意識したものになり、クリエーターたちが新たな可能性を試せるようになりました。これによりチャンピオンの個性の表現をこれまで以上に豊かにして、リーグ・オブ・レジェンドの世界をさらに拡大していくことができます。そして、ゲームに追加するセリフの一つひとつが、プレイヤーを心から愉しませ、お気に入りのチャンピオンとの距離を近づける新たな機会として活用できるようになりました。



2 months ago