Mesin berbasis Entity Component System

9

Catatan: Saya memprogram ini dalam Javascript, tetapi harus agnostik bahasa di sebagian besar.

Saya sedang berpikir tentang mengubah mesin saya ke yang berbasis ECS.

Saya mendapatkan ide dasarnya ( catatan: ini salah, lihat jawaban saya ):

Entitas adalah objek game.
Komponen adalah bit fungsionalitas ( reactToInput()) atau state ( position) yang bisa "direkatkan" ke entitas.
Sistem memiliki daftar entitas yang mereka kelola dan perbarui.

Tapi, saya tidak begitu yakin bahwa saya mendapatkan implementasi dan beberapa detail ...

Pertanyaan: dapatkah sistem beroperasi pada berbagai jenis entitas? Saya biasanya memberikan contoh kelas yang disebut Scenedi mesin saya, dan itu akan melayani tujuan ini sekarang juga. Adegan adalah wadah dari semua objek yang dapat dirender, diperbarui, memengaruhi rendering (lampu), dan mungkin, di masa depan, bahkan 2DSoundEmitterobjek. Ini memiliki antarmuka tingkat tinggi sehingga pengguna tidak perlu khawatir tentang jenis objek yang sedang scene.add()dikerjakannya, dan semua hal semacam itu.

Saya menyadari bahwa itu Scenebisa menjadi suatu sistem. Dibutuhkan dalam entitas, menyimpannya, dan kemudian dapat memanggil metode pembaruan mereka, dan bahkan mungkin melakukan beberapa perubahan status. Tapi, ada masalah: seperti yang saya jelaskan di atas, Scenebisa diberi makan berbagai jenis objek! Apa yang harus saya lakukan, katakanlah, situasi di mana sebuah adegan memiliki objek yang dapat diurai ("dapat digambar") dan lampu di dalamnya? Haruskah saya membuatnya memeriksa entitas sebelum berinteraksi? Atau, haruskah saya menyelesaikannya pada level yang lebih rendah lagi: membuat LightSourcekomponen yang dapat ditambahkan ke objek apa pun , dan cahayanya akan menjadi entitas LightSourcedan Positionkomponen. Apakah itu dapat diterima?

Juga, apakah itu praktik yang baik untuk tetap menggunakan warisan tradisional dan kelas tradisional? Misalnya, saya tidak tahu apa yang akan saya Renderermenjadi! Ini bukan sistem, karena satu-satunya fungsi adalah untuk mengambil kamera dan pemandangan, membuat semuanya dan menerapkan efek (seperti bayangan). Itu juga mengelola konteks, lebar dan tinggi permainan, membuat terjemahan ... Tapi itu masih bukan sistem!

Sunting: dapatkah Anda menautkan sumber daya apa pun yang Anda temukan di ECS? Saya kesulitan menemukan yang bagus.

jcora
sumber
2
Daripada memposting ulang jawaban di halaman ini, saya hanya akan memberikan tautan ini: gamedev.stackexchange.com/questions/23533/... Entitas tidak boleh berasal, perbedaan apa pun antara entitas harus diselesaikan melalui komponen. Secara umum Anda akan memerlukan antarmuka untuk setiap sistem utama (Rendering, Fisika, Jaringan, Input, Audio, dll ...). Cara saya mengatur penyaji saya adalah untuk meminta adegan untuk entitas yang dapat di-render, dan manajer adegan daripada meminta setiap entitas yang memiliki komponen render untuk informasi rendernya.
Nic Foster
1
Desain komponen pada blog T = Machine (karena Anda meminta yang bagus)
John McDonald
Kode dan diskusi kerangka kerja entitas: gamadu.com/artemis
Patrick Hughes
@JohnMcDonald, saya menulis komentar pada artikel itu, meskipun sedang menunggu moderasi. Anda dapat melihatnya di sini: t-machine.org/index.php/2007/12/22/… . Saya "Yannbane".
jcora
Juga, @NicFoster, artikel yang ditautkan John pada T = Machine menjelaskan sesuatu yang agak berbeda dari jawaban Anda ... Untuk itu Dave, entitas tidak memiliki daftar komponen, mereka hanya sebuah nama. Seperti "flsjn304" - itu adalah entitas. Itu disimpan "di suatu tempat". Dan saya harus membaca kembali hal itu untuk memahami apakah dia benar-benar menyimpan komponen di dalam sistem , yang tampaknya sangat aneh bagi saya!
jcora

Jawaban:

6

Biarkan saya melihat apakah dengan mencoba memahami sebagai web / UI JS dev, saya dapat membantu. Juga, jangan melangkah terlalu jauh dalam agnostisisme bahasa. Banyak pola yang dibuat dalam bahasa lain layak dipelajari tetapi dapat diterapkan sangat berbeda di JS karena fleksibilitasnya atau sebenarnya tidak diperlukan karena sifat bahasa yang mudah ditempa. Anda dapat menghancurkan beberapa peluang jika Anda menulis kode yang menganggap JS memiliki batas yang sama dengan bahasa berorientasi OOP yang lebih klasik.

Pertama-tama, pada faktor "jangan gunakan OOP", ingat bahwa objek JavaScript seperti playdough dibandingkan dengan bahasa lain dan Anda benar-benar harus pergi keluar dari cara Anda untuk membangun mimpi buruk skema warisan-cascading karena JS bukan kelas Berbasis dan pengomposisian datang secara alami. Jika Anda menerapkan beberapa kelas konyol atau prototipe sistem hand-down di JS Anda, pertimbangkan membuangnya. Di JS kami menggunakan penutupan, prototipe, dan kami melewati fungsi seperti permen. Itu menjijikkan dan kotor dan salah tetapi juga kuat, ringkas dan itulah cara kami menyukainya.

Pendekatan berat warisan sebenarnya dieja sebagai anti-pola dalam Pola Desain dan untuk alasan yang baik sebagai siapa saja yang telah berjalan 15+ level senilai kelas atau struktur kelas-seperti untuk mencoba dan mencari tahu di mana sih versi rusak dari suatu metode datang dari dapat memberitahu Anda.

Saya tidak tahu mengapa begitu banyak programmer suka melakukan ini (terutama orang-orang java menulis JavaScript untuk beberapa alasan), tapi itu mengerikan, tidak terbaca, dan benar-benar tidak dapat dipelihara bila digunakan secara berlebihan. Warisan baik-baik saja di sana-sini, tetapi tidak terlalu diperlukan di JS. Dalam bahasa yang merupakan jalan pintas yang lebih memikat, itu harus benar-benar dicadangkan untuk masalah arsitektur yang lebih abstrak daripada skema pemodelan yang lebih literal seperti frankensteining implementasi zombie melalui rantai pewarisan yang menyertakan BunnyRabbit karena kebetulan bekerja. Itu bukan kode yang baik digunakan kembali. Ini adalah mimpi buruk pemeliharaan.

Sebagai JS dev Entity / Component / System engine engine menyerang saya sebagai sistem / pola untuk decoupling masalah desain dan kemudian mengomposit objek untuk implementasi pada level yang sangat granular. Dengan kata lain, permainan anak dalam bahasa seperti JavaScript. Tapi biarkan saya melihat apakah saya grokking ini dengan benar terlebih dahulu.

  • Entity - Hal spesifik yang Anda rancang. Kita berbicara lebih banyak ke arah kata benda yang tepat (tapi tentu saja tidak sebenarnya). Bukan 'Scene', tapi 'IntroAreaLevelOne'. IntroAreaLevelOne mungkin duduk di dalam kotak sceneEntity dari beberapa jenis tetapi kami fokus pada sesuatu yang spesifik yang bervariasi dari hal-hal terkait lainnya. Dalam kode tersebut, suatu entitas benar-benar hanya sebuah nama (atau ID) yang diikat ke banyak hal yang harus diimplementasikan atau dibuat (komponen) agar bermanfaat.

  • Komponen - jenis hal yang dibutuhkan entitas. Ini adalah kata benda umum. Seperti WalkingAnimation. Dalam WalkingAnimation kita bisa mendapatkan yang lebih spesifik, seperti "Shambling" (pilihan yang baik untuk zombie dan monster tanaman), atau "ChickenWalker" (bagus untuk tipe robot reverse-joint ed-209ish). Catatan: Tidak yakin bagaimana itu bisa lepas dari rendering model 3D seperti itu - jadi mungkin contoh omong kosong tapi saya lebih dari pro JS daripada pengembang game yang berpengalaman. Di JS saya akan meletakkan mekanisme pemetaan dalam kotak yang sama dengan komponen. Komponen dalam hak mereka sendiri cenderung ringan pada logika dan lebih dari peta jalan memberitahu sistem Anda apa yang harus diterapkan jika sistem bahkan diperlukan (dalam upaya saya di ECS beberapa komponen hanya kumpulan set properti). Setelah komponen terbentuk, itu '

  • Sistem - Daging program yang sebenarnya ada di sini. Sistem AI dibangun dan dihubungkan, Rendering tercapai, sekuens animasi dibuat, dll ... Saya menghabiskan sebagian besar ini untuk imajinasi tetapi dalam contoh System.AI mengambil banyak properti dan mengeluarkan fungsi yang digunakan untuk menambahkan event handler ke objek yang akhirnya akan digunakan dalam implementasi. Kuncinya tentang System.AI adalah bahwa ia mencakup beberapa jenis komponen. Anda bisa memilah-milah semua barang AI dengan satu komponen tetapi untuk melakukannya adalah salah paham tentang titik pembuatan barang-barang granular.

Pikirkan Tujuan: Kami ingin membuatnya mudah untuk menyambungkan beberapa jenis antarmuka GUI untuk non-desainer untuk dengan mudah mengubah berbagai jenis barang dengan memaksimalkan dan mencocokkan komponen dalam paradigma yang masuk akal bagi mereka, dan kami ingin menjauh dari skema kode arbitrer populer yang jauh lebih mudah untuk ditulis daripada harus dimodifikasi atau dipelihara.

Jadi di JS, mungkin kira-kira seperti ini. Game devs, tolong beri tahu saya jika saya salah:

//I'm going with simple objects of flags over arrays of component names
//easier to read and can provide an opt-out default
//Assume a genre-bending stealth assassin game

//new (function etc... is a lazy way to define a constructor and auto-instantiate
var npcEntities = new (function NpcEntities(){

    //note: {} in JS is an object literal, a simple obj namespace (a dictionary)
    //plain ol' internal var in JS is akin to a private member
    var default={ //most NPCs are humanoids and critters - why repeat things?
        speedAttributes:true,
        maneuverAttributes:true,
        combatAttributes:true,
        walkingAnimation:true,
        runningAnimation:true,
        combatAnimation:true,
        aiOblivious:true,
        aiAggro:true,
        aiWary:true, //"I heard something!"
        aiFearful:true
    };

    //this. exposes as public

    this.zombie={ //zombies are slow, but keep on coming so don't need these
        runningAnimation:false,
        aiFearful:false
    };

    this.laserTurret={ //most defaults are pointless so ignore 'em
        ignoreDefault:true,
        combatAttributes:true,
        maneuverAttrubtes:true, //turning speed only
    };
    //also this.nerd, this.lawyer and on and on...

    //loop runs on instantiation which we're forcing on the spot

    //note: it would be silly to repeat this loop in other entity collections
    //but I'm spelling it out to keep things straight-forward.
    //Probably a good example of a place where one-level inheritance from
    //a more general entity class might make sense with hurting the pattern.
    //In JS, of course, that would be completely unnecessary. I'd just build a
    //constructor factory with a looping function new objects could access via
    //closure.

    for(var x in npcEntities){

        var thisEntity = npcEntities[x];

        if(!thisEntity.ignoreDefaults){

            thisEntity = someObjectXCopyFunction(defaults,thisEntity);
            //copies entity properties over defaults

        }
        else {
            //remove nonComponent property since we loop again later
            delete thisEntity.ignoreDefaults;
        }
    }
})() //end of entity instantiation

var npcComponents = {
    //all components should have public entityMap properties

    //No systems in use here. Just bundles of related attributes
    speedAttributes: new (function SpeedAttributes(){
        var shamblingBiped = {
            walkingAcceleration:1,
            topWalking:3
        },
        averageMan = {
            walkingAcceleration:3,
            runningAcceleration:4,
            topWalking: 4,
            topRunning: 6
        },
        programmer = {
            walkingAcceleration:1,
            runningAcceleration:100,
            topWalking:2
            topRunning:2000
        }; //end local/private vars

        //left is entity names | right is the component subcategory
        this.entityMap={
            zombie:shamblingBiped,
            lawyer:averageMan,
            nerd:programmer,
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(), //end speedAttributes

    //Now an example of an AI component - maps to function used to set eventHandlers
    //functions which, because JS is awesome we can pass around like candy
    //I'll just use some imaginary systems on this one

    aiFearful: new (function AiFearful(){
        var averageMan = Systems.AI({ //builds and returns eventSetting function
            fearThreshold:70, //%hitpoints remaining
            fleeFrom:'lastAttacker',
            tactic:'avoidIntercept',
            hazardAwareness:'distracted'
        }),
        programmer = Systems.AI({
            fearThreshold:95,
            fleeFrom:'anythingMoving',
            tactic:'beeline',
            hazardAwareness:'pantsCrappingPanic'
        });//end local vars/private members


         this.entityMap={
            lawyer:averageMan,
            nerd:averageMan, //nerds can run like programmers but are less cowardly
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(),//and more components...

    //Systems.AI is general and would get called for all the AI components.
    //It basically spits out functions used to set events on NPC objects that
    //determine their behavior. You could do it all in one shot but
    //the idea is to keep it granular enough for designers to actually tweak stuff
    //easily without tugging on developer pantlegs constantly.
    //e.g. SuperZombies, zombies, but slightly tougher, faster, smarter
}//end npcComponents

function createNPCConstructor(npcType){

    var components = npcEntities[npcType],

    //objConstructor is returned but components is still accessible via closure.

    objConstructor = function(){
        for(var x in components){
            //object iteration <property> in <object>

            var thisComponent = components[x];

            if(typeof thisComponent === 'function'){
                thisComponent.apply(this);
                //fires function as if it were a property of instance
                //would allow the function to add additional properties and set
                //event handlers via the 'this' keyword
            }
            else {
                objConstructor.prototype[x] = thisComponent;
                //public property accessed via reference to constructor prototype
                //good for low memory footprint among other things
            }
        }
    }
    return objConstructor;
}

var npcBuilders= {}; //empty object literal
for (var x in npcEntities){
    npcConstructors[x] = createNPCConstructor(x);
}

Sekarang kapan pun Anda membutuhkan NPC, Anda bisa membuatnya npcBuilders.<npcName>();

GUI dapat menyambung ke objek npcEntities dan komponen dan memungkinkan desainer untuk mengubah entitas lama atau hanya dengan mencampur dan mencocokkan komponen (meskipun tidak ada mekanisme di sana untuk komponen non-standar tetapi komponen khusus dapat ditambahkan dengan cepat di kode selama ada komponen yang ditentukan untuk itu.

Erik Reppen
sumber
Melihat ini enam tahun kemudian, saya tidak yakin saya mengerti jawaban saya sendiri. Bisakah ini diperbaiki?
Erik Reppen
1

Saya telah membaca tentang Entity Systems di artikel yang diberikan orang-orang baik di komentar, tetapi saya masih memiliki beberapa keraguan, jadi saya mengajukan pertanyaan lain .

Pertama, definisi saya salah. Entitas dan Komponen hanyalah pemegang data yang bodoh, sementara sistem menyediakan semua fungsionalitas.

Saya cukup belajar untuk menutupi sebagian besar pertanyaan saya di sini, jadi saya akan menjawabnya.

The Scenekelas I bicarakan seharusnya tidak menjadi sebuah sistem. Namun, itu harus menjadi manajer pusat yang dapat menampung semua entitas, memfasilitasi pesan, dan bahkan mungkin mengelola sistem. Itu juga dapat berfungsi sebagai semacam pabrik untuk entitas, dan saya telah memutuskan untuk menggunakannya seperti itu. Itu dapat mengambil semua jenis entitas, tetapi kemudian harus memberi makan entitas itu ke sistem yang sesuai (yang, karena alasan kinerja, tidak boleh melakukan pemeriksaan tipe apa pun, kecuali bitwise).

Saya seharusnya tidak menggunakan OOP saat mengimplementasikan ES, saran Adam, tetapi saya tidak menemukan alasan untuk tidak memiliki objek dengan metode untuk kedua entitas dan komponen, bukan hanya pemegang data bodoh.

Ini Rendererhanya dapat diimplementasikan sebagai suatu sistem. Itu akan memelihara daftar objek yang dapat digambar dan memanggil draw()metode komponen render mereka masing-masing 16 ms.

jcora
sumber
1
"Entitas dan Komponen hanyalah pemegang data yang bodoh, sementara sistem" "menyediakan semua fungsionalitas, memanggil metode draw komponen render mereka ()," Anda masih bingung, selain itu metode "draw" mengalahkan tujuan sistem Rendering. Juga saya tidak mengerti mengapa grafik Scene Anda tidak bisa menjadi bagian dari Renderer, itu hanya alat yang nyaman, Anda selalu dapat menerapkan komponen "yang dapat digambar" sebagai sebuah simpul. Membuat grafik adegan lebih bertanggung jawab daripada adegan itu tidak perlu dan saya yakin itu akan berantakan untuk debug.
Dreta
@dreta, renderer saat ini (implementasi mesin non-ES) melakukan transformasi, perubahan kamera, hal-hal alfa, dan di masa depan itu akan menarik berbagai efek, GUI dan bayangan. Rasanya wajar untuk mengelompokkan barang-barang itu. Bukankah adegan harus bertanggung jawab untuk membuat entitas penyimpanan? Atau haruskah ada yang menyimpannya? Bagian penciptaan mungkin hanya beberapa baris agregat komponen yang disediakan pengguna bersama-sama, itu tidak benar-benar "menciptakan" apa pun, hanya instancing.
jcora
tidak setiap objek dapat di-render, tidak setiap objek dapat bertabrakan dengan atau mengeluarkan suara, dengan objek pemandangan yang sedang Anda lakukan kopling ekstrem, mengapa? ini hanya akan merepotkan untuk menulis dan men-debug. Entitas digunakan untuk mengidentifikasi objek, komponen menyimpan data dan sistem beroperasi pada data. Mengapa Anda menggabungkan semua itu daripada memiliki sistem yang tepat seperti RenderingSystem dan SoundSystem dan hanya mengganggu sistem itu jika suatu entitas memiliki semua komponen yang diperlukan.
dreta
1
pengecoran bayangan biasanya melekat pada sumber cahaya, meskipun Anda hanya dapat membuat komponen "CastsShadow" dan mencarinya saat merender objek dinamis. jika Anda melakukan 2D maka ini hanya masalah dasar pemesanan, algoritma pelukis sederhana akan menyelesaikan masalah ini untuk Anda. TBH Anda terlalu cepat khawatir. Anda akan menemukan ini ketika saatnya untuk melakukannya dan Anda hanya memiliki itu di pikiran Anda, saat ini Anda hanya membingungkan diri sendiri. Anda tidak dapat berharap untuk melakukan semuanya dengan benar pada kali pertama, itu tidak akan terjadi. Anda akan menyeberangi jembatan itu ketika sampai di sana.
dreta
1
"Entitas dan Komponen hanyalah pemegang data yang bodoh, sementara sistem menyediakan semua fungsionalitas." Belum tentu. Mereka berada dalam pendekatan beberapa orang. Tapi tidak yang lain. Lihatlah mesin Unity - semua perilaku ada di komponen.
Kylotan
-2

Pengantar manajemen ketergantungan 101.

Kursus ini mengasumsikan Anda memiliki pengetahuan dasar tentang injeksi ketergantungan dan desain repositori.

Ketergantungan injeksi hanyalah cara mewah untuk objek untuk berbicara satu sama lain (melalui pesan / sinyal / delegasi / apa pun) tanpa langsung digabungkan.

Itu berjalan dengan ungkapan: " newadalah lem."

Saya akan menunjukkan ini dalam C #.

public interface IEntity
{
    int[] Position { get; }
    int[] Size { get; }
    bool Update();
    void Render();
}

public interface IRenderSystem
{
    void Draw(IEntity entity);
}

public interface IMovementSystem
{
    bool CanMoveLeft();
    void MoveLeft();
    bool CanMoveRight();
    void MoveRight();
    bool CanMoveUp();
    void MoveUp();
    bool CanMoveDown();
    void MoveDown();
    bool Moved();
    int[] Position { get; set; }
}

public interface IInputSystem
{
    string Direction { get; set; }
}

public class Player : IEntity
{
    private readonly IInputSystem _inputSystem;
    private readonly IMovementSystem _movementSystem;
    private readonly IRenderSystem _renderSystem;
    private readonly int[] _size = new[] { 10, 10 };

    public Player(IRenderSystem renderSystem, IMovementSystem movementSystem, IInputSystem inputSystem)
    {
        _renderSystem = renderSystem;
        _movementSystem = movementSystem;
        _inputSystem = inputSystem;
    }

    public bool Update()
    {
        if (_inputSystem.Direction == "Left" && _movementSystem.CanMoveLeft())
            _movementSystem.MoveLeft();
        if (_inputSystem.Direction == "Right" && _movementSystem.CanMoveRight())
            _movementSystem.MoveRight();
        if (_inputSystem.Direction == "Up" && _movementSystem.CanMoveUp())
            _movementSystem.MoveUp();
        if (_inputSystem.Direction == "Down" && _movementSystem.CanMoveDown())
            _movementSystem.MoveDown();

        return _movementSystem.Moved();
    }

    public void Render()
    {
        if (_movementSystem.Moved())
            _renderSystem.Draw(this);
    }

    public int[] Position
    {
        get { return _movementSystem.Position; }
    }

    public int[] Size
    {
        get { return _size; }
    }
}

Ini adalah sistem dasar untuk Input, Gerakan, dan Rendering. The Playerkelas entitas dalam kasus ini dan komponen adalah Interfaces. The Playerkelas tahu apa-apa tentang internal bagaimana beton IRenderSystem, IMovementSystematau IInputSystempekerjaan. Namun, antarmuka menyediakan cara untuk Playermengirim sinyal (misalnya memanggil Draw di IRenderSystem) tanpa tergantung pada bagaimana hasil akhir dicapai.

Sebagai contoh, ambil implementasi IMovementSystem saya:

public interface IGameMap
{
    string LeftOf(int[] currentPosition);
    string RightOf(int[] currentPosition);
    string UpOf(int[] currentPosition);
    string DownOf(int[] currentPosition);
}

public class MovementSystem : IMovementSystem
{
    private readonly IGameMap _gameMap;
    private int[] _previousPosition;
    private readonly int[] _currentPosition;
    public MovementSystem(IGameMap gameMap, int[] initialPosition)
    {
        _gameMap = gameMap;
        _currentPosition = initialPosition;
        _previousPosition = initialPosition;
    }

    public bool CanMoveLeft()
    {
        return _gameMap.LeftOf(_currentPosition) == "Unoccupied";
    }

    public void MoveLeft()
    {
        _previousPosition = _currentPosition;
        _currentPosition[0]--;
    }

    public bool CanMoveRight()
    {
        return _gameMap.RightOf(_currentPosition) == "Unoccupied";
    }

    public void MoveRight()
    {
        _previousPosition = _currentPosition;
        _currentPosition[0]++;
    }

    public bool CanMoveUp()
    {
        return _gameMap.UpOf(_currentPosition) == "Unoccupied";
    }

    public void MoveUp()
    {
        _previousPosition = _currentPosition;
        _currentPosition[1]--;
    }

    public bool CanMoveDown()
    {
        return _gameMap.DownOf(_currentPosition) == "Unoccupied";
    }

    public void MoveDown()
    {
        _previousPosition = _currentPosition;
        _currentPosition[1]++;
    }

    public bool Moved()
    {
        return _previousPosition == _currentPosition;
    }

    public int[] Position
    {
        get { return _currentPosition; }
    }
}

MovementSystemdapat memiliki dependensinya sendiri dan Playerbahkan tidak akan peduli. Dengan menggunakan antarmuka, mesin status permainan dapat dibuat:

public class GameEngine
{
    private readonly List<IEntity> _entities;
    private List<IEntity> _renderQueue; 

    public GameEngine()
    {
        _entities = new List<IEntity>();
    }

    public void RegisterEntity(IEntity entity)
    {
        _entities.Add(entity);
    }

    public void Update()
    {
        _renderQueue = new List<IEntity>();
        foreach (var entity in _entities)
        {
            if(entity.Update())
                _renderQueue.Add(entity);
        }
        // Linq version for those interested
        //_renderQueue.AddRange(_entities.Where(e => e.Update()));
    }

    public void Render()
    {
        foreach (var entity in _renderQueue)
        {
            entity.Render();
        }
    }
}

Dan itu adalah awal dari permainan yang indah (itu juga bisa diuji unit).

Dan dengan beberapa tambahan dan beberapa polimorfisme:

public interface IEntity
{
}

public interface IRenderableEntity : IEntity
{
    void Render();        
}

public interface IUpdateableEntity : IEntity
{
    void Update();
    bool Updated { get; }
}

public interface IRenderSystem
{
    void Draw(IRenderableEntity entity);
}

// new player class
public class Player : IRenderableEntity, IUpdateableEntity
{
    private readonly IInputSystem _inputSystem;
    private readonly IMovementSystem _movementSystem;
    private readonly IRenderSystem _renderSystem;
    private readonly int[] _size = new[] { 10, 10 };

    public Player(IRenderSystem renderSystem, IMovementSystem movementSystem, IInputSystem inputSystem)
    {
        _renderSystem = renderSystem;
        _movementSystem = movementSystem;
        _inputSystem = inputSystem;
    }

    public void Update()
    {
        if (_inputSystem.Direction == "Left" && _movementSystem.CanMoveLeft())
            _movementSystem.MoveLeft();
        if (_inputSystem.Direction == "Right" && _movementSystem.CanMoveRight())
            _movementSystem.MoveRight();
        if (_inputSystem.Direction == "Up" && _movementSystem.CanMoveUp())
            _movementSystem.MoveUp();
        if (_inputSystem.Direction == "Down" && _movementSystem.CanMoveDown())
            _movementSystem.MoveDown();
    }

    public bool Updated
    {
        get { return _movementSystem.Moved(); }
    }

    public void Render()
    {
        if (_movementSystem.Moved())
            _renderSystem.Draw(this);
    }

    public int[] Position
    {
        get { return _movementSystem.Position; }
    }

    public int[] Size
    {
        get { return _size; }
    }
}

public class GameEngine
{
    private readonly List<IEntity> _entities;
    private List<IRenderableEntity> _renderQueue; 

    public GameEngine()
    {
        _entities = new List<IEntity>();
    }

    public void RegisterEntity(IEntity entity)
    {
        _entities.Add(entity);
    }

    public void Update()
    {
        _renderQueue = new List<IRenderableEntity>();
        foreach (var entity in _entities)
        {
            if (entity is IUpdateableEntity)
            {
                var updateEntity = entity as IUpdateableEntity;
                updateEntity.Update();
            }

            if (entity is IRenderableEntity)
            {
                var renderEntity = entity as IRenderableEntity;
                _renderQueue.Add(renderEntity);
            }
        }
    }

    public void Render()
    {
        foreach (var entity in _renderQueue)
        {
            entity.Render();
        }
    }
}

Kami sekarang memiliki sistem entitas / komponen primitif yang didasarkan pada antarmuka agregasi dan warisan yang longgar.

Dustin Kingen
sumber
1
Ini sangat bertentangan dengan desain komponen :) Apa yang akan Anda lakukan jika Anda ingin satu pemain membuat suara dan yang lainnya tidak?
Kikaimaru
@Kikaimaru Pass dalam ISoundSystem yang tidak memutar suara. yaitu Tidak melakukan apa-apa.
Dustin Kingen
3
-1, bukan karena itu kode yang buruk, tetapi karena itu tidak relevan sama sekali dengan arsitektur berbasis komponen - pada kenyataannya itu adalah proliferasi dari antarmuka seperti ini yang berusaha dihindari oleh komponen.
Kylotan
@Kylotan Saya kira pemahaman saya pasti salah.
Dustin Kingen