Desain apa yang ada untuk sistem entitas berbasis komponen yang ramah pengguna tetapi masih fleksibel?

23

Saya telah tertarik pada sistem entitas berbasis komponen untuk sementara waktu, dan membaca artikel yang tak terhitung jumlahnya di dalamnya (The Insomiac , standar cantik Evolve Your Hierarchy , T-Machine , Chronoclast ... hanya untuk beberapa nama).

Mereka semua tampaknya memiliki struktur di luar sesuatu seperti:

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

Dan jika Anda membawa ide data bersama (ini adalah desain terbaik yang saya lihat sejauh ini, dalam hal tidak menggandakan data di mana-mana)

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

Ya, ini sangat efisien. Namun, itu bukan cara termudah untuk membaca atau menulis; rasanya sangat kikuk dan bekerja melawan Anda.

Saya pribadi ingin melakukan sesuatu seperti:

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

Meskipun tentu saja satu-satunya cara untuk mendapatkan desain semacam itu adalah kembali ke Entity-> NPC-> Enemy-> FlyingEnemy-> FlyingEnemyWithAHat Pada jenis hierarki desain ini mencoba untuk menghindari.

Adakah yang melihat desain untuk sistem komponen semacam ini yang masih fleksibel, namun tetap mempertahankan tingkat keramahan pengguna? Dan dalam hal ini, mengelola penyimpanan data (mungkin masalah tersulit) dengan cara yang baik?

Desain apa yang ada untuk sistem entitas berbasis komponen yang ramah pengguna tetapi masih fleksibel?

Bebek Komunis
sumber

Jawaban:

13

Salah satu hal yang dilakukan Unity adalah menyediakan beberapa asesoris pembantu pada objek game induk untuk memberikan akses yang lebih ramah pengguna ke komponen umum.

Misalnya, posisi Anda mungkin tersimpan di komponen Transform. Dengan menggunakan contoh Anda, Anda harus menulis sesuatu seperti

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

Tetapi di Unity, itu disederhanakan menjadi

e.transform.position = ....;

Di mana transformsecara harfiah hanya metode pembantu sederhana di GameObjectkelas dasar ( Entitykelas dalam kasus Anda) yang melakukannya

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

Unity juga melakukan beberapa hal lain, seperti mengatur properti "Nama" pada objek game itu sendiri, bukan dalam komponen anak-anaknya.

Secara pribadi saya tidak suka ide desain berbagi-data-dengan-nama Anda. Mengakses properti dengan nama alih-alih oleh variabel dan memiliki pengguna juga harus tahu jenis apa itu sepertinya sangat rentan terhadap saya. Apa yang dilakukan Unity adalah bahwa mereka menggunakan metode yang sama seperti GameObject transformproperti di dalam Componentkelas untuk mengakses komponen saudara. Jadi komponen sewenang-wenang yang Anda tulis dapat melakukannya:

var myPos = this.transform.position;

Untuk mengakses posisi. Di mana transformproperti itu melakukan sesuatu seperti ini

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

Tentu, ini sedikit lebih verbal daripada hanya mengatakan e.position = whatever, tetapi Anda terbiasa, dan itu tidak tampak sama buruknya dengan sifat generik. Dan ya, Anda harus melakukannya dengan cara memutar untuk komponen klien Anda, tetapi idenya adalah bahwa semua komponen "mesin" Anda yang umum (renderer, collider, sumber audio, transformasi, dll) memiliki aksesor yang mudah.

Tetrad
sumber
Saya bisa melihat mengapa sistem shared-data-by-name mungkin menjadi masalah, tetapi bagaimana lagi saya bisa menghindari masalah beberapa komponen yang membutuhkan data (yaitu di mana saya menyimpan sesuatu)? Hanya sangat berhati-hati pada tahap desain?
Bebek Komunis
Juga, dalam hal 'memiliki pengguna juga harus tahu jenis apa itu', tidak bisakah saya hanya melemparkannya ke properti.GetType () dalam metode get ()?
Bebek Komunis
1
Apa jenis data global (dalam lingkup entitas) yang Anda dorong ke repositori data bersama ini? Segala jenis data objek game harus mudah diakses melalui kelas "mesin" Anda (transform, collider, renderers, dll). Jika Anda membuat perubahan logika game berdasarkan "data bersama" ini, maka jauh lebih aman untuk memiliki satu kelas memilikinya dan benar-benar memastikan bahwa komponen ada pada objek game itu daripada hanya meminta secara membabi buta untuk melihat apakah ada beberapa variabel bernama. Jadi ya, Anda harus mengatasinya pada fase desain. Anda selalu dapat membuat komponen yang hanya menyimpan data jika Anda benar-benar membutuhkannya.
Tetrad
@Tetrad - Dapatkah Anda menguraikan secara singkat tentang bagaimana metode <Crans>> GetComponent yang Anda usulkan akan bekerja? Apakah akan mengulang semua Komponen GameObject dan mengembalikan Komponen tipe T pertama? Atau ada hal lain yang terjadi di sana?
Michael
@Michael itu akan berhasil. Anda bisa menjadikan penelepon untuk melakukan cache secara lokal sebagai peningkatan kinerja.
Tetrad
3

Saya akan menyarankan beberapa jenis kelas Interface untuk objek Entity Anda akan lebih baik. Itu bisa melakukan penanganan kesalahan dengan memeriksa untuk memastikan suatu entitas mengandung komponen dari nilai yang sesuai juga di satu lokasi sehingga Anda tidak perlu melakukannya di mana pun Anda mengakses nilai. Bergantian, sebagian besar desain yang pernah saya lakukan dengan sistem berbasis komponen, saya berurusan dengan komponen secara langsung, meminta, misalnya, komponen posisi mereka dan kemudian mengakses / memperbarui properti komponen itu secara langsung.

Sangat mendasar pada level tinggi, kelas menerima entitas yang dimaksud dan menyediakan antarmuka yang mudah digunakan ke bagian komponen yang mendasarinya sebagai berikut:

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;
        }
    }
}
James
sumber
Jika saya berasumsi saya hanya perlu menangani untuk NoPositionComponentException atau sesuatu, apa keuntungan dari ini daripada hanya meletakkan semua itu di kelas Entity?
Bebek Komunis
Menjadi tidak yakin apa kelas entitas Anda sebenarnya mengandung saya tidak bisa mengatakan ada keuntungan atau tidak. Saya pribadi menganjurkan sistem komponen di mana tidak ada entitas dasar per-katakan hanya untuk menghindari pertanyaan 'Yah apa yang termasuk di dalamnya'. Namun saya akan menganggap kelas antarmuka seperti ini argumen yang bagus untuk itu ada.
James
Saya dapat melihat bagaimana ini lebih mudah (saya tidak perlu menanyakan posisi melalui GetProperty), tetapi saya tidak melihat keuntungan dari cara ini alih-alih menempatkannya di kelas Entity itu sendiri.
Bebek Komunis
2

Dengan Python, Anda dapat mencegat bagian 'setPosition' e.SetPosition(4,5,6)dengan mendeklarasikan __getattr__fungsi pada Entity. Fungsi ini dapat beralih melalui komponen dan menemukan metode atau properti yang sesuai dan mengembalikannya, sehingga panggilan fungsi atau penugasan pergi ke tempat yang tepat. Mungkin C # memiliki sistem yang serupa, tetapi mungkin tidak dapat dilakukan karena diketik secara statis - mungkin tidak dapat dikompilasi e.setPositionkecuali e memiliki setPosition di antarmuka di suatu tempat.

Anda mungkin juga dapat membuat semua entitas Anda mengimplementasikan antarmuka yang relevan untuk semua komponen yang mungkin Anda tambahkan ke mereka, dengan metode rintisan yang menimbulkan pengecualian. Kemudian ketika Anda memanggil addComponent, Anda hanya mengarahkan ulang fungsi entitas untuk antarmuka komponen itu ke komponen. Agak fiddly.

Tapi mungkin termudah adalah dengan membebani [] operator pada kelas Entity Anda untuk mencari komponen yang terpasang untuk keberadaan properti valid dan kembali itu, sehingga dapat ditugaskan untuk suka begitu: e["Name"] = "blah". Setiap komponen perlu mengimplementasikan GetPropertyByName sendiri dan Entitas memanggil masing-masing satu per satu sampai menemukan komponen yang bertanggung jawab atas properti yang dimaksud.

Kylotan
sumber
Saya sudah berpikir untuk menggunakan pengindeks .. ini sangat bagus. Saya akan menunggu sedikit untuk melihat apa yang muncul, tapi saya berbelok ke arah campuran ini, GetComponent () biasa, dan beberapa properti seperti @James disarankan untuk komponen umum.
Bebek Komunis
Saya mungkin kehilangan apa yang dikatakan di sini tetapi ini sepertinya lebih merupakan pengganti untuk e.GetProperty <type> ("NameOfProperty"). Apa pun dengan e ["NameOfProperty"]. Apa pun ??
James
@ James Ini adalah pembungkus untuk itu (Sama seperti desain Anda) .. kecuali itu lebih dinamis - ia melakukannya secara otomatis dan lebih bersih daripada metode GetProperty.. Satu - satunya downside adalah manipulasi string dan perbandingan. Tapi itu poin yang bisa diperdebatkan.
Bebek Komunis
Ah, kesalahpahaman saya di sana. Saya berasumsi GetProperty adalah bagian dari entitas (Dan akan melakukan apa yang dijelaskan di atas) dan bukan metode C #.
James
2

Untuk memperluas jawaban Kylotan, jika Anda menggunakan C # 4.0, Anda bisa mengetik variabel secara dinamis menjadi dinamis .

Jika Anda mewarisi dari System.Dynamic.DynamicObject, Anda dapat mengganti TryGetMember dan TrySetMember (di antara banyak metode virtual) untuk mencegat nama-nama komponen dan mengembalikan komponen yang diminta.

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

Saya menulis sedikit tentang sistem entitas selama bertahun-tahun, tetapi saya menganggap saya tidak dapat bersaing dengan para raksasa. Beberapa catatan ES (agak ketinggalan jaman dan tidak mencerminkan pemahaman saya saat ini tentang sistem entitas, tetapi layak dibaca, imho).

Raine
sumber
Terima kasih untuk itu, akan membantu :) Bukankah cara dinamis memiliki efek yang sama seperti hanya casting ke property.GetType (), sih?
Bebek Komunis
Akan ada pemain di beberapa titik, karena TryGetMember memiliki objectparameter keluar - tetapi semua ini akan diselesaikan pada saat runtime. Saya pikir ini adalah cara yang dimuliakan untuk menjaga antarmuka yang lancar di atas entitas yaitu.TryGetComponent (compName, komponen keluar). Namun saya tidak yakin saya mengerti pertanyaannya :)
Raine
Pertanyaannya adalah saya bisa menggunakan tipe dinamis di mana-mana, atau (AFAICS) mengembalikan (property.GetType ()) properti.
Bebek Komunis
1

Saya melihat banyak ketertarikan di sini di ES pada C #. Lihatlah port saya dari implementasi ES yang sangat baik Artemis:

https://github.com/thelinuxlich/artemis_CSharp

Juga, contoh gim untuk memulai cara kerjanya (menggunakan XNA 4): https://github.com/thelinuxlich/starwarrior_CSharp

Saran dipersilahkan!

thelinuxlich
sumber
Tentu memang terlihat bagus. Saya pribadi bukan penggemar ide begitu banyak EntityProcessingSystems - dalam kode, bagaimana cara kerjanya dalam hal penggunaan?
Bebek Komunis
Setiap Sistem adalah Aspek yang memproses Komponen dependennya. Lihat ini untuk mendapatkan ide: github.com/thelinuxlich/starwarrior_CSharp/tree/master/…
thelinuxlich
0

Saya memutuskan untuk menggunakan sistem refleksi C # yang sangat baik. Saya mendasarkannya pada sistem komponen umum, ditambah campuran jawaban di sini.

Komponen ditambahkan dengan sangat mudah:

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

Dan dapat ditanyakan dengan nama, tipe, atau (jika yang umum seperti Kesehatan dan Posisi) berdasarkan properti:

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

Dan dihapus:

e.RemoveComponent<HealthComponent>();

Data, sekali lagi, diakses secara umum oleh properti:

e.MaxHP

Yang disimpan di sisi komponen; lebih sedikit overhead jika tidak memiliki 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;
}

Banyaknya pengetikan dibantu oleh Intellisense dalam VS, tetapi sebagian besar ada sedikit pengetikan - hal yang saya cari.

Di belakang layar, saya menyimpan Dictionary<Type, Component>dan Componentsmenimpa ToStringmetode untuk memeriksa nama. Desain asli saya digunakan terutama string untuk nama dan hal-hal, tetapi menjadi babi untuk bekerja dengan (oh lihat, saya harus melempar ke mana-mana dan juga kurangnya akses mudah ke properti).

Bebek Komunis
sumber