手続き型の生成、ゲームの更新、バタフライ効果


10

注:数日前にStack Overflowでこれを尋ねましたが、ビューが非常に少なく応答がありませんでした。代わりにgamdev.stackexchangeで尋ねるべきだと考えました。

これは、以前に生成されたコンテンツを壊すことなく、複数のリリース後更新を通じて手続き型生成システムを維持することに関する一般的な質問/アドバイスです。

ゲームの手続き型コンテンツを作成するときに、「バタフライ効果」の問題を回避するための情報と手法を見つけようとしています。シードされた乱数ジェネレータを使用する場合、乱数の繰り返しシーケンスを使用して、再現可能な世界を作成できます。一部のゲームは、生成された世界を生成後にディスクに保存するだけですが、手続き型生成の強力な機能の1つは、同じ方法で領域を再作成するために数列の再現性に複数回頼ることができるということです。持続。私の特定の状況の制約のため、永続性を最小限に抑える必要があり、純粋にシードされた濃度にできるだけ依存する必要があります。

このアプローチの主な危険性は、手続き型生成システムのわずかな変更でさえ、世界全体を変更する蝶の影響を引き起こす可能性があることです。これにより、プレイヤーが探索している世界を破壊することなくゲームを更新するのは非常に難しくなります。

この問題を回避するために私が使用してきた主な手法は、複数のフェーズで手続き型生成を設計することです。各フェーズには、独自のシード乱数ジェネレーターがあります。つまり、各サブシステムは自己完結型であり、何かが壊れても、世界のすべてに影響を与えることはありません。ただし、これはゲームの孤立した部分であっても、依然として「破損」の可能性が多いようです。

この問題に対処するもう1つの方法は、コード内でジェネレーターの完全なバージョンを維持し、特定のワールドインスタンスに適切なジェネレーターを使い続けることです。これは私にとってはメンテナンスの悪夢のように思えますが、誰かが実際にこれを行っているかどうか知りたいです。

したがって、私の質問は、特にバタフライ効果のこの問題に対処するための、特にリリース後のゲームアップデートのコンテキストでの一般的なアドバイス、テクニック、およびデザインパターンの要求です。(うまくいけば、それは広すぎる質問ではありません。)

現在Unity3D / C#で作業していますが、これは言語にとらわれない質問です。

更新:

返信ありがとうございます。

ますます静的データが最良かつ最も安全なアプローチであるように見えます。また、大量の静的データを保存することはオプションではない場合、生成された世界で長いキャンペーンを行うには、使用されるジェネレーターの厳密なバージョン管理が必要になります。私の場合の制限の理由は、モバイルベースのクラウドの保存/同期の必要性です。私の解決策は、重要な事柄に関する少量のコンパクトデータを格納する方法を見つけることかもしれません。

ストームウィンドの「ケージ」の概念は、物事を考える上で特に有用な方法だと思います。ケージは基本的には再シードポイントであり、小さな変更、つまりバタフライのケージの実行に伴う影響を防ぎます。


この質問は範囲が広すぎるため、トピック外として終了するよう投票します。
Almo

私はそれが非常に広いことに気づきました。代わりにgamedevフォーラムか何かを試すことをお勧めしますか?質問をより具体的にする方法は実際にはありません。私はこの分野で多くの経験を積んでいて、私には思いつかなかったいくつかの狡猾なトリックがある人から聞いてくれることを望んでいました。
null

2
アルモは間違っています。それはまったく広くありません。これは優れた質問であり、適切な回答を提供するのに十分な範囲です。これは、手続き型の多くの人がよく考えていることだと思います。
エンジニア、

回答:


8

ここでベースをカバーしたと思います:

  • 変更による波及を制限するために、複数のジェネレーターを使用するか、間隔を置いて再シードします(空間ハッシュを使用するなど)。これはおそらく化粧品のコンテンツで機能しますが、指摘したように、1つのセクションに含まれる破損を引き起こす可能性があります。

  • 保存ファイルで使用されているジェネレーターのバージョンを追跡し、適切に応答する。「適切」の意味は...

    • 以前のすべてのバージョンのジェネレーターの履歴をゲームの実行可能ファイルに保存し、保存に一致するバージョンを使用します。これにより、プレイヤーが古いセーブを使い続けると、バグの修正が難しくなります。
    • この保存ファイルが古いバージョンのものであることをプレーヤーに警告し、別の実行可能ファイルとしてそのバージョンにアクセスするためのリンクを提供します。キャンペーンが数時間から数日続くゲームに適しています。数週間以上プレーする予定のキャンペーンには適していません。
    • n実行可能ファイルには最新のジェネレーターバージョンのみを保持します。保存ファイルがこれらの最新バージョンのいずれかを使用している場合は、保存ファイルを最新バージョンに更新(提供)します。これは適切なジェネレーターを使用して、古い状態をリテラル(または、非常に類似している場合は同じシード上の新しいジェネレーターの出力からのデルタ)にアンパックします。これ以降の新しい状態は、最新のジェネレーターからのものです。ただし、長期間プレイしないプレイヤーは取り残される可能性があります。そして最悪の場合、ゲームの状態全体をリテラル形式で保存することになり、その場合も同様です...
  • 生成ロジックを頻繁に変更することが予想され、以前のバージョンとの互換性を壊したくない場合は、ジェネレーターの決定論に依存せず、状態全体を保存ファイルに保存してください。(すなわち、「それを軌道から核にかけなさい。それは確かにする唯一の方法です」)


生成ルールを作成した場合、生成を元に戻す方法はありますか?IEでは、ゲームの状態を考慮して、これをシードに戻すことができますか?データで可能であれば、プレーヤーを別のゲームバージョンにリンクする代わりに、古いシステムでシードからワールドを生成し、生成された状態を使用して新しいジェネレーターのシードを生成する更新ユーティリティを提供できます。ただし、変換の待機をプレーヤーに警告する必要がある場合があります。
ジョー

1
これは一般的には不可能です。古いジェネレーターと同じ出力を提供する新しいジェネレーターのシードが存在することさえ保証されていません。通常、これらのシードには約64ビットが含まれますが、ゲームがサポートできる可能性のある世界の数は2 ^ 64を超える可能性が高いため、各ジェネレーターはこれらのサブセットのみを生成します。ジェネレーターを変更すると、レベルの新しいサブセットが発生する可能性が高く、前のジェネレーターのセットとほとんどまたはまったく交差しない場合があります。
DMGregory

「正しい」答えを選ぶのは大変でした。簡潔であり、主要な問題を明確に要約したので、これを選びました。ありがとう。
null

4

単一数発生器から決定論的に保つための簡単な十分なはずです- -のではなく、このようなバタフライエフェクトの主な供給源は、間違いなく数世代ではない使用のクライアントコードでこれらの数字の。コードの変更は、物事を安定させるための真の課題です。

コード:ユニットテストマイナーな変更が他の場所で意図せずに発生しないようにするための最良の方法は、ビルドにすべての生成的側面の完全なユニットテストを含めることです。これは、1つの変更が他の多くに影響を与える可能性があるすべてのコンパクトコードに当てはまります。単一のビルドで何が影響を受けたかを確認できるように、すべてのテストが必要です。

番号:周期的なシーケンス/スロットすべてに対応する1つの番号ジェネレーターがあるとします。それは意味を割り当てません、それは単に他のPRNGのように順番に数字を吐き出すだけです。2つの実行で同じシードが与えられた場合、同じシーケンスが得られますか?次に、物事を考えて、ランダムな値を定期的に提供する必要があるゲームの30の側面があると判断しました。ここでは、30スロットのサイクリングシーケンスを割り当てます。たとえば、シーケンスの最初の数値はすべてラフな地形レイアウトで、2番目の数値は地形の摂動です...など... 10番目の数値ごとに、AI状態に多少のエラーが追加されてリアルになります。あなたの期間は30です。

10を超えると、20のスロットが解放され、ゲームの設計が進むにつれて他の側面に使用できるようになります。ここでのコストはもちろん、スロットが現在使用されていない場合でもスロット11〜30の番号を生成する必要があることです。つまり、期間を完了して、次の1〜10のシーケンスに戻る必要があります。CPUコストはかかりますが、(空きスロットの数によっては)わずかなはずです。もう1つの欠点は、開発プロセスの最初の段階で使用可能にしたスロットの数に最終的な設計を確実に収容できるようにする必要があることです。最初から割り当てる数が多いほど、「空の」スロットが多くなります。物事を機能させるために、潜在的にそれぞれを通過する必要があります。

これの影響は次のとおりです。

  • あなたはすべての数を生成する1つのジェネレータを持っています
  • 数値を生成する必要があるアスペクトの数を変更しても、決定論には影響しません(期間がすべてのアスペクトに対応するのに十分な大きさである場合)

もちろん、ゲームが一般に利用できない期間が長くなります。つまり、いわばアルファ版です。そのため、プレーヤーに影響を与えることなく、30から20アスペクトに削減することができます。あなたは割り当てられていた方法で開始時にあまりにも多くのスロットを。もちろん、これはいくつかのCPUサイクルを節約します。しかし、良いハッシュ関数(自分で書くことができます)はとにかく高速であることを覚えておいてください。そのため、追加のスロットを実行する必要があるため、コストがかかることはありません。


こんにちは。それは私がやっていることに似ているように思えます。私は通常、最初のワールドシードに基づいて、事前にサブシードの束を生成します。最近、私は長い配列のノイズの事前生成を開始し、各「スロット」は単にその配列へのインデックスになっています。このようにして、各サブシステムは適切なシードを取得し、分離して動作することができます。別の優れた手法は、x、y座標を使用して各場所のシードを生成することです。私は、このスタックページに陶酔の答えからコードを使用しています: programmers.stackexchange.com/questions/161336/...
nullの

3

PCGでの永続性が必要な場合は、PCGコード自体をデータとして扱うことをお勧めします。通常のコンテンツや生成されたコンテンツを持つリビジョン間でデータを永続化するのと同じように、リビジョン間でデータを永続化したい場合は、ジェネレーターを永続化する必要があります。

もちろん、最も一般的なアプローチは、生成データを静的データに変換することです。

PCGゲームでは永続性が異常であるため、多くのジェネレーターバージョンを保持しているゲームの例はわかりません。そのため、permadeathがPCGと密接に関連していることがよくあります。ただし、同じゲーム内に、同じタイプの複数のPCGの例がたくさんあります。たとえば、Unangbandにはダンジョンルーム用の独立したジェネレータが多数あり、新しいジェネレータが追加されても、古いジェネレータは引き続き同じように動作します。それが保守可能かどうかは、実装次第です。それを保守可能に保つ1つの方法は、スクリプトを使用してジェネレータを実装し、ゲームコードの残りの部分でそれらを分離しておくことです。


これは賢いアイデアであり、単に異なるジェネレータを異なる領域に使用することです。
null

2

私は約30000平方キロメートルの面積を維持し、約100万の建物やその他のオブジェクトを保持しているだけでなく、その他のものをランダムに配置しています。屋外でのシミュレーション 保存されるデータは約4 GBです。収納スペースがあるのはラッキーですが、容量に制限はありません。

ランダムは制御されていないランダムなものです。しかし、少しケージに入れることもできます。

  • 開始と終了を制御します(他の投稿で述べたように、シード番号と生成された番号の数)。
  • たとえば、その数値空間を制限します。0から100の間の整数のみを生成します。
  • 値を追加して、その数値空間をオフセットします(例:100 + [0から100までの生成された数値]は、100から200までの乱数を生成します)
  • 拡大縮小する(例:0.1を掛ける)
  • そして、その周りに様々なケージを適用します。これは、世代の一部を削減、廃棄することです。例えば。2次元空間で生成する場合は、数値ペアの上に長方形を置き、外にあるものをスクラップできます。または円、または多角形。3D空間の場合、たとえば、球または他の形状の内部に存在するトリプレットのみを受け入れることができます(現在は視覚的に考えていますが、これは必ずしも実際の視覚化や配置とは関係ありません)。

それだけです。ケージは、残念ながらデータも消費します。

フィンランド語のHajota ja hallitseには格言があります。Divide and conquerに変換します。

私は細部の正確な定義という考えをすぐに放棄しました。ランダムは自由を望んでいるので、自由を得ました。蝶を飛ばそう-檻の中。代わりに、ケージを定義する(そしてメインテイン!!)ための豊富な方法に焦点を当てました。彼らがどんな車であるかは関係ありません、それらが青かダークブルーである限り(退屈な雇用主は一度言った:-))。「ブルーまたはダークブルー」は、ここでは(非常に小さい)ケージであり、色の次元に沿っています。

数値空間を制御および管理するために、何が管理可能ですか?

  • ブールグリッドは(ビットが小さい!)
  • コーナーポイントは
  • 木構造のように(=「ケージを保持するケージ」に続く)

メンテナンスとバージョンの互換性について...
:if version = n then
:elseif version = m then ...
はい、コードベースは大きくなります:-)。

身近なこと。先に進む正しい方法は、分割して征服するための豊富な方法を定義し、その上でいくつかのデータを犠牲にすることです。次に、可能であれば、(ローカルで)ランダム化の自由を与えます。制御することが重要ではありません。

DMGregoryによって提案されたおかしい "nuke it fom orbit"と完全に互換性がないわけではありませんが、おそらく小さく正確な核を使用しますか?:-)


ご回答有難うございます。これは、保守するのに非常に大きな手順領域のように聞こえます。このように広い領域で、多くのストレージにアクセスできる場合でも、すべてを単純に格納することはまだ不可能です。バージョン化されたジェネレーターは今後の方向に向かわなければならないようです。ですから:)
null

すべての答えの中で、私はこれについて最も考えています。少し哲学的なものの説明を楽しんだ。アイデアを説明するときに「ケージ」という言葉が非常に役立つと思います。ありがとうございました。蝶をケージに入れて飛ばしてみましょう:)
null

PS私はあなたがどのゲームに取り組んでいるのか本当に知りたいです。その情報を共有できますか?
null

数値空間についてもう1つ追加できます。常にゼロに近い値を維持することには価値があります。あなたはおそらくすでに知っていました。ゼロに近いと、数値の精度が最高になり、ビット数が最も少なくなります。常にゼロに近い数値の束全体をいつでもオフセットできますが、そのために必要なのは単一の数値のみです。同様に、オフセットを使用して、遠くの計算をゼロに近づけることができます(ほとんど言う必要があります)。- Stormwindの
Stormwindの

以前を評価して、小さな車両の動き、0.01の1フレーム増分[メートル、単位]を考えます。32ビットの数値精度で10000.1 + 0.01を正確に計算することはできません(単一)が、0.1 + 0.01は計算できます。したがって、「アクション」が遠く離れた場所(山の後ろ:-)で行われる場合は、そこに移動せず、代わりに山をあなたに移動します(10000で移動すると、今は0.1になります)。収納スペースにも有効です。互いに近い数値の格納に貪欲になる可能性があります。それらの共通部分を一度に保存し、バリエーションを個別に保存します-ビットを節約できます!リンクは見つかりましたか?;-)
Stormwind 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.