ユーザーフレンドリーでありながら柔軟なコンポーネントベースのエンティティシステムにはどのような設計がありますか?


23

私はしばらくの間、コンポーネントベースのエンティティシステムに興味があり、その上で数え切れないほどの記事を読んでいます(Insomiacゲーム、かなり標準的なEvolve Your HierarchyT-MachineChronoclast ...など)。

それらはすべて、次のようなものの外側に構造を持っているようです。

Entity e = Entity.Create();
e.AddComponent(RenderComponent, ...);
//do lots of stuff
e.GetComponent<PositionComponent>(...).SetPos(4, 5, 6);

そして、共有データのアイデアを取り入れると(これは、どこでもデータを複製しないという点で、これまで見た中で最高の設計です)

e.GetProperty<string>("Name").Value = "blah";

はい、これは非常に効率的です。ただし、読み取りまたは書き込みが最も簡単なわけではありません。それはとても不格好で、あなたに対して働いているように感じます。

私は個人的に次のようなことをしたいと思います:

e.SetPosition(4, 5, 6);
e.Name = "Blah";

もちろん、この種の設計に到達する唯一の方法は、Entity-> NPC-> Enemy-> FlyingEnemy-> FlyingEnemyWithAHatOnのような階層に戻ることですが、この設計は回避しようとします。

この種のコンポーネントシステムの設計を見たことがありますが、これはまだ柔軟性がありますが、ユーザーフレンドリーなレベルを維持していますか?それに、データストレージ(おそらく最も難しい問題)を良い方法で回避できますか?

ユーザーフレンドリーでありながら柔軟なコンポーネントベースのエンティティシステムにはどのような設計がありますか?

回答:


13

Unityが行うことの1つは、親ゲームオブジェクトにヘルパーアクセサーを提供して、共通コンポーネントへのユーザーフレンドリーなアクセスを提供することです。

たとえば、Transformコンポーネントに位置を保存している場合があります。あなたの例を使用すると、次のようなものを書く必要があります

e.GetComponent<Transform>().position = new Vector3( whatever );

しかしUnityでは、それは

e.transform.position = ....;

どこtransformベースで文字通り単純なヘルパーメソッドであるGameObjectクラス(Entityないあなたの場合はクラス)

Transform transform
{ 
    get { return this.GetComponent<Transform>(); }
}

Unityは、子コンポーネントではなくゲームオブジェクト自体に「Name」プロパティを設定するなど、他にもいくつかのことを行います。

個人的には、名前ごとの共有データデザインのアイデアは好きではありません。変数ではなく名前でプロパティにアクセスし、ユーザーにそれがどのタイプであるかを知ってもらう必要がありますが、本当にエラーが発生しやすいようです。Unityが行うことはGameObject transformComponentクラス内のプロパティと同様のメソッドを使用して、兄弟コンポーネントにアクセスすることです。したがって、作成する任意のコンポーネントは、単にこれを行うことができます。

var myPos = this.transform.position;

位置にアクセスします。そのtransformプロパティがこのようなことをする場所

Transform transform
{
    get { return this.gameObject.GetComponent<Transform>(); }
}

確かに、それは単に言うよりも少し冗長ですe.position = whateverが、あなたはそれに慣れるでしょう、そしてそれは一般的なプロパティほど厄介に見えません。そして、はい、あなたはあなたのクライアントコンポーネントの回り道をする必要がありますが、アイデアはすべての一般的な「エンジン」コンポーネント(レンダラー、コライダー、オーディオソース、トランスフォームなど)に簡単なアクセサーがあるということです。


名前ごとの共有データシステムが問題になる理由はわかりますが、データを必要とする複数のコンポーネントの問題を回避するにはどうすればよいでしょう(つまり、どのコンポーネントに何かを保存しますか)。設計段階で非常に注意するだけですか?
共産主義のダック

また、「ユーザーがどのタイプであるかを知る必要がある」という点で、get()メソッドでproperty.GetType()にキャストすることはできませんか?
共産主義のダック

1
これらの共有データリポジトリにプッシュするのは、どのようなグローバル(エンティティスコープ内)データですか?あらゆる種類のゲームオブジェクトデータは、「エンジン」クラス(変換、コライダー、レンダラーなど)から簡単にアクセスできる必要があります。この「共有データ」に基づいてゲームロジックを変更する場合、1つのクラスにそれを所有させ、名前付き変数が存在するかどうかを盲目的に確認するよりも、実際にコンポーネントがそのゲームオブジェクト上にあることを確認する方がはるかに安全です。そのため、設計段階でそれを処理する必要があります。本当に必要な場合は、常にデータを保存するだけのコンポーネントを作成できます。
テトラッド

@Tetrad-提案されているGetComponent <Transform>メソッドがどのように機能するかについて簡単に説明できますか?GameObjectのすべてのコンポーネントをループし、タイプTの最初のコンポーネントを返しますか?それとも他に何かが起こっていますか?
マイケル

動作する@Michael。パフォーマンスを向上させるために、それをローカルにキャッシュするのは呼び出し側の責任にするだけです。
12

3

Entityオブジェクト用のある種のInterfaceクラスがいいと思います。エンティティの適切な値のコンポーネントが1つの場所に含まれていることを確認することでエラー処理を実行できるため、値にアクセスするすべての場所でエンティティを実行する必要はありません。あるいは、コンポーネントベースのシステムでこれまでに行った設計のほとんどは、コンポーネントを直接処理し、たとえば位置コンポーネントを要求してから、そのコンポーネントのプロパティに直接アクセス/更新します。

高レベルで非常に基本的なクラスは、問題のエンティティを取り込み、次のように、基礎となるコンポーネントパーツへの使いやすいインターフェイスを提供します。

public class EntityInterface
{
    private Entity entity { get; set };
    public EntityInterface(Entity e)
    {
        entity = e;
    }

    public Vector3 Position
    {
        get
        {
            return entity.GetProperty<Vector3>("Position");
        }
        set
        {
            entity.GetProperty<Vector3>("Position") = value;
        }
    }
}

NoPositionComponentExceptionまたは何かを処理する必要があると仮定した場合、これをすべてEntityクラスに配置することの利点は何ですか?
共産主義のダック

あなたのエンティティクラスが実際に何を含んでいるかわからないので、利点があるかどうかは言えません。私は個人的には、「そこに何が属しているのか」という質問を避けるために、基本エンティティごとにコンポーネントシステムがないことを個人的に提唱しています。しかし、私はこのようなインターフェースクラスが存在するための良い議論だと思います。
ジェームズ

これがいかに簡単かはわかります(GetPropertyを介して位置を照会する必要はありません)が、Entityクラス自体に配置するのではなく、この方法の利点はわかりません。
共産主義のダック

2

Pythonでは、Entityで関数e.SetPosition(4,5,6)を宣言する__getattr__ことにより、「setPosition」部分をインターセプトできます。この関数はコンポーネントを反復処理し、適切なメソッドまたはプロパティを見つけてそれを返すことができるため、関数呼び出しまたは割り当ては適切な場所に送られます。おそらくC#にも同様のシステムがありますが、静的に型指定されているため不可能なe.setPosition場合があります。おそらく、インターフェイスのどこかにeがsetPositionを持たない限りコンパイルできません。

また、例外を発生させるスタブメソッドを使用して、エンティティに追加するすべてのコンポーネントに関連するインターフェイスをすべてのエンティティに実装させることもできます。次に、addComponentを呼び出すと、そのコンポーネントのインターフェイスのエンティティの機能をコンポーネントにリダイレクトするだけです。少し面倒ですが。

ただし、おそらく最も簡単なのは、Entityクラスの[]演算子をオーバーロードして、有効なプロパティの存在をアタッチされたコンポーネントで検索し、それを返すことですe["Name"] = "blah"。したがって、次のように割り当てることができます。各コンポーネントは、独自のGetPropertyByNameを実装する必要があり、エンティティは、問題のプロパティを担当するコンポーネントが見つかるまで、それぞれを順番に呼び出します。


私はインデクサーを使用することを考えていました。これは本当に素晴らしいです。他に何が表示されるか少し待ちますが、これ、通常のGetComponent()、および一般的なコンポーネントに推奨される@Jamesのようなプロパティの組み合わせに向かっています。
共産主義のダック

ここで言われていることを見逃しているかもしれませんが、これはe.GetProperty <type>( "NameOfProperty")。Whatever with e ["NameOfProperty"]。Whatever ??
ジェームズ

@Jamesそれはそれのラッパーです(あなたのデザインと同じです)..より動的ではありません- GetPropertyメソッドよりも自動的にきれいになります。文字列の操作と比較は欠点だけです。しかし、それは重要なポイントです。
共産主義のダック

ああ、そこに私の誤解。GetPropertyはエンティティの一部であり(上記で説明したことも行う)、C#メソッドではないと想定しました。
ジェームズ

2

Kylotanの答えを拡張するために、C#4.0を使用している場合、変数を静的に入力してdynamicにすることができます。

System.Dynamic.DynamicObjectから継承する場合、TryGetMemberとTrySetMember(多くの仮想メソッドの中で)をオーバーライドして、コンポーネント名をインターセプトし、要求されたコンポーネントを返すことができます。

dynamic entity = EntityRegistry.Entities[1000];
entity.MyComponent.MyValue = 100;

私は長年にわたってエンティティシステムについて少し書きましたが、巨人と競争できないと思います。いくつかのESのメモ(多少時代遅れであり、必ずしもエンティティシステムに関する私の現在の理解を反映しているわけではありませんが、読む価値はあります)。


おかげで、助けになります:)しかし、動的な方法は、property.GetType()にキャストするのと同じ種類の効果はありませんか?
共産主義者のダック

TryGetMemberにはout objectパラメーターがあるため、ある時点でキャストが発生しますが、これらはすべて実行時に解決されます。つまり、entity.TryGetComponent(compName、out component)上で流なインターフェースを維持するための栄光に満ちた方法だと思います。私は確かに、私はしかし、質問を理解しなかった:)ないよ
レイン

問題は、どこでも動的な型を使用できること、または(AFAICS)(property.GetType())プロパティを返すことです。
共産主義のダック

1

ここでは、C#のESに多くの関心を寄せています。優れたES実装Artemisの私のポートをチェックしてください。

https://github.com/thelinuxlich/artemis_CSharp

また、(XNA 4を使用して)動作を開始するためのサンプルゲーム:https : //github.com/thelinuxlich/starwarrior_CSharp

提案は大歓迎です!


確かにきれいに見えます。私は個人的に非常に多くのEntityProcessingSystemsのアイデアのファンではありません-コードでは、それは使用に関してどのように機能しますか?
共産主義のダック

すべてのシステムは、その依存コンポーネントを処理するアスペクトです。アイデアを得るために、これを参照してください。github.com/thelinuxlich/starwarrior_CSharp/tree/master/...
thelinuxlich

0

C#の優れた反射システムを使用することにしました。一般的なコンポーネントシステムに加えて、ここでの回答の混合物に基づいています。

コンポーネントは非常に簡単に追加できます。

Entity e = new Entity(); //No templates/archetypes yet.
e.AddComponent(new HealthComponent(100));

また、名前、タイプ、または(HealthやPositionなどの一般的なものの場合)プロパティで照会できます。

e["Health"] //This, unfortunately, is a bit slower since it requires a dict search
e.GetComponent<HealthComponent>()
e.Health

そして削除:

e.RemoveComponent<HealthComponent>();

ここでも、データはプロパティによって一般的にアクセスされます。

e.MaxHP

どちらがコンポーネント側に保存されているか。HPがない場合のオーバーヘッドが少ない:

public int? MaxHP
{
get
{

    return this.Health.MaxHP; //Error handling returns null if health isn't here. Excluded for clarity
}
set
{
this.Health.MaxHP = value;
}

大量のタイピングは、VSのIntellisenseによって支援されますが、ほとんどの場合、タイピングはほとんどありません-私が探していたものです。

舞台裏では、名前をチェックするためのメソッドDictionary<Type, Component>Componentsオーバーライドして保存していますToString。私の元のデザインは主に名前と物事に文字列を使用していましたが、それは一緒に仕事をするブタになりました(見た目、どこにでもキャストしなければならず、アクセスしにくいプロパティがありません)。

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