14

私は夏の間 C# を学んでおり、今までの経験から小さなプロジェクトを作りたいと思っています。ある種のテキストベースのアドベンチャー ゲームに決めました。

ゲームの基本構造には、多数のセクター (または部屋) が含まれます。部屋に入ると、説明といくつかのアクションなどが出力されます。その部屋にあるものを調べ、拾い、使用する能力。おそらく戦闘システムなどです。セクターは、最大4つの他のセクターに接続できます。

とにかく、このためのコードを設計する方法について紙にアイデアを走り書きしていると、コードの一部の構造に頭を悩ませています。

プレイヤー クラスと、レベル/ダンジョン/エリアを表す「レベル」クラスを決定しました。このレベルのクラスは、相互接続された多数の「セクター」で構成されます。いつでも、プレーヤーはレベル内の特定のセクターに存在します。

だからここに混乱があります:

論理的には、player.Move(Dir d)
このようなメソッドはレベル オブジェクトの「現在のセクター」フィールドを変更する必要があります。これは、クラスPlayerがクラスLevelについて知る必要があることを意味します。うーん。また、LevelはPlayerオブジェクトを操作する必要がある場合があります (たとえば、プレーヤーが部屋に入る、何かに待ち伏せされる、在庫から何かを失う)。では、LevelPlayerオブジェクトへの参照を保持する必要があるのでしょうか?

これは気分が悪いです。すべてが他のすべてへの参照を保持する必要があります。

この時点で、私が使用している本からデリゲートについて読んだことを思い出しました。C++ の関数ポインターについては知っていますが、デリゲートに関する章では、一種の「イベント ベース」プログラミングの観点から例が示されていましたが、それについてはあまり理解していませんでした。

これにより、次のようにクラスを設計するというアイデアが得られました。

プレーヤー:

class Player
{
    //...

    public delegate void Movement(Dir d);   //enum Dir{NORTH, SOUTH, ...}

    public event Movement PlayerMoved;

    public void Move(Dir d)
    {        
        PlayerMoved(d);

        //Other code...
    }

}

レベル:

class Level
{
    private Sector currSector;
    private Player p;
    //etc etc...

    private void OnMove(Dir d)
    {
        switch (d)
        {
            case Dir.NORTH:
                //change currSector
                //other code
                break;

                //other cases
        }
    }

    public Level(Player p)
    {
        p.PlayerMoved += OnMove;  
        currSector = START_SECTOR;
        //other code
    }

    //etc...
}

これはこれを行う良い方法ですか?
デリゲートの章がこのように提示されていなければ、私はそのような「イベント」を使用することを考えなかったでしょう. では、コールバックを使用せずにこれを実装するにはどうすればよいでしょうか?

細かいことまで書く癖があります…すみませんv__v

4

5 に答える 5

5

プレーヤーや現在の部屋などの情報の大部分を保持する「ゲーム」クラスについてはどうでしょうか。プレーヤーの移動などの操作の場合、ゲームクラスは、部屋のレベルマップに基づいてプレーヤーを別の部屋に移動できます。

ゲームクラスは、ゲームのさまざまなコンポーネント間のすべての相互作用を管理します。

このような目的でイベントを使用すると、イベントが絡まる危険があります。注意しないと、イベントが互いに発生してスタックがオーバーフローし、特別な状況でイベントをオフにするフラグが発生し、プログラムが理解しにくくなります。

UDPATE:

コードをより管理しやすくするために、Fightクラスなど、メインクラス間の相互作用の一部をクラス自体としてモデル化できます。インターフェイスを使用して、メインクラスが特定の相互作用を実行できるようにします。(私はあなたがあなたのゲームで望まないかもしれないいくつかのものを発明する自由をとったことに注意してください)。

例えば:

// Supports existance in a room.
interface IExistInRoom { Room GetCurrentRoom(); }

// Supports moving from one room to another.
interface IMoveable : IExistInRoom { void SetCurrentRoom(Room room); }

// Supports being involved in a fight.
interface IFightable
{
  Int32 HitPoints { get; set; }
  Int32 Skill { get; }
  Int32 Luck { get; }
}

// Example class declarations.
class RoomFeature : IExistInRoom
class Player : IMoveable, IFightable
class Monster : IMoveable, IFightable

// I'd proably choose to have this method in Game, as it alters the
// games state over one turn only.
void Move(IMoveable m, Direction d)
{
  // TODO: Check whether move is valid, if so perform move by
  // setting the player's location.
}

// I'd choose to put a fight in its own class because it might
// last more than one turn, and may contain some complex logic
// and involve player input.
class Fight
{
  public Fight(IFightable[] participants)

  public void Fight()
  {
    // TODO: Logic to perform the fight between the participants.
  }
}

あなたの質問では、PlayerクラスにMoveメソッドのようなものを貼り付けた場合、お互いを知る必要のある多くのクラスがあるという事実を特定しました。これは、移動のようなものがプレーヤーにも部屋にも属していないためです。移動は両方のオブジェクトに相互に影響します。メインオブジェクト間の「相互作用」をモデル化することにより、これらの依存関係の多くを回避できます。

于 2010-07-16T18:10:05.787 に答える
2

Command クラスまたは Service クラスをよく使用するシナリオのように聞こえます。たとえば、レベルと人物の間で操作と調整を実行する MoveCommand クラスを作成するとします。

このパターンには、Single Responsibility Principal (SRP) をさらに適用するという利点があります。SRP は、クラスを変更する理由は 1 つだけであるべきだと述べています。Person クラスが移動を担当している場合、変更する理由が複数あることは間違いありません。Move off のロジックを独自のクラスに分割することで、カプセル化が向上します。

Command クラスを実装するにはいくつかの方法があり、それぞれが異なるシナリオにより適しています。コマンド クラスには、必要なすべてのパラメーターを受け取る Execute メソッドを含めることができます。

 public class MoveCommand {
    public void Execute(Player currentPlayer, Level currentLevel) { ... }
 }

 public static void Main() {
     var cmd = new MoveCommand();
     cmd.Execute(player, currentLevel);
}

または、コマンドオブジェクトでプロパティを使用する方が簡単で柔軟な場合もありますが、クライアントコードがプロパティの設定を忘れてクラスを誤用しやすくなりますが、利点は、同じ関数シグネチャを持っていることですすべてのコマンド クラスで実行するため、そのメソッドのインターフェイスを作成し、抽象コマンドを操作できます。

 public class MoveCommand {
    public Player CurrentPlayer { get; set; } 
    public Level CurrentLevel { get; set; }
    public void Execute() { ... }
 }

 public static void Main() {
     var cmd = new MoveCommand();
     cmd.CurrentPlayer = currentPlayer;
     cmd.CurrentLevel = currentLevel;
     cmd.Execute();
}

最後に、パラメーターをコンストラクター引数として Command クラスに提供することもできますが、そのコードは省略します。

いずれにせよ、コマンドまたはサービスを使用すると、移動などの操作を処理するための非常に強力な方法であることがわかります。

于 2010-07-16T18:22:56.067 に答える
1

何かを行うための「正しい」方法について、感情的または知的に夢中になることは避けてください。代わりに、行うことに集中してください。やりたいことをサポートするために、コードの一部またはすべてを変更する必要がある場合があるため、既に記述したコードにあまり価値を置かないでください。

IMO パターンやクールなテクニック、そしてそのすべてのジャズにあまりにも多くのエネルギーが費やされています。やりたいことを実行するための簡単なコードを書くだけです。

レベルには、その中にすべてが「含まれています」。そこから始められます。レベルが必ずしもすべてを駆動する必要はありませんが、すべてがレベルにあります。

プレイヤーは移動できますが、レベルの範囲内でのみ移動できます。したがって、プレイヤーはレベルを照会して、移動方向が有効かどうかを確認する必要があります。

レベルはプレイヤーからアイテムを取得しておらず、レベルはダメージを与えていません。レベル内の他のオブジェクトがこれらのことを行っています。これらの他のオブジェクトは、プレーヤーを検索するか、プレーヤーの近さを通知する必要があります。その後、プレーヤーに対して直接必要なことを行うことができます。

レベルがプレーヤーを「所有」し、プレーヤーがそのレベルへの参照を持つことは問題ありません。これは、オブジェクト指向の観点からは「理にかなっています」。あなたは惑星地球に立ち、それに影響を与えることができますが、穴を掘っている間、それはあなたを宇宙の周りに引きずっています.

シンプルなことをする。何かが複雑になったときはいつでも、それを簡単にする方法を見つけてください。シンプルなコードは扱いやすく、バグにも強くなります。

于 2010-07-16T18:57:19.623 に答える
1

テキストベースのゲームの場合、ユーザーが入力したコマンドを評価する CommandInterpretor (または同様の) オブジェクトを使用することはほぼ確実です。このレベルの抽象化により、Player オブジェクトにすべての可能なアクションを実装する必要はありません。インタープリターは、いくつかの入力されたコマンドを Player オブジェクトにプッシュし (「インベントリを表示」)、一部のコマンドを現在占有されている Sector オブジェクトにプッシュし (「list exits」)、一部のコマンドを Level オブジェクトにプッシュし (「プレイヤーを北に移動」)、いくつかのコマンドをプッシュします。特殊オブジェクトへのコマンド (「攻撃」は CombatManager オブジェクトにプッシュされる場合があります)。

そのようにして、Player オブジェクトはより Character に似たものになり、CommandInterpretor はキーボードに座っている実際の人間のプレーヤーをより表現します。

于 2010-07-16T18:24:25.237 に答える
0

まず、これはこれを行う良い方法ですか?

絶対!

第二に、デリゲートの章がそのまま提示されていなければ、私はそのような「イベント」を使用することを考えなかったでしょう. では、コールバックを使用せずにこれを実装するにはどうすればよいでしょうか?

私はこれを実装する他の多くの方法を知っていますが、何らかのコールバック メカニズムなしでは他の良い方法はありません。私見は、分離された実装を作成する最も自然な方法です。

于 2010-07-16T18:21:57.630 に答える