Mengorganisasikan sistem entitas dengan manajer komponen eksternal?


13

Saya merancang mesin game untuk game 2D shooter top-down multiplayer, yang saya ingin dapat digunakan kembali secara wajar untuk game top-down shooter lainnya. Saat ini saya sedang memikirkan bagaimana sesuatu seperti sistem entitas di dalamnya harus dirancang. Pertama saya memikirkan hal ini:

Saya memiliki kelas yang disebut EntityManager. Seharusnya menerapkan metode yang disebut Pembaruan dan yang lain disebut Draw. Alasan saya memisahkan Logic dan Rendering adalah karena saya dapat menghilangkan metode Draw jika menjalankan server mandiri.

EntityManager memiliki daftar objek tipe BaseEntity. Setiap entitas memiliki daftar komponen seperti EntityModel (representasi yang dapat ditarik dari suatu entitas), EntityNetworkInterface, dan EntityPhysicalBody.

EntityManager juga memiliki daftar manajer komponen seperti EntityRenderManager, EntityNetworkManager dan EntityPhysicsManager. Setiap manajer komponen menyimpan referensi ke komponen entitas. Ada berbagai alasan untuk memindahkan kode ini dari kelas entitas sendiri dan melakukannya secara kolektif. Misalnya, saya menggunakan perpustakaan fisika eksternal, Box2D, untuk permainan. Di Box2D, Anda pertama-tama menambahkan tubuh dan bentuk ke dunia (dimiliki oleh EntityPhysicsManager dalam kasus ini) dan menambahkan collback callbacks (yang akan dikirim ke objek entitas itu sendiri di sistem saya). Kemudian Anda menjalankan fungsi yang mensimulasikan semua yang ada di sistem. Saya merasa sulit untuk menemukan solusi yang lebih baik untuk melakukan ini daripada melakukannya di manajer komponen eksternal seperti ini.

Pembuatan entitas dilakukan seperti ini: EntityManager mengimplementasikan metode RegisterEntity (entitasClass, pabrik) yang mendaftarkan cara membuat entitas dari kelas itu. Ini juga mengimplementasikan metode CreateEntity (entitasClass) yang akan mengembalikan objek tipe BaseEntity.

Sekarang tiba masalah saya: Bagaimana referensi ke komponen didaftarkan ke manajer komponen? Saya tidak tahu bagaimana saya akan merujuk manajer komponen dari pabrik / penutupan.


Saya tidak tahu apakah ini dimaksudkan sebagai sistem hibrida, tetapi sepertinya "manajer" Anda adalah apa yang secara umum saya dengar disebut sebagai "sistem;" yaitu Entity adalah abstrak-ID; Komponen adalah kumpulan data; dan apa yang Anda sebut "manajer" adalah apa yang umumnya disebut sebagai "Sistem." Apakah saya menafsirkan kosa kata dengan benar?
BRPocock

Pertanyaan saya di sini mungkin menarik: Komponen Game, Manajer Game, dan Properti Obyek
George Duckett

Lihatlah gamadu.com/artemis dan lihat apakah metode mereka menjawab pertanyaan Anda.
Patrick Hughes

1
Tidak ada satu cara merancang sistem entitas karena ada sedikit konsensus tentang definisi itu. Apa yang dijelaskan oleh @BRPocock dan juga apa yang digunakan Artemis telah dijelaskan lebih mendalam di blog ini: t-machine.org/index.php/category/entity-systems bersama-sama dengan wiki: entity-systems.wikidot.com
user8363

Jawaban:


6

Sistem harus menyimpan pasangan nilai kunci Entity to Component dalam semacam Map, Object Dictionary, atau Associative Array (tergantung pada bahasa yang digunakan). Selain itu, ketika Anda membuat Objek Entitas Anda, saya tidak akan khawatir tentang menyimpannya di manajer kecuali Anda harus dapat membatalkan pendaftarannya dari salah satu Sistem. Entity adalah gabungan komponen, tetapi ia seharusnya tidak menangani pembaruan komponen apa pun. Itu harus ditangani oleh Sistem. Alih-alih memperlakukan Entitas Anda sebagai kunci yang dipetakan ke semua komponen yang dikandungnya dalam sistem, serta hub komunikasi untuk komponen-komponen tersebut untuk saling berbicara.

Bagian hebat dari model Entity-Component-System adalah Anda dapat mengimplementasikan kemampuan untuk meneruskan pesan dari satu komponen ke komponen entitas yang lain dengan cukup mudah. Ini memungkinkan komponen untuk berbicara dengan komponen lain tanpa benar-benar mengetahui siapa komponen itu atau bagaimana menangani komponen itu berubah. Alih-alih mengirimkan pesan dan membiarkan komponen itu mengubah dirinya sendiri (jika ada)

Misalnya, Sistem Posisi tidak akan memiliki banyak kode di dalamnya, hanya melacak Objek Entitas yang dipetakan ke Komponen Posisi mereka. Tetapi ketika suatu posisi berubah, mereka dapat mengirim pesan kepada Entitas yang terlibat, yang pada gilirannya diserahkan ke semua komponen entitas itu. Posisi berubah karena alasan apa pun? Position System mengirim Entity pesan yang mengatakan bahwa posisi berubah, dan di suatu tempat, komponen rendering gambar entitas itu mendapatkan pesan itu dan memperbarui di mana ia akan menggambar dirinya sendiri berikutnya.

Sebaliknya, Sistem Fisika perlu mengetahui apa yang dilakukan semua objeknya; Itu harus dapat melihat semua objek dunia untuk menguji tabrakan. Ketika tabrakan terjadi, itu memperbarui komponen arah Entitas dengan mengirimkan semacam "Pesan Perubahan Arah" ke entitas alih-alih merujuk ke komponen Entitas secara langsung. Ini memisahkan manajer agar tidak perlu tahu cara mengubah arah dengan menggunakan pesan alih-alih mengandalkan komponen tertentu yang ada di sana (yang mungkin tidak ada sama sekali, dalam hal ini pesan hanya akan jatuh di telinga tuli alih-alih beberapa kesalahan terjadi karena objek yang diharapkan tidak ada).

Anda akan melihat keuntungan besar dari ini karena Anda menyebutkan Anda memiliki Antarmuka Jaringan. Komponen Jaringan akan mendengarkan semua pesan yang masuk yang harus diketahui semua orang. Ia menyukai gosip. Kemudian ketika Sistem Jaringan memperbarui, komponen-komponen Jaringan mengirim pesan-pesan itu ke Sistem Jaringan lain pada mesin klien lain, yang kemudian mengirim ulang pesan-pesan itu ke semua komponen lain untuk memperbarui posisi pemain, dll. Logika khusus mungkin diperlukan sehingga hanya entitas tertentu yang dapat mengirim pesan melalui jaringan tetapi itulah keindahan Sistem, Anda bisa mengendalikannya dengan mendaftarkan hal-hal yang benar.

Pendeknya:

Entity adalah komposisi Komponen yang dapat menerima pesan. Entity dapat menerima pesan, mendelegasikan pesan tersebut ke semua komponennya untuk memperbaruinya. (Posisi berubah Pesan, Arah Perubahan Kecepatan, dll.) Ini seperti kotak surat pusat yang semua komponen dapat dengar satu sama lain alih-alih berbicara langsung satu sama lain.

Komponen adalah bagian kecil dari Entitas yang menyimpan beberapa status entitas. Ini dapat mengurai pesan tertentu dan membuang pesan lainnya. Misalnya, "Komponen Arah" hanya akan peduli tentang "Pesan Perubahan Arah" tetapi tidak "Pesan Perubahan Posisi". Komponen memperbarui status mereka sendiri berdasarkan pesan, dan kemudian memperbarui status komponen lain dengan mengirim pesan dari Sistem mereka.

Sistem mengelola semua komponen dari jenis tertentu, dan bertanggung jawab untuk memperbarui komponen-komponen tersebut di setiap frame, serta mengirim pesan dari komponen yang mereka kelola ke Entitas yang menjadi milik Komponen.

Sistem bisa dapat memperbarui semua komponennya secara paralel, dan menyimpan semua pesan saat digunakan. Kemudian ketika eksekusi semua metode pembaruan Sistem selesai, Anda meminta setiap sistem untuk mengirimkan pesan mereka dalam urutan tertentu. Kontrol pertama mungkin, diikuti oleh Fisika, diikuti oleh arah, posisi, render, dll. Yang penting urutan mana mereka dikirim karena Perubahan Arah Fisika harus Selalu keluar mempertimbangkan perubahan arah berdasarkan kontrol.

Semoga ini membantu. Ini seperti Pola Desain, tapi sangat kuat jika dilakukan dengan benar.


0

Saya menggunakan sistem serupa di mesin saya dan cara saya melakukannya adalah setiap Entitas berisi daftar Komponen. Dari EntityManager, saya dapat meminta setiap Entitas dan melihat mana yang berisi Komponen yang diberikan. Contoh:

class Component
{
    private uint ID;
    // etc...
}

class Entity
{
    List<Component> Components;
    // etc...
    public bool Contains(Type type)
    {
        foreach(Component comp in Components)
        {
            if(typeof(comp) == type)
                return true;
        }
        return false;
    }
}

class EntityManager
{
    List<Entity> Entities;
    // etc...
    public List<Entity> GetEntitiesOfType(Type type)
    {
        List<Entity> results = new List<Entity>();
        foreach(Entity entity in Entities)
        {
            if(entity.Contains(type))
                results.Add(entity);
        }
        return results;
    }
}

Jelas ini bukan kode yang tepat (Anda sebenarnya membutuhkan fungsi template untuk memeriksa berbagai jenis komponen, daripada menggunakan typeof) tetapi konsepnya ada di sana. Kemudian Anda bisa mengambil hasil itu, referensi komponen yang Anda cari, dan mendaftarkannya ke pabrik Anda. Ini mencegah sambungan langsung antara Komponen dan manajernya.


3
Peringatan emptor: pada titik di mana Entitas Anda berisi data, itu adalah objek, bukan entitas ... Seseorang kehilangan sebagian besar manfaat paralelisasi (sic?) ECS dalam struktur ini. Sistem E / C / S "murni" bersifat relasional, tidak berorientasi objek ... Bukan berarti "buruk" untuk beberapa kasus, tetapi tentu saja "melanggar model relasional"
BRPocock

2
Saya tidak yakin saya mengerti Anda. Pemahaman saya (dan tolong koreksi saya jika saya salah) adalah Sistem Entitas-Komponen-dasar memiliki kelas Entitas yang menampung Komponen dan mungkin memiliki ID, nama, atau pengidentifikasi. Saya pikir kita mungkin memiliki kesalahpahaman dalam apa yang saya maksud dengan "tipe" Entitas. Ketika saya mengatakan Entity "type," Saya mengacu pada tipe Component. Dengan kata lain, Entitas adalah tipe "Sprite" jika mengandung Komponen Sprite.
Mike Cluck

1
Dalam sistem Entitas / Komponen murni, Entitas biasanya atom: misalnya typedef long long int Entity; Komponen adalah catatan (mungkin diimplementasikan sebagai kelas objek, atau hanya a struct) yang memiliki referensi ke Entitas yang dilampirkan; dan Sistem akan menjadi metode atau kumpulan metode. Model ECS tidak terlalu kompatibel dengan model OOP, meskipun Komponen dapat menjadi (kebanyakan) objek data saja, dan Sistem objek tunggal kode-satunya yang keadaannya hidup dalam komponen ... meskipun sistem "hybrid" adalah lebih umum daripada yang "murni", mereka kehilangan banyak manfaat bawaan.
BRPocock

2
@BRPocock re "murni" sistem entitas. Saya pikir entitas sebagai objek baik-baik saja, tidak harus berupa id sederhana. Satu hal adalah representasi serial, yang lain adalah tata letak in-memory dari suatu objek / konsep / entitas. Selama Anda dapat mempertahankan data-driven, seseorang tidak boleh terikat pada kode non-idiomatik hanya karena itu cara "murni".
Raine

1
@BRPocock ini adalah peringatan yang adil, tetapi untuk sistem entitas yang mirip "t-machine". Saya mengerti mengapa, tetapi itu bukan satu-satunya cara untuk memodelkan entitas berbasis komponen. aktor adalah alternatif yang menarik. Saya cenderung lebih menghargai mereka, terutama untuk entitas yang sepenuhnya logis.
Raine

0

1) Metode Pabrik Anda harus memberikan referensi ke EntityManager yang menyebutnya (saya akan menggunakan C # sebagai contoh):

delegate BaseEntity EntityFactory(EntityManager manager);

2) Miliki CreateEntity juga menerima id (mis. String, integer, terserah Anda) di samping kelas / jenis entitas, dan secara otomatis mendaftarkan entitas yang dibuat pada Kamus menggunakan id itu sebagai kunci:

class EntityManager
{
    // Rest of class omitted

    BaseEntity CreateEntity(string id, Type entityClass)
    {
        BaseEntity entity = factories[entityClass](this);
        registry.Add(id, entity);
        return entity;
    }

    Dictionary<Id, BaseEntity> registry;
}

3) Tambahkan Getter ke EntityManager untuk mendapatkan entitas apa pun dengan ID:

class EntityManager
{
    // Rest of class omitted

    BaseEntity GetEntity(string id)
    {
        return registry[id];
    }
}

Dan hanya itu yang Anda butuhkan untuk merujuk Manajer Komponen apa pun dari dalam metode Pabrik Anda. Contohnya:

BaseEntity CreateSomeSortOfEntity(EntityManager manager)
{
    // Create and configure entity
    BaseEntity entity = new BaseEntity();
    RenderComponent renderComponent = new RenderComponent();
    entity.AddComponent(renderComponent);

    // Get a reference to the render manager and register component
    RenderEntityManager renderer = manager.GetEntity("RenderEntityManager") as RenderEntityManager;
    if(renderer != null)
        renderer.Register(renderComponent)

    return entity;
}

Selain Id, Anda juga dapat menggunakan semacam properti Tipe (enum khusus, atau hanya mengandalkan sistem tipe bahasa), dan membuat pengambil yang mengembalikan semua BaseEntities dari jenis tertentu.


1
Bukan untuk menjadi bertele-tele, tetapi sekali lagi ... dalam sistem Entitas (relasional) murni, entitas tidak memiliki tipe, kecuali yang diberikan kepada mereka berdasarkan komponennya ...
BRPocock

@BRPocock: Bisakah Anda membuat contoh yang mengikuti kebajikan murni?
Zolomon

1
@Raine Mungkin, saya tidak punya pengalaman langsung dengan ini, tapi itulah yang saya baca. Dan ada optimasi yang dapat Anda terapkan untuk mengurangi waktu yang dihabiskan mencari komponen dengan id. Adapun koherensi cache, saya pikir itu masuk akal karena Anda menyimpan data dari tipe yang sama secara bersamaan di memori, terutama ketika komponen Anda sifat ringan atau sederhana. Saya telah membaca bahwa satu cache miss pada PS3 bisa semahal seribu instruksi CPU, dan pendekatan ini menyimpan data dengan tipe yang sama secara berdekatan adalah teknik optimasi yang sangat umum dalam pengembangan game modern.
David Gouveia

2
Dalam ref: sistem entitas "murni": ID Entitas biasanya kira-kira seperti typedef unsigned long long int EntityID;:; idealnya adalah, bahwa setiap Sistem dapat hidup pada CPU atau host yang terpisah, dan hanya perlu mengambil komponen yang relevan dengan / aktif dalam Sistem itu. Dengan objek Entity, seseorang mungkin harus instantiate objek Entity yang sama pada setiap host, membuat penskalaan lebih sulit. Model entitas-komponen-sistem murni membagi pemrosesan melintasi node (proses, CPU, atau host) oleh sistem, bukan oleh entitas, biasanya.
BRPocock

1
@DavidGouveia menyebutkan "optimisasi ... mencari entitas dengan ID." Bahkan, (beberapa) sistem yang saya terapkan dengan cara ini, cenderung tidak melakukannya. Lebih sering, pilih Komponen dengan beberapa pola yang menunjukkan bahwa mereka menarik bagi Sistem tertentu, menggunakan Entitas (ID) hanya untuk Gabungan komponen silang.
BRPocock
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.