paint-brush
ゾンビ状態での古典的な機械学習とリカレントニューラルネットワークを使用したミッション生成@evlko
3,703 測定値
3,703 測定値

ゾンビ状態での古典的な機械学習とリカレントニューラルネットワークを使用したミッション生成

evlko15m2024/05/01
Read on Terminal Reader

長すぎる; 読むには

ローグライク ゲーム向けの従来の機械学習とリカレント ニューラル ネットワークを使用してミッションを生成する全体像を説明します。
featured image - ゾンビ状態での古典的な機械学習とリカレントニューラルネットワークを使用したミッション生成
evlko HackerNoon profile picture
0-item
1-item

おそらく、手続き型レベル生成についてはご存知でしょう。この投稿では、手続き型ミッション生成について取り上げます。ローグライク ゲーム向けの従来の機械学習とリカレント ニューラル ネットワークを使用してミッションを生成する全体像を説明します。


皆さん、こんにちは!私の名前は Lev Kobelev です。MY.GAMES のゲーム デザイナーです。この記事では、従来の ML とシンプルなニューラル ネットワークを使用した経験を共有し、手続き型ミッション生成を選択した理由と方法を説明します。また、Zombie State でのプロセスの実装についても詳しく説明します。


免責事項: この記事は情報提供/娯楽目的のみを目的としており、特定のソリューションを使用する場合は、特定のリソースの利用規約を慎重に確認し、法務担当者に相談することをお勧めします。

ミッション「ボックス」の基本:ウェーブ、スポーンなど

☝🏻 まず、用語について説明します。この文脈では、「アリーナ」、「レベル」、「ロケーション」は同義語であり、「エリア」、「ゾーン」、「スポーンエリア」も同様です。


さて、「ミッション」を定義しましょう。ミッションとは、特定のルールに従って敵が特定の場所に出現する、あらかじめ決められた順序のことです。前述のように、Zombie State では場所が生成されるため、「段階的な」体験は作成されません。つまり、あらかじめ決められたポイントに敵を配置するわけではありません。実際、そのようなポイントは存在しません。私たちの場合、敵はプレイヤーまたは特定の壁の近くのどこかに出現します。さらに、ゲーム内のすべてのアリーナは長方形であるため、どのミッションもどのアリーナでもプレイできます。


スポーン」という用語を紹介しましょう。スポーンとは、指定されたゾーン内のポイントに、事前に決められたパラメータに従って同じ種類の敵が複数出現することです。1 つのポイントに 1 体の敵。エリア内に十分なポイントがない場合は、特別なルールに従ってエリアが拡張されます。ゾーンはスポーンがトリガーされたときにのみ決定されることも理解しておくことが重要です。エリアはスポーン パラメータによって決定されます。以下では、プレイヤーの近くと壁の近くの 2 つの例を考えてみましょう。


最初のタイプのスポーンは、プレイヤーの近くです。プレイヤーの近くでの出現は、セクターによって指定されます。セクターは、外部と内部の 2 つの半径 (R と r)、セクターの幅 (β)、プレイヤーに対する回転角度 (α)、および敵の出現の望ましい可視性 (または不可視性) によって表されます。セクター内には、敵に必要な数のポイントがあり、そこから敵が出現します。

2 つ目のタイプのスポーンは壁の近くです。レベルが生成されると、各サイドにタグ (基本方向) が付けられます。出口のある壁は常に北にあります。壁の近くに敵が出現するかどうかは、タグ、タグからの距離 (o)、長さ (a)、ゾーンの幅 (b)、および敵の出現の望ましい可視性 (または不可視性) によって指定されます。ゾーンの中心は、プレイヤーの現在の位置を基準にして決定されます。

スポーンはウェーブで出現します。ウェーブとはスポーンが出現する方法、つまりスポーン間の遅延のことです。一度にすべての敵をプレイヤーにぶつけたくないからです。ウェーブはミッションに結合され、特定のロジックに従って次々に開始されます。たとえば、2 番目のウェーブは最初のウェーブの 20 秒後 (またはウェーブ内のゾンビの 90% 以上が殺された場合) に開始できます。つまり、ミッション全体を大きなボックスと見なすことができ、そのボックス内には中サイズのボックス (ウェーブ) があり、ウェーブ内にはさらに小さなボックス (スポーン) があります。

そのため、実際のミッションに取り組む前に、いくつかのルールをすでに定義しています。


  1. アクション感覚を一定に保つために、プレイヤーの近くの目に見えるポイントに通常のゾンビを頻繁に出現させるようにしてください。
  2. 出口を強調したり、プレイヤーを特定の側から押し出すために、主に壁の近くに遠距離戦闘の敵を出現させるように努める
  3. 時々、プレイヤーの目の前に、しかし見えない地点に特殊な敵を出現させます。
  4. プレイヤーからXメートル以内に敵を出現させない
  5. 同時にX体以上の敵を出現させない


ある時点では、約 100 個のミッションが準備できていましたが、しばらくすると、さらに多くのミッションが必要になりました。他のデザイナーと私は、さらに 100 個のミッションを作成するために多くの時間と労力を費やしたくなかったので、ミッションを生成するための迅速で安価な方法を探し始めました。

ミッション生成

ミッションの分解

すべてのジェネレーターは、一連のルールに従って動作し、手動で作成したミッションも、特定の推奨事項に従って実行されました。そこで、ミッション内のパターンに関する仮説を立て、そのパターンがジェネレーターのルールとして機能します。


✍🏻 本文中に出てくる用語の一部:

  • クラスタリングとは、特定のコレクションを重複しないサブセット (クラスター) に分割するタスクです。これにより、類似のオブジェクトは同じクラスターに属し、異なるクラスターのオブジェクトは大幅に異なるようになります。

  • カテゴリ特徴は、有限集合から値を取得し、数値表現を持たないデータです。たとえば、スポーン ウォール タグ (北、南など) などです。

  • カテゴリ特徴のコーディングは、事前に指定されたいくつかの規則に従って、カテゴリ特徴を数値表現に変換する手順です。たとえば、北 → 0、南 → 1 などです。

  • 正規化とは、数値特性を前処理して、範囲の違いに関する情報を失うことなく、共通のスケールに合わせる方法です。たとえば、オブジェクトの類似性を計算するために使用できます。前述のように、オブジェクトの類似性はクラスタリングの問題で重要な役割を果たします。


これらすべてのパターンを手動で検索するのは非常に時間がかかるため、クラスタリングを使用することにしました。ここで役立つのが機械学習です。機械学習はこのタスクをうまく処理します。


クラスタリングは N 次元空間で機能し、ML は特に数値で機能します。したがって、すべてのスポーンはベクトルになります。

  • カテゴリー変数はコード化された
  • すべてのデータは正規化された


つまり、例えば「北側の壁のくぼみ 2 メートル、幅 10、長さ 5 の領域にゾンビ シューター 10 体をスポーンする」というスポーンは、ベクトル [0.5, 0.25, 0.2, 0.8, …, 0.5] になります (←これらの数字は抽象的です)。


さらに、特定の敵を抽象型にマッピングすることで、敵セットの威力が軽減されました。まず、このようなマッピングにより、新しい敵を特定のクラスターに割り当てることが容易になりました。これにより、最適なパターンの数を減らすことも可能になり、結果として生成精度が向上しました。これについては後ほど詳しく説明します。

クラスタリングアルゴリズム


クラスタリング アルゴリズムには、K-Means、DBSCAN、スペクトル、階層など、さまざまなものがあります。これらはすべて異なるアイデアに基づいていますが、目的は同じです。つまり、データ内のクラスターを見つけることです。以下では、選択したアルゴリズムに応じて、同じデータに対してクラスターを見つけるさまざまな方法を示します。

K-Means アルゴリズムは、スポーンの場合に最も優れたパフォーマンスを発揮しました。


ここで、このアルゴリズムについて何も知らない人のために少し余談します (この記事はゲーム開発に関するものであり、ML の基礎に関するものではないため、厳密な数学的推論はありません)。K 平均法は、各特徴から割り当てられたクラスターの平均値までの二乗距離の合計を最小化することにより、データを K 個のクラスターに繰り返し分割します。平均は、クラスター内二乗距離の合計で表されます。


この方法については、次の点を理解することが重要です。

  • クラスターのサイズが同じになることは保証されません。ミッション内のクラスターの分布は不均一になる可能性があるため、これは私たちにとっては問題ではありませんでした。
  • データ内のクラスターの数を決定するわけではありませんが、入力として特定の K 数、つまり必要なクラスターの数が必要です。この数は事前に決定されている場合もあれば、決定されていない場合もあります。さらに、「最適な」クラスターの数を見つけるための一般に受け入れられている方法はありません。


2 番目のポイントをもう少し詳しく見てみましょう。

クラスターの数

エルボー法は、最適なクラスター数を選択するためによく使用されます。考え方は非常に単純です。アルゴリズムを実行し、1 から N まで K をすべて試します。ここで、N は適切な数です。このケースでは 10 でした。これ以上のクラスターを見つけるのは不可能でした。次に、各クラスター内の距離の二乗の合計 (WSS または SS と呼ばれるスコア) を計算します。これをすべてグラフに表示し、y 軸の値が大幅に変化しなくなるポイントを選択します。


説明のために、よく知られているデータセットであるアイリスの花のデータセットK を 1 から 10 まで変化させてアルゴリズムを実行し、上記の推定値が K に応じてどのように変化するかを見てみましょう。およそ K=3 で推定値の変化は止まります。これは、元のデータセットに存在したクラスの数とまったく同じです。

肘が見えない場合は、シルエット法を使用できますが、この記事の範囲外です。


上記および下記の計算はすべて、ML およびデータ分析用の標準ライブラリ (pandas、numpy、seaborn、sklearn) を使用して Python で実行されました。この記事の主な目的は技術的な詳細を説明することではなく、機能を説明することであるため、コードは公開していません。


各クラスターの分析


最適なクラスター数を取得したら、各クラスターを詳細に調査する必要があります。クラスターに含まれるスポーンと、それらが取る値を確認する必要があります。今後の生成で使用するために、各クラスターに独自の設定を作成しましょう。パラメーターは次のとおりです。


  • 確率を計算するための敵の重み。例えば、通常のゾンビ = 5、ヘルメットをかぶったゾンビ = 1。したがって、通常のゾンビの確率は 5/6、ヘルメットをかぶったゾンビの確率は 1/6 です。重みは操作に便利です。
  • 値の制限。たとえば、ゾーンの回転の最小角度と最大角度、またはその幅などです。各パラメータは独自のセグメントによって記述され、その値はどれも同じ確率で発生します。
  • 壁のタグやポイントの可視性などのカテゴリ値は敵の設定として記述され、重みによって表されます。


クラスター設定について考えてみましょう。これは言葉で説明すると、「単純な敵がプレイヤーの近くのどこか、短い距離、おそらくは目に見える場所に出現する」というものです。


クラスター 1 テーブル

タイプ

r

Rデルタ

回転

可視性

ゾンビ_コモン_3_5=4、ゾンビ_ヘビー=1

プレーヤー

10-12

1-2

0-30

30-45

表示=9、非表示=1


ここに 2 つの便利なトリックがあります:


  • 指定されるのは敵の固定数ではなく、その数が選択されるセグメントです。これにより、異なるクラスター内の異なる数の同じ敵を操作するのに役立ちます。
  • 指定されるのは外側の半径 (R) ではなく、内側の半径 (r) に対するデルタ (R-デルタ) であるため、R > r の規則が尊重されます。したがって、R-デルタはランダムな r に追加され、R-デルタ > 0 の場合は r+R-デルタ > r となり、すべてが適切であることを意味します。


これは各クラスターで実行されましたが、クラスターの数は 10 未満だったので、それほど時間はかかりませんでした。


クラスタリングに関する興味深い点


このトピックについてはまだ少ししか触れていませんが、学ぶべき興味深い内容はまだたくさんあります。ここでは、データの処理、クラスタリング、結果の分析のプロセスについてわかりやすく説明した記事をいくつか紹介します。



ミッションの時間


スポーン パターンに加えて、生成時にこのパラメータを使用するために、ミッション内の敵の総体力とその完了予定時間の依存性を調査することにしました。


手動ミッションを作成する過程では、チャプターの調整されたペース、つまり短い、長い、短い、また短い、といった一連のミッションを構築することが課題でした。プレイヤーの予想 DPS とその時間がわかっている場合、ミッション内の敵の総体力をどのように取得できるでしょうか?


💡 線形回帰は、線形依存関数を使用して、ある変数の別の変数または他の複数の変数への依存性を再構築する方法です。以下の例では、1 つの変数からの線形回帰のみを検討します: f(x) = wx + b。


以下の用語を紹介します。

  • HPはミッション内の敵の総体力です
  • DPSは1秒あたりのプレイヤーの予想ダメージです
  • アクションタイムは、プレイヤーがミッションで敵を破壊するのに費やす秒数です。
  • フリータイムとは、例えばターゲットを変更したり、
  • 予想されるミッション時間は、行動時間と自由時間の合計です


つまり、HP = DPS * アクション時間 + 自由時間です。マニュアルの章を作成するときに、各ミッションの予想時間を記録しました。次に、アクション時間を見つける必要があります。


予想されるミッション時間がわかっている場合は、アクション時間を計算し、それを予想される時間から引いて自由時間を得ることができます。自由時間 = ミッション時間 - アクション時間 = ミッション時間 - HP * DPS。この数値をミッション内の平均敵数で割ると、敵 1 体あたりの自由時間が得られます。したがって、残っているのは、予想されるミッション時間から敵 1 体あたりの自由時間への線形回帰を構築するだけです。

さらに、ミッション時間からのアクション時間の割合の回帰を構築します。


計算例を見て、なぜこれらの回帰が使用されるのかを見てみましょう。

  1. ミッション時間とDPSの2つの数字を30と70として入力します
  2. ミッション時間からアクション時間の割合の回帰を見て、答えは0.8です。
  3. アクション時間を30*0.8=6秒と計算します
  4. HPは6*70=420と計算されます
  5. ミッション時間から敵 1 体あたりの自由時間の回帰を確認し、答えを取得します。答えは 0.25 秒です。


ここで疑問が湧きます。なぜ敵の自由時間を知る必要があるのでしょうか? 前述のように、スポーンは時間順に並べられています。したがって、i 番目のスポーンの時間は、(i-1) 番目のスポーンのアクション時間とその中の自由時間の合計として計算できます。


ここで別の疑問が浮かびます。なぜ行動時間と自由時間の割合は一定ではないのでしょうか?


このゲームでは、ミッションの難易度はミッションの期間と関係があります。つまり、短いミッションは簡単で、長いミッションは難しくなります。難易度パラメータの 1 つは、敵ごとの自由時間です。上のグラフには複数の直線があり、それらの傾き係数 (w) は同じですが、オフセット (b) が異なります。したがって、難易度を変更するには、オフセットを変更するだけで十分です。b を増やすとゲームは簡単になり、減らすと難しくなり、負の数も許可されます。これらのオプションは、章ごとに難易度を変更するのに役立ちます。


私は、すべてのデザイナーが回帰の問題を詳しく調べるべきだと考えています。回帰は他のプロジェクトを分解する際に役立つことが多いからです。



新たなミッションの創出


これで、ジェネレータのルールを見つけることができたので、生成プロセスに進むことができます。


抽象的に考えると、どのミッションも数字のシーケンスとして表すことができ、各数字は特定のスポーン クラスターを反映します。たとえば、ミッション: 1、2、1、1、2、3、3、2、1、3。つまり、新しいミッションを生成するタスクは、新しい数字のシーケンスを生成することになります。生成後は、クラスター設定に従って各数字を個別に「拡張」するだけです。


基本的なアプローチ


シーケンスを生成する単純な方法を考えると、特定のスポーンが他のスポーンに続く統計的確率を計算できます。たとえば、次の図が得られます。

図の上部は、それが導くクラスター、つまり頂点であり、エッジの重みは、次のクラスターの確率です。


このようなグラフをたどっていくと、シーケンスを生成できます。ただし、このアプローチにはいくつかの欠点があります。たとえば、メモリが不足している (現在の状態しか認識できない) ことや、統計的にその状態に戻る確率が高い場合に、1 つの状態に「とどまる」可能性があることなどが挙げられます。


✍🏻 このグラフをプロセスとして考えると、単純なマルコフ連鎖が得られます。


リカレントニューラルネットワーク


ニューラル ネットワーク、特にリカレント ネットワークに目を向けてみましょう。リカレント ネットワークには、基本的なアプローチの欠点がありません。これらのネットワークは、自然言語処理タスクにおける文字や単語などのシーケンスをモデル化するのに優れています。簡単に言うと、ネットワークは、シーケンスの前の要素に基づいて次の要素を予測するようにトレーニングされます。

これらのネットワークがどのように機能するかについては、非常に大きなトピックであるため、この記事では説明しません。代わりに、トレーニングに必要なものを見てみましょう。


  • Lの長さのN個のシーケンスの集合
  • N個のシーケンスのそれぞれの答えはワンホットベクトル、つまり、C-1 個のゼロと 1 個の 1 で構成される長さ C のベクトルが答えを示します。
  • C は回答セットの累乗です。


N=2、L=3、C=5 の簡単な例です。シーケンス 1、2、3、4、1 を取り、その中に長さ L+1 のサブシーケンス [1、2、3、4]、[2、3、4、1] を探します。シーケンスを L 文字の入力と回答 (ターゲット) - (L+1) 番目の文字* に分割します。* たとえば、[1、2、3、4] → [1、2、3] および [4] です。回答をワンホット ベクトル [4] → [0、0、0、0、1] にエンコードします。

次に、Tensorflow または PyTorch を使用して、Python で簡単なニューラル ネットワークをスケッチします。以下のリンクを使用して、これがどのように行われるかを確認できます。残っているのは、上記のデータでトレーニング プロセスを開始し、待機することだけです。その後、本番環境に移行できます。


機械学習モデルには、精度などの特定の指標があります。精度は、正しく与えられた回答の割合を示します。ただし、データにクラスの不均衡がある可能性があるため、注意して見る必要があります。クラス不均衡がまったくない場合 (またはほとんどない場合)、ランダムよりも正確に回答を予測する場合、つまり精度 > 1/C の場合、モデルは適切に機能していると言えます。1 に近い場合は、非常にうまく機能しています。


私たちの場合、モデルは良好な精度を示しました。これらの結果の理由の 1 つは、敵をそのタイプとバランスにマッピングすることで達成されたクラスターの数が少ないことです。


興味のある方のために、RNN に関するその他の資料を以下に示します。


生成プロセス

発電機のセットアップ


訓練されたモデルは簡単に連載なので、エンジン(この場合は Unity)のアセットとして使用できます。それに応じて、ジェネレーターは API を介してモデルにアクセスし、反復的にシーケンスを作成します。結果は展開され、別の CSV ファイルに保存されます。


モデルを操作するために、Unity にカスタム ウィンドウが作成され、ゲーム デザイナーはここで必要なすべてのミッション パラメータを設定できます。

  • 名前
  • 間隔
  • 利用可能な敵、敵が徐々に出現する
  • ミッションの波の数と波間の体力の分布
  • 敵固有の重み付け修正。これにより、新しい敵など、特定の敵をより頻繁に選択できるようになります。
  • 等々


設定を入力したら、あとはボタンを押して、必要に応じて編集できるファイルを取得するだけです。はい、ゲーム中ではなく事前にミッションを生成して、微調整できるようにしたかったのです。

世代の段階

生成プロセスを見てみましょう。


  1. モデルはシーケンスを入力として受け取り、答え (i 番目のクラスターが次に来る確率のベクトル) を生成します。アルゴリズムはサイコロを振り、その数がエラー確率より大きい場合は最も可能性の高いものを採用し、そうでない場合はランダムにします。このトリックにより、創造性と多様性が少し追加されます。
  2. このプロセスは、指定された回数の反復まで継続されます。これは、手動で作成されたミッションのスポーン回数よりも多くなります。
  3. シーケンスは継続され、つまり、各番号がクラスターの保存されたデータにアクセスし、そこからランダムな値を受け取ります。
  4. データ内の健全性が合計され、期待される健全性より大きいものはすべてシーケンスから除外されます(その計算については上記で説明しました)
  5. スポーンは指定された体力分布に応じてウェーブに分割され、その後グループに分割されます (複数の敵が一度に表示されるように)。出現時間は、前のグループのスポーンのアクション時間と自由時間の合計として与えられます。
  6. ミッション準備完了!


結論

したがって、これはミッションの作成を数倍高速化するのに役立つ優れたツールです。さらに、数秒で既成のソリューションを入手できるようになったため、一部のデザイナーはいわゆる「ライターズ ブロック」の恐怖を克服するのにも役立ちました。


この記事では、ミッション生成の例を使用して、従来の機械学習手法とニューラル ネットワークがゲーム開発にどのように役立つかを説明しようとしました。最近では生成 AI への大きなトレンドがありますが、機械学習の他の分野も忘れないでください。それらにも多くの可能性があります。


この記事を読んでいただきありがとうございます。生成された場所でのミッションへのアプローチとミッションの生成の両方について理解していただけたら幸いです。新しいことを学ぶことを恐れず、自分自身を成長させ、良いゲームを作りましょう。


イラスト:shabbyrtist