アドベンチャーゲームのさまざまな状態のコーディング


12

私はアドベンチャーゲームを計画していますが、ストーリーの進行状況に応じてレベルの動作を実装する正しい方法がわかりません。

私のシングルプレイヤーゲームは、プレイヤーがゲーム内のさまざまなポイントで町の人々と対話しなければならない巨大な世界を特徴としています。ただし、ストーリーの進行状況に応じて、プレイヤーにはさまざまなものが提示されます。たとえば、ギルドリーダーは町の広場から市内のさまざまな場所に場所を変更します。ドアは、特定のルーチンを終了した後の1日の特定の時間にのみロック解除されます。特定のマイルストーンに到達した後にのみ、異なるスクリーン/トリガーイベントが発生します。

私は最初にswitch {}ステートメントを使用してNPCの発言内容または検索対象を決定し、グローバルgame_state変数の状態を確認した後にのみクエストの目的を相互作用可能にすることを考えました。しかし、オブジェクトの動作を変更するために、さまざまなゲームの状態やスイッチケースにすぐに出くわすことに気付きました。そのswitchステートメントはデバッグするのも非常に難しく、レベルエディターで使用するのも難しいかもしれません。

したがって、複数の状態を持つ単一のオブジェクトを持つのではなく、単一の状態を持つ同じオブジェクトの複数のインスタンスを持つ必要があると考えました。そうすれば、レベルエディターのようなものを使用する場合、NPCのインスタンスを、彼が登場する可能性のあるすべての異なる場所に配置することができます。また、会話状態ごとのインスタンスも配置できます。しかし、それは、レベルの周りに浮かぶ非アクティブで不可視のゲームオブジェクトがたくさんあることを意味します。これは、メモリの問題か、レベルエディタで見づらいかもしれません。

または、単純に、ゲームの状態ごとに同一だが別々のレベルを作成します。これは最もクリーンでバグのない方法ですが、レベルの各バージョンが実際に同一であることを確認する大規模な手動作業のように感じます。

私のすべての方法は非常に非効率だと感じているので、私の質問を要約すると、ストーリーの進行状況に応じてレベルの動作を実装するためのより良いまたは標準化された方法はありますか?

PS:私はまだレベルエディターを持っていません-JME SDKのようなものを使用するか、自分で作成することを考えています。

回答:


9

この場合に必要なのは、状態設計パターンだと思います。各ゲームオブジェクトのインスタンスを複数持つ代わりに、単一のインスタンスを作成しますが、その動作を別のクラスにカプセル化します。考えられる動作ごとに1つずつ、複数のクラスを作成し、すべてのクラスに同じインターフェースを与えます。1つをゲームオブジェクトに関連付け(初期状態)、条件が変わったとき(マイルストーンに到達したとき、時刻が過ぎたときなど)、そのオブジェクトの状態を切り替えます(つまり、ゲームロジックに応じて別のオブジェクトに関連付けます)該当する場合はそのプロパティを更新します。

状態インターフェースがどのように見えるかの1つの例(完全に構成されています-このスキームが提供する制御レベルを示すためだけです):

interface NPCState {
    Scene whereAmI(NPC o);
    String saySomething(NPC o);
}

そして、2つの実装クラス:

class Busy implements NPCState {
    Scene whereAmI(NPC o) {
        return o.getWorkScene();
    }
    String saySomething(NPC o) {
        return "Can't talk now, I'm busy!";
    }
}

class Available implements NPCState {
    Scene whereAmI(NPC o) {
        return TAVERN;
    }
    String saySomething(NPC o) {
        String[] choices = o.getRandomChat();
        return choices[RANDOM.getInt(choices.length)];
    }
}

状態の切り替え:

// The time of day passed from "afternoon" to "evening"
NPCState available = new Available();
for ( NPC o : list ) {
    Scene oldScene = o.state.whereAmI(o);
    o.state = available;
    Scene newScene = o.state.whereAmI(o);
    moveGameObject(o, oldScene, newScene);
    ...

重要なNPCにはカスタム状態がある場合があり、状態選択ロジックはよりカスタマイズ可能であり、ゲームのさまざまなファセットに対して異なる状態を持つことができます(この例では、場所とチャットの両方を伝えるために単一のクラスを使用しましたが、それらと多くの組み合わせを行います)。

これはレベルエディターでもうまく機能します。レベルの「グローバル」状態を切り替える単純なコンボボックスを使用し、その状態で表示したいゲームオブジェクトを追加および再配置できます。ゲームエンジンは、オブジェクトが正しい状態になったときにそのオブジェクトを実際にシーンに「追加」するだけですが、そのパラメーターはユーザーフレンドリーな方法で編集可能です。

(免責事項:私はゲームエディターの実世界での経験がほとんどないため、プロのエディターがどのように機能するかについて自信を持って言うことができますが、状態パターンについての私のポイントは引き続き保持されます。リソース。)


ご存知のとおり、このStateデザインパターンを、私が説明した連想配列と組み合わせることができます。ここで説明したように状態オブジェクトをコーディングし、次に提案した連想配列を使用して異なる状態オブジェクトを選択できます。
12

同意します。ゲームをエンジンから分離することも良いことです。また、ゲームロジックをハードコーディングすると、ゲームロジック間の結合が強化されます(再利用の可能性が減ります)。ただし、意図した動作の複雑さによっては、すべてを「ソフトコード化」しようとすると、不要な混乱が生じる可能性があるため、トレードオフがあります。この場合、混合アプローチが望ましい場合があります(つまり、「汎用」状態遷移ロジックがありますが、カスタムコードも組み込むことができます)
-mgibsonbr

私が理解しているように、NPCStateとGameStateの間には1対1のマッピングがあります。次に、NPCを配列に入れて繰り返し処理し、ゲームの状態の変化が観察されたときに新しいNPCStateを割り当てます。NPCStateは、送信されるすべてのdiff NPCの処理方法を認識できる必要があります。したがって、本質的にNPCStateには、特定の状態のすべてのNPCの動作が含まれます。すべてのビヘイビアが単一のNPCStateにきれいに保存され、ゲームエディタの実装にきれいにマッピングされるのが好きですが、NPCStateがかなり巨大になります。
カーディン

ああ、私はあなたを誤解したと思います。オブザーバーを含めるように少し変更しました。したがって、状態を共有できるCrowd NPCなどの非常に汎用的なものを除き、すべてのdiff NPCに対して1つのdiff NPCStateです。ゲームの状態ごとに、NPCは自身とそのNPCStateをオブザーバーに登録します。したがって、オブザーバーは、どのNPCが登録されているかを正確に把握して、どのゲーム状態の動作を変更し、単純にそれらを繰り返し処理します。また、ゲームエディター側では、ゲームエディターはレベル全体の状態を変更するためにObserverに信号を渡すだけです。
カルダン

1
はい、それがアイデアです!重要なNPCには多くの状態があり、状態の移行は主に完了したマイルストーンに依存します。ジェネリックNPCは時々マイルストーンに反応することもあり、選択された状態が内部プロパティに依存することもあります(すべてのNPCがデフォルトの初期状態を持っているとしましょう。通常の状態切り替えサイクル)。
mgibsonbr

2

私が検討する選択肢は、個々のオブジェクトを異なるゲーム状態に応答させるか、異なるゲーム状態で異なるレベルを提供することです。これら2つの間の選択は、ゲーム内で正確に何をしようとしているかによって異なります(異なる状態は何ですか?状態間でゲームがどのように移行するかなど)。

いずれにせよ、状態をゲームコードにハードコーディングすることでそれを行いません。NPCオブジェクトの大規模なswitchステートメントではなく、データファイルから連想配列に読み込まれたNPC動作の代わりに、その連想配列を使用して、関連付けられた状態に対して異なる動作を実行します。

if (state in behaviors) {
  behaviors[state]();
}

そのデータファイルは一種のスクリプト言語でしょうか?プレーンテキストデータファイルでは、動作を説明するには不十分だと思います。とにかく、動的にロードする必要があるのは正しいことです。ゲームエディターを使用して有効なJavaコードを生成することは考えられません。間違いなく、ある程度解析する必要があります。
カーディン

1
まあそれは私の最初の考えでしたが、mgibsonbrの答えを見た後、さまざまな動作を別々のクラスとしてコーディングし、データファイルでどの動作クラスがどの状態に対応するかを言うことができることに気付きました。実行時にそのデータを連想配列にロードします。
ジョッキング

ああ...確かに簡単ですいや!:D Luaハハのようなものを埋め込むシナリオと比較して
..-カーディン

2

オブザーバーパターンを使用してマイルストーンの変更を探すのはどうですか?変更が発生した場合、一部のクラスはこれを認識し、たとえば、npcに対して行う必要がある変更を処理します。

前述の状態設計パターンの代わりに、戦略パターンを使用します。

NPCがキャラクターと対話できる方法がn個あり、mが自分の位置にいる可能性がある場合、最大(m * n)+1個のクラスを設計する必要があります。戦略パターンを使用すると、n + m + 1クラスになりますが、これらの戦略は他のnpcsでも使用できます。

そのため、マイルストーンを処理するクラスと、このクラスを監視し、npcまたは敵、または変更する必要があるものを処理するクラスが存在する可能性があります。オブザーバーが更新された場合、オブザーバーは自分が支配するインスタンスに何かを変更する必要があるかどうかを判断します。たとえば、NPCクラスは、コンストラクターで、NPCマネージャーに更新する必要がある場合と更新する必要があるものを通知します...


Observerパターンは興味深いようです。国家オブザーバーに登録することは、NPCのすべての責任を明確に残すことができると思います。Strategyパターンは、Unity EngineのTriggerとAIの振る舞いによく似ています。これは、diffゲームオブジェクトの動作を共有するために使用されます(私は思う)。実現可能だと思われます。賛否両論は今のところわからないが、Unityも同じ方法を使用しているので、やや心強い。.–
Cardin

私はこれらの2つのパターンを数回使用したので、短所について話すことはできません:-/しかし、単一の責任とすべての戦略をテストする可用性の場合には良いと思います:)多くの異なるクラスで戦略を使用し、それを使用するすべてのクラスを見つけたい場合。
TOAOGG

0

指定されたアプローチはすべて有効です。それは、あなたがどんな瞬間にいるかによって異なります。多くのアドベンチャーまたはMMOは、これらの組み合わせを使用します。

たとえば、重要なイベントがレベルの大部分を変更する場合(たとえば、借金取りがアパートを一掃し、その中の全員が逮捕される場合)、通常、部屋全体を同じように見える2番目の部屋に置き換える方が簡単です。

OTOH、キャラクターがマップを歩き回り、さまざまな場所でさまざまなことを行う場合、多くの場合、単一の俳優がさまざまな行動オブジェクトを介して回転します(たとえば、まっすぐ歩く/会話なしvsここに立つ/ミッチの死についての会話)目的が達成されている場合は「隠されている」。

ただし、通常、手動で作成したオブジェクトの複製を作成しても問題は発生しません。いくつのオブジェクトを作成できますか?ゲームがループオーバーできるより多くのオブジェクトを作成でき、それらの「隠された」プロパティを見てスキップすると、エンジンが遅すぎます。それで、私はそれについてあまり心配しません。多くのオンラインゲームは実際にこれを行います。特定のキャラクターやアイテムは常に存在しますが、対応するミッションを持っていないキャラクターには表示されません。

アプローチを組み合わせることもできます。アパートの建物に2つのドアを設置します。1つは「借金取りの前」のアパートへ、1つは後のアパートへと続きます。廊下に入ると、ストーリーの進行に適用されるもののみが実際に表示されます。そうすれば、「アイテムがストーリーの現在のポイントで表示される」ための一般的なメカニズムと、単一の目的地を持つドアを持つことができます。代わりに、交換可能なビヘイビアを持つことができるより複雑なドアを作成することもできます。その1つは「フルアパートメントに行く」、もう1つは「空のアパートに行く」です。本当にドアの行き先だけが変わる場合、これは無意味に思えるかもしれませんが、外観が同様に変わる場合(たとえば、ドアの前の大きなロックが最初に割れなければならない場合)、

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.