シンプルなアドベンチャーゲームでの動作の実装


11

私は最近、単純なテキストベースのアドベンチャーゲームをプログラミングして楽しませており、非常に単純なデザインの問題のように見えます。

概要を説明すると、ゲームはRoomオブジェクトに分解されます。それぞれにRoomは、Entityその部屋にあるオブジェクトのリストがあります。それぞれEntityに、単純な文字列->ブールマップであるイベント状態と、文字列->関数マップであるアクションリストがあります。

ユーザー入力は次の形式を取ります[action] [entity]Room適切戻るには、エンティティ名を使用Entityして、正しい機能を見つけるために、アクション名を使用するオブジェクトを、それを実行します。

部屋の説明を生成するために、各Roomオブジェクトは独自の説明文字列を表示し、すべての説明文字列を追加しますEntityEntity説明は(ETC、「ドアが開いている」、「ドアが閉じている」、「ドアがロックされている」)、その状態に基づいて変更することができます。

ここに問題があります。この方法を使用すると、実装する必要のある記述およびアクション関数の数がすぐに手に負えなくなります。私のスターティングルームだけでも、5つのエンティティの間に約20の機能があります。

すべてのアクションを1つの関数に結合し、それらをif-else / switchで結合できますが、それでもエンティティあたり2つの関数です。またEntity、ドアやキーなどの一般的なオブジェクトや一般的なオブジェクトの特定のサブクラスを作成することもできますが、それだけではこれまでのところはできません。

編集1:要求どおり、これらのアクション関数の疑似コード例。

string outsideDungeonBushesSearch(currentRoom, thisEntity, player)
    if thisEntity["is_searched"] then
        return "There was nothing more in the bushes."
    else
        thisEntity["is_searched"] := true
        currentRoom.setEntity("dungeonDoorKey")
        return "You found a key in the bushes."
    end if

string dungeonDoorKeyUse(currentRoom, thisEntity, player)
    if getEntity("outsideDungeonDoor")["is_locked"] then
        getEntity("outsideDungeonDoor")["is_locked"] := false
        return "You unlocked the door."
    else
        return "The door is already unlocked."
    end if

説明関数はほとんど同じように動作し、状態をチェックして適切な文字列を返します。

編集2:私の質問文言を改訂しました。他のオブジェクトと共通の動作(特定のアクションに対する状態ベースの応答)を共有しないゲーム内オブジェクトがかなりの数存在するとします。エンティティ固有のアクションごとにカスタム関数を作成するよりも、クリーンでメンテナンス可能な方法でこれらの固有の動作を定義できる方法はありますか?


1
私はあなたがこれらの「アクション関数」が何をしているのかを説明し、おそらくいくつかのコードを投稿する必要があると思います。
2012年

コードを追加しました。
Eric

回答:


5

名詞と動詞のすべての組み合わせに対して個別の関数を作成するのではなく、ゲーム内のすべてのオブジェクトが実装する1つの共通インターフェイスがあるアーキテクチャをセットアップする必要があります。

私の頭の上の1つのアプローチは、ゲーム内のすべての特定のオブジェクトが拡張するEntityオブジェクトを定義することです。各エンティティには、さまざまなアクションをさまざまな結果に関連付けるテーブル(連想配列で使用するデータ構造に関係なく)があります。表のアクションは文字列(例: "open")ですが、言語がファーストクラスの関数をサポートしている場合、関連する結果はオブジェクトのプライベート関数になる可能性もあります。

同様に、オブジェクトの状態は、オブジェクトのさまざまなフィールドに格納されます。したがって、たとえば、ブッシュにものの配列を含めることができ、「検索」に関連付けられた関数がその配列に作用して、見つかったオブジェクトまたは文字列「茂みにはこれ以上何もありませんでした」を返します。

一方、パブリックメソッドの1つはEntity.actOn(String action)のようなものです。次に、そのメソッドで、渡されたアクションとそのオブジェクトのアクションのテーブルを比較します。そのアクションがテーブルにある場合、結果を返します。

これで、各オブジェクトに必要なすべてのさまざまな機能がオブジェクト内に含まれ、そのオブジェクトを他の部屋で簡単に繰り返すことができます(たとえば、ドアのあるすべての部屋でDoorオブジェクトをインスタンス化します)。

最後に、すべての部屋をXMLまたはJSONなどで定義して、部屋ごとに個別のコードを記述しなくても、多数のユニークな部屋を作成できるようにします。ゲームの開始時にこのデータファイルをロードし、データを解析して、ゲームに入力するオブジェクトをインスタンス化します。何かのようなもの:

<rooms>
  <room id="room1">
    <description>Outside the dungeon you see some bushes and a heavy door over the entrance.</description>
    <entities>
      <bush>
        <description>The bushes are thick and leafy.</description>
        <contains>
          <key />
        </contains>
      </bush>
      <door connection="room2" isLocked="true">
        <description>It's an oak door with stout iron clasps.</description>
      </door>
    </entities>
  </room>

  <room id="room2">
    etc.

補足:ああ、私はFxIIIの答えを読んだだけで、終わり近くのこのビットが私に飛び出しました:

(no things like <item triggerFlamesOnPicking="true"> that you will use just once)

トリガーされるフレームトラップが1回だけ発生するものではないことに同意しませんが(このトラップが多くの異なるオブジェクトに再利用されるのを見ることができました)、ユーザー入力に一意に反応するエンティティについて、最終的にはあなたが何を意味するのか理解できたと思います。私はおそらく、すべてのエンティティをコンポーネントアーキテクチャ(他の場所で詳細に説明)で構築することにより、ダンジョンの1つのドアに火の玉の罠を仕掛けるようなものに対処するでしょう。

このようにして、各Doorエンティティはコンポーネントのバンドルとして構築され、異なるエンティティ間でコンポーネントを柔軟に組み合わせることができます。たとえば、ドアの大部分は次のような構成になります

<entity name="door">
  <description>It's an oak door with stout iron clasps.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room2" />
  </components>
</entity>

しかし、火の玉の罠のある扉は

<entity name="door">
  <description>There are strange runes etched into the wood.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room7" />
    <fireballTrap />
  </components>
</entity>

そして、そのドア用に記述しなければならない唯一のユニークなコードは、FireballTrapコンポーネントです。他のすべてのドアと同じLockおよびPortalコンポーネントを使用します。後で宝箱などにFireballTrapを使用することにした場合、FireballTrapコンポーネントをその宝箱に追加するだけの簡単なものです。

かどうかはあなたがコンパイルされたコード内または別のスクリプト言語のすべてのコンポーネントを定義するには、私の心に大きな違い(あなたは、コードの書き込みするつもりされているいずれかの方法ではありませんどこかに)が、重要なことは、あなたが大幅に減らすことができるということですあなたが書く必要があるユニークなコードの量。レベルデザイナ/モッダーの柔軟性が気にならない場合(結局のところ、自分でこのゲームを作成している場合)、すべてのエンティティにEntityを継承させ、構成ファイルやスクリプトではなく、コンストラクタにコンポーネントを追加することもできます。なんでも:

Door extends Entity {
  public Door() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
  }
}

TrappedDoor extends Entity {
  public TrappedDoor() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
    addComponent(new FireballTrap());
  }
}

1
これは一般的な繰り返し可能なアイテムで機能します。しかし、ユーザー入力に一意に応答するエンティティについてはどうでしょうか?Entity単一のオブジェクトのみのサブクラスを作成すると、コードがグループ化されますが、記述しなければならないコードの量は減りません。それとも、この点で避けられない落とし穴ですか?
エリック

1
私はあなたが与えた例に取り組みました。私はあなたの心を読むことができません。どのオブジェクトと入力が必要ですか?
2012年

私の意図をよりよく説明するために私の投稿を編集しました。私の例を正しく理解すると、各エンティティタグがのサブクラスに対応しEntity、属性がその初期状態を定義しているように見えます。エンティティの子タグは、そのタグが関連付けられているアクションのパラメータとして機能すると思いますよね?
エリック

うん、それはアイデアです。
2012年

コンポーネントがソリューションの一部であると考えるべきでした。助けてくれてありがとう。
エリック

1

対処する次元の問題はごく普通のことであり、ほとんど避けられません。エンティティを表現する方法として、簡潔柔軟性のある方法を見つけたいと考えています。

「コンテナー」(ジョッキングの答えの茂み)は簡単な方法ですが、十分に柔軟ではありません。

あなたは常に持つことになりますので、私は、一般的なインタフェースを見つけ、その後の行動を指定するには、設定ファイルを使用しようとするあなたを示唆していない不快感をする岩の間(標準と退屈なエンティティ、説明しやすい)とハードな場所(ユニークな素晴らしいエンティティですが、実装するには長すぎます)。

私の提案は、インタプリタ言語使用して動作をコーディングすることです。

ブッシュの例について考えてみましょう。これはコンテナですが、ブッシュには特定のアイテムが必要です。コンテナオブジェクトには次のものがあります。

  • 語り手がアイテムを追加する方法、
  • エンジンが含むアイテムを表示するためのメソッド、
  • プレイヤーがアイテムを選ぶためのメソッド。

これらのアイテムの1つには、わなをトリガーするロープがあり、それが茂みを燃やす炎を発します...

誰かがコンテナからアイテム選ぶたびにメインプログラムから実行するフックに関連する追加コードを配置する設定ファイルの代わりに、スクリプトを使用してこのブッシュを記述することができます。

これで、多くのアーキテクチャーの選択肢があります。コード言語またはスクリプト言語(コンテナー、ドアのようなもの)を使用して、動作ツールを基本クラスとして定義できます。これらの目的は、簡単な動作簡単に集約し、スクリプト言語のバインディングを使用して構成するエンティティ説明できるようにすることです

すべてのエンティティはスクリプトにアクセスできる必要があります。各エンティティに識別子を関連付け、スクリプト言語スクリプトの拡張でエクスポートされるコンテナにそれらを配置できます。

スクリプト戦略を使用すると、構成をシンプルに保ち(<item triggerFlamesOnPicking="true">一度だけ使用するようなものはありません)、コードの行を追加して奇妙な動作(楽しいもの)を表現できます

つまり、コードを実行できる設定ファイルとしてのスクリプト。

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