動作/属性GameComponentアーキテクチャ


7

私は大学生で、ゲーム開発は私にとって少し趣味です。私は新しいゲームを書いていて、ゲームエンジンをより柔軟に(そして将来の取り組みのための強固な基盤を提供できるようにするために)開発するための新しいアプローチを探しています。ゲームエンティティを表すために深い継承に依存するのではなく(継承ツリーのルートノードとリーフノードが重い場合や、場合によってはダイヤモンドの継承につながる可能性がある)方法に関するいくつかの記事を見つけましたが、より良いアプローチはロジックをモデル化することですコンポーネント内(より良い、動作と属性)。

これを示すプレゼンテーションは次のとおりです。http//www.gdcvault.com/free/gdc-canada-09 「ゲームオブジェクトコンポーネントアーキテクチャの理論と実践...」

XNAとC#を使用してこれを実装しています。私はエンティティクラスがあります:

class Entity
{
    private Game1 mGame;
    private Dictionary<AttributeType, object> mAttributes;
    private Dictionary<BehaviorType, Behavior> mBehaviors;

    public Entity(Game game)
    {
        mGame = game;
        mAttributes = new Dictionary<AttributeType, object>();
        mBehaviors = new Dictionary<BehaviorType, Behavior>();
    }

    public Game GetGame()
    {
        return mGame;
    }

    //Attributes
    public void SetAttribute<T>(AttributeType attributeType, T value)
    {
        if (mAttributes.ContainsKey(attributeType))
        {
            mAttributes[attributeType] = value;
            OnMessage<T>(MessageType.ATTRIBUTE_UPDATED, attributeType, value);
        }
        else
        {
            mAttributes.Add(attributeType, value);
            OnMessage<T>(MessageType.ATTRIBUTE_CREATED, attributeType, value);
        }
    }

    public T GetAttribute<T>(AttributeType attributeType)
    {
        if (!mAttributes.ContainsKey(attributeType))
        {
            throw new KeyNotFoundException("GetAttribute: Attribute with type: " + attributeType.ToString() + " not found.");
        }
        return (T)mAttributes[attributeType];
    }

    public bool HasAttribute(AttributeType attributeType)
    {
        return mAttributes.ContainsKey(attributeType);
    }

    //Behaviors
    public void SetBehavior(BehaviorType behaviorType, Behavior behavior)
    {
        if (mBehaviors.ContainsKey(behaviorType))
        {
            mBehaviors[behaviorType] = behavior;
        }
        else
        {
            mBehaviors.Add(behaviorType, behavior);
        }
    }

    public Behavior GetBehavior(BehaviorType behaviorType)
    {
        if (!mBehaviors.ContainsKey(behaviorType))
        {
            throw new KeyNotFoundException("GetBehavior: Behavior with type: " + behaviorType.ToString() + " not found.");
        }
        return mBehaviors[behaviorType];
    }

    public bool HasBehavior(BehaviorType behaviorType)
    {
        return mBehaviors.ContainsKey(behaviorType);
    }

    public Behavior RemoveBehavior(BehaviorType behaviorType)
    {
        if (!mBehaviors.ContainsKey(behaviorType))
        {
            throw new KeyNotFoundException("RemoveBehavior: Behavior with type: " + behaviorType.ToString() + " not found.");
        }
        Behavior behavior = mBehaviors[behaviorType];
        mBehaviors.Remove(behaviorType);
        return behavior;
    }

    public void OnUpdate(GameTime gameTime)
    {
        foreach (Behavior behavior in mBehaviors.Values)
        {
            behavior.OnUpdate(gameTime);
        }
    }

    public void OnMessage<T>(MessageType messageType, AttributeType attributeType, T data)
    {
        foreach (Behavior behavior in mBehaviors.Values)
        {
            behavior.OnMessage<T>(messageType, attributeType, data);
        }
    }
}

「AttributeType」と「BehaviorType」は単なる列挙型です。

public enum AttributeType
{
    POSITION_2D,
    VELOCITY_2D,
    TEXTURE_2D,
    RGB_8888
}

動作クラスは、他のすべての動作のスーパークラスです。ビヘイビアーは自己完結型でモジュール型であることを意味します(たとえば、エンティティをSpriteBatchにレンダリングする方法のみを知っているSpriteBatchRenderBehaviorがあり、このフレームワークを使用する新しいゲームで使用できるはずです)。

abstract class Behavior
{
    //Reference to owner to access attributes
    private Entity mOwner;

    public Behavior(Entity owner)
    {
        mOwner = owner;
    }

    public Entity GetOwner()
    {
        return mOwner;
    }

    protected void SetAttribute<T>(AttributeType attributeType, T value)
    {
        mOwner.SetAttribute<T>(attributeType, value);
    }

    protected T GetAttribute<T>(AttributeType attributeType)
    {
        return (T)mOwner.GetAttribute<T>(attributeType);
    }

    public abstract void OnUpdate(GameTime gameTime);

    public abstract void OnMessage<T>(MessageType messageType, AttributeType attributeType, T data);
}

したがって、エンティティにはラベル付けされた属性(POSITION_2D、TEXTURE_2Dなど)のバッグがあり、各動作はget / setメソッドを通じて所有者の属性にアクセスできます。私のエンティティの属性を動作間で共有する必要があるという問題が発生しています(これが実際に問題であるかどうかはわかりません)。基本的に、ゲームの世界でエンティティの位置を変更する動作がある場合、それはエンティティの位置属性を変更します。次に、SpriteBatchDrawBehaviorをエンティティに追加します。これは、位置を持つエンティティに依存します(そのため、エンティティをどこに描画するかがわかります)。私の問題は、移動動作の前に描画動作を追加した場合、エンティティがまだ位置属性を取得しているという保証がないことです(その場合、キーが見つからないという例外がスローされます)。

描画動作(またはこの属性に依存するその他の動作)を追加する前に位置を確実に確保する動作を追加することで、エンティティに位置があることを常に確認できますが、これにより、これらの動作がすべきではない。

もう1つのオプションは、位置動作から継承する移動動作から描画動作を継承することです(ただし、これにより、ダイヤモンドの継承などの問題に戻ります。また、親クラスとの実際の関連性がほとんどない、またはまったくない子クラス、その他同様の依存関係よりも)。

もう1つのオプションは、「私の所有者がポジションを持っていない場合は、彼のポジション属性を作成する」などの私の動作にチェックを書き込むことですが、これにより問題が発生します。新しい属性にはどのデフォルト値を選択する必要がありますか?などなど

助言がありますか?私は何かを見落としているか?

ありがとう!

c#  xna 

1
参考:C#でC ++を記述しようとしているようです。GetGame()のようなゲッターは使用しません。「プロパティ」を検索し、あなたの人生を簡素化します。
3Dave

回答:


5

Yikes-エンティティをコンポーネントにモデル化する必要があると彼らが言ったとき、それらはすべてのエンティティを列挙ベースの属性のグラブバッグにすることを意味しませんでした!

コンポーネントベースの設計のポイントは、従来の多くのゲームのように、クラスの1つの大きなツリー階層Penguin継承元からFlightlessBird継承元からBird継承元からAnimal継承元...)を持つのではなく、コンポーネントクラスからクラスを構築することです。クラスが持つプロパティを指定します。たとえば、PenguinクラスにEatsFishコンポーネントのインスタンスが含まれていても、は含まれていない場合がありますCanFly

コンポーネントベースの設計について、私がこれまでに望んでいたよりも優れた説明については、ここここを参照してください(特にこの記事が好きです


リンクしたスライドは、ミックスイン使用してコンポーネントをモデルすることをお勧めします。C#はC ++のように多重継承をサポートしていないため、実際にミックスインを「実行」することはできません。あなたは、あなたのクラスより構成する必要があります通常のインタフェースおよび/または組成物を用いた:方法を。

また、メッセージングシステムの使用をサポートしていることにも注意してください。メッセージングシステムは、設計悪いと考えられています(広く使用されているにもかかわらず)。

最後に、XNAを使用しているとおっしゃっていましたが、XNAにはサービスのサポートが組み込まれていると言わざるを得ません。 これにより、発生しているインスタンス化の問題が解決されます。さらに重要なことに、これはコードをクリーンで分離された状態に保つのに役立ちます...しかし、なんらかの不思議な理由で、XNAのチュートリアルや本で言及されていないようです。


私はこのフレームワークを可能な限り移植性の高いものにしたいと考えています。XNA GameServicesを使用してゲームを作成し、ゲームをXNAに結び付けないのでしょうか。または、他のプラットフォーム(android、iphoneなど)で使用するために自分で実装するのに十分単純なGameServiceフレームワークですか。また、私が参照したプレゼンテーションでは、コンポーネントの所有者から関連する属性への参照を取得するヘルスコンポーネント "GetAttribute(HEALTH_KEY);"を示しているため、このような方法で属性をモデル化することにしました。

@Travis:android / iphoneに移植可能にしたい場合、C#は良い選択ではありません。JavaまたはC ++のクロスプラットフォームモバイルライブラリを調べたい場合があります。ただし、C#だけがわかっている場合は、それをそのまま使用して、今のところ実際にゲーム作成することを検討してください。後で移植性について心配します-ゲームを作成することは、その上に新しい言語を学ぶ必要がなければ十分に難しいです!
BlueRaja-Danny Pflughoeft 2012年

ああ、ははははははははははははCcは移植性に最適ではないことは確かですが、このゲームエンジンの設計で使用されている概念は、どのプラットフォームにも移植可能です(これが私が焦点を当てているものです)。私はandroid ndk、open gl es 2.0、およびc ++ directx 10 libsを使用してゲームを作成することについてかなり理解しています。キャンパスでのMicrosoft Windows Phoneイベント用であるため、このゲームをC#およびXNAで作成しています。

Travisのコードをどのように変更するかについて詳しく説明できますか?私はまだコンポーネントベースのゲームデザインを理解するのに苦労しています、そしてあなたがリンクした記事は私が以前に何度も見たものと同じものです。それらはレベルが高すぎて漠然としていて、パターンを理解するのに役立ちません(とにかく私にとって)。コンポーネントベースの設計についての私の理解から、Travisは正しい方向に進んでいるようです。彼がどこで問題があったと思うか、そして彼のコードをどのように改善できるかについて、もっと具体的に説明できますか?
Michael

1

質問を読む数時間前に、Marcin ChadyによるGDC Canadaからのプレゼンテーションを読みましたが、属性のポイントを理解していなかったと思います。私は個人的に(私はプレゼンテーションを書いておらず、Radical Entertainmentで実際にどのように機能するのかもわかりません)、実際に使用するに必要な属性を作成する必要があると思います。

ChangePositionBehaviourRenderBehaviourは使用の両方位置属性を、しかし、最初の1、及び第2 1どちらが行う初期化属性を。最初に初期化する必要あります。エンティティがどこにあるのかをエンティティに通知する必要があります。

Breakoutクローンの簡単な例でそれをお見せしましょう:

あなたは持っているLevel含まれているクラス(それがゲームレベル=部屋=マップです)、Brickオブジェクト(エンティティ)。Level初期位置は、その知っているBrick場所にある4.0f, 8.0f(X、Y)との速度です0.0f, 1.0f。レベル定義(レベルデータファイル)でオブジェクトの場所を指定する必要があるため、それを認識していない場合は、レベルでなかった可能性があります。それで、レベルは何をしますか?Brickエンティティを作成し、その直後に(動作が呼び出される前に)の位置をBrick最初のエンティティに設定します。

Brick.SetAttribute<Vector2>(AttributeType.POSITION_2D, new Vector2(4, 8))
Brick.SetAttribute<Vector2>(AttributeType.VELOCITY_2D, new Vector2(0, 1))

その後、単に続行できます。また、を描画する必要がある場合Brick、その位置はすでにわかっています。位置を変更する必要がある場合(これは、画面の左側から右側に移動してから戻る移動可能なレンガです)、次のようにします。

Vector2 position = GetAttribute<Vector2>(AttributeType.POSITION_2D);
Vector2 speed = GetAttribute<Vector2>(AttributeType.VELOCITY_2D);
position += speed;
SetAttribute<Vector2>(AttributeType.POSITION_2D, position);

理解していただければ幸いです。動作は単純です。動作は属性依存するため、OnUpdate()およびに進む前に属性を設定しておく必要がありOnMessage()ます。

ちなみに: (GameDev SEでの初心者の評判についてコメントすることはできないので、このbtwテキストを私の回答に追加しています) 多重継承は必要ないため、主な回答がどこにあるのか本当にわかりませんBlueRaja-Danny Pflughoeft going ... ゲームオブジェクトコンポーネントアーキテクチャ理論と実践は、問題なくC#や他のOOP言語に完全に変換できます。そして、メッセージパッシングの何が問題なのかはわかりません。この例では、他に考えられる解決策がないためです(この意見を一般的なものにしたい場合は、メッセージの使用を避けられません)。 。メッセージの代わりにカスタムイベントハンドラーを使用して、イベント引数を渡すこともできます。それもうまくいくかもしれません。


0

私の提案は、ビヘイビアーの初期化関数を持つことです。エンティティに動作を追加すると、その属性を追加できます。初期化するときに、必要な属性を検索し、存在しない場合はエラーを発生させます。これにより、すべてを任意の順序で追加できますが、初期化するまで実際には属性を検索しません。

または、OnMessageインフラストラクチャを使用して、動作がエンティティに追加されたときに欠落している属性を無視し、その動作のOnMessageに追加される必要なすべての属性をリッスンできます。ビヘイビアーを使用しようとしたときに属性が欠落している場合は、エラーをスローするか、ビヘイビアーを実行しないでください。どちらがゲームに適しているか。

それは役に立ちますか?


必要な属性が存在するかどうかを確認するために、構築後にビヘイビアーを初期化するというアイデアが気に入っています。私はそれを試してみて、それがどのように機能するかを見ることができると思います。OnMessageメソッドを使用して、新しい属性の追加について動作を通知することについて、私は実際に同じ考えを持っていました。しかし、これらのソリューションはどちらも、コンポーネントが追加/初期化される順序に依存しているようです。メッセージシステムでは、コンポーネントが属性を追加すると、1回限りのメッセージがすべての動作に送信されますが、関連する動作がまだエンティティに追加されておらず、メッセージが欠落している可能性があります。

@TravisSeinメッセージの送信後に追加されたものは、それらが追加されるときに属性をチェックする必要があります。それが適切になる前に存在していた場合は、そのようにしてチェックします。後で作成された場合は、メッセージが表示されます。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.