Motore basato su Entity Component System


9

Nota: lo sto programmando in Javascript, ma dovrebbe essere in gran parte indipendente dal linguaggio.

Sto pensando di convertire il mio motore in uno basato su ECS.

Ottengo l'idea di base ( nota: questo è sbagliato, vedi la mia risposta ):

Le entità sono oggetti di gioco.
I componenti sono bit di funzionalità ( reactToInput()) o state ( position) che possono essere "incollati" alle entità.
I sistemi dispongono di un elenco di entità che gestiscono e aggiornano.

Ma non sono del tutto sicuro di ottenere l'implementazione e alcuni dettagli ...

Domanda: un sistema può operare su diversi tipi di entità? Di solito faccio l'esempio di una classe chiamata Scenenel mio motore, e ora servirà anche a questo scopo. Una scena è un contenitore di tutti gli oggetti che possono essere renderizzati, aggiornati, influenzano il rendering (luci) e forse, in futuro, anche gli 2DSoundEmitteroggetti. Ha un'interfaccia di alto livello in modo che l'utente non debba preoccuparsi del tipo di oggetto scene.add()e di tutto quel genere di cose.

Mi rendo conto che Scenepotrebbe essere un sistema. Accetta entità, le memorizza e quindi può chiamare i loro metodi di aggiornamento e forse anche apportare alcune modifiche di stato. Ma c'è un problema: come ho descritto sopra, è Scenepossibile alimentare diversi tipi di oggetti! Cosa dovrei fare, diciamo, in una situazione in cui una scena contiene sia oggetti renderizzabili ("disegnabili") sia luci? Devo farlo controllare le entità prima di interagire? Oppure, dovrei risolverlo a un livello ancora più basso: creare un LightSourcecomponente che può essere aggiunto a qualsiasi oggetto, e la luce sarebbe solo un'entità con LightSourcee Positioncomponenti. È accettabile?

Inoltre, è una buona pratica utilizzare ancora l'eredità convenzionale e le classi tradizionali? Ad esempio, non riesco proprio a capire quale sarebbe il mio Renderer! Non è un sistema, in quanto la sua unica funzione è quella di catturare una telecamera e una scena, renderizzare tutto e applicare effetti (come le ombre). Gestisce anche il contesto, la larghezza e l'altezza del gioco, fa traduzioni ... Ma non è ancora un sistema!

Modifica: potresti collegare alcune risorse che hai trovato sull'ECS? Ho problemi a trovarne di buoni.


2
Invece di ripubblicare la risposta in questa pagina, fornirò solo questo link: gamedev.stackexchange.com/questions/23533/… L' entità non dovrebbe essere derivata, qualsiasi differenza tra entità dovrebbe essere realizzata attraverso i componenti. Generalmente avrai bisogno di un'interfaccia per ogni sistema principale (Rendering, Fisica, Networking, Input, Audio, ecc ...). Il modo in cui ho impostato il mio renderer è di interrogare la scena per entità renderizzabili, e il gestore della scena chiede a ciascuna entità che ha un componente di rendering su di essa per le sue informazioni di rendering.
Nic Foster,

1
Progettazione dei componenti sul blog T = Machine (da quando ne hai chiesto una buona)
John McDonald

Codice e discussione di un framework di entità: gamadu.com/artemis
Patrick Hughes,

@JohnMcDonald, ho scritto un commento su quell'articolo, anche se è in attesa di moderazione. Puoi vederlo qui: t-machine.org/index.php/2007/12/22/… . Sono "Yannbane".
jcora,

Inoltre, @NicFoster, l'articolo che John ha collegato su T = Machine descrive qualcosa di diverso dalla tua risposta ... A quel Dave, le entità non hanno un elenco di componenti, sono solo un nome. Come "flsjn304" - questa è un'entità. È memorizzato "da qualche parte". E devo rileggere la cosa per capire se mantiene effettivamente i componenti all'interno dei sistemi , il che mi sembra molto strano!
jcora,

Risposte:


6

Fammi vedere se provando a capire come sviluppatore JS web / UI, posso esserti di aiuto. Inoltre, non andare troppo lontano nell'agnosticismo linguistico. Molti modelli stabiliti in altre lingue meritano di essere studiati, ma possono essere applicati in modo molto diverso in JS a causa della sua flessibilità o in realtà non sono necessari a causa della natura malleabile della lingua. Potresti cogliere alcune opportunità se scrivi il tuo codice pensando a JS di avere lo stesso insieme di confini di un linguaggio più classico orientato verso OOP.

Prima di tutto, sul fattore "non usare OOP", ricorda che gli oggetti JavaScript sono come la pasta madre rispetto ad altre lingue e devi davvero fare di tutto per costruire un incubo a cascata con ereditarietà poiché JS non è di classe a base e il compositing arriva molto più naturalmente ad esso. Se stai implementando qualche stupida classe o prototipo di sistema manuale nel tuo JS, considera di abbandonarlo. In JS utilizziamo chiusure, prototipi e trasmettiamo funzioni come caramelle. È disgustoso, sporco e sbagliato ma anche potente, conciso ed è così che ci piace.

Approcci pesanti di ereditarietà sono in realtà indicati come un anti-pattern in Design Patterns e per una buona ragione come chiunque abbia camminato per oltre 15 livelli di strutture di classe o di classe per cercare di capire dove diavolo la versione rotta di un metodo stava arrivando da te lo posso dire.

Non so perché così tanti programmatori adorino farlo (specialmente i ragazzi java che scrivono JavaScript per qualche motivo), ma è terribile, illeggibile e completamente non mantenibile se usato in eccesso. L'ereditarietà va bene qua e là, ma non è davvero necessaria in JS. Nelle lingue in cui si tratta di una scorciatoia più allettante, dovrebbe davvero essere riservato a preoccupazioni di architettura più astratte piuttosto che a schemi di modellazione più letterali come Frankensteining un'implementazione di zombi attraverso una catena di eredità che includeva un BunnyRabbit perché funzionava. Non è un buon riutilizzo del codice. È un incubo per la manutenzione.

Come sviluppatore di JS, i motori basati su entità / componenti / sistemi mi colpiscono come un sistema / modello per disaccoppiare le preoccupazioni di progettazione e quindi comporre gli oggetti per l'implementazione a un livello altamente granulare. In altre parole, un gioco da ragazzi in una lingua come JavaScript. Ma fammi vedere se sto facendo questo trekking prima.

  • Entità - La cosa specifica che stai progettando. Stiamo parlando più nella direzione dei nomi propri (ma non in realtà, ovviamente). Non "Scene", ma "IntroAreaLevelOne". IntroAreaLevelOne potrebbe trovarsi all'interno di una scatola di SceneEntity di qualche tipo, ma ci stiamo concentrando su qualcosa di specifico che varia da altre cose correlate. Nel codice, un'entità è in realtà solo un nome (o ID) legato a un mucchio di cose che deve essere implementato o stabilito (i componenti) per essere utile.

  • Componenti: tipi di cose di cui un'entità ha bisogno. Questi sono nomi generali. Come WalkingAnimation. All'interno di WalkingAnimation possiamo essere più specifici, come "Shambling" (buona scelta per zombi e mostri vegetali), o "ChickenWalker" (ottimo per i tipi di robot ed-209ish a giunto inverso). Nota: non sono sicuro di come questo potrebbe essere disaccoppiato dal rendering di un modello 3D del genere - quindi forse un esempio di merda, ma io sono più un professionista JS che uno sviluppatore di gioco esperto. In JS metterei il meccanismo di mappatura nella stessa scatola con i componenti. I componenti a sé stanti sono probabilmente leggeri sulla logica e più di una roadmap che dice ai tuoi sistemi cosa implementare se i sistemi sono persino necessari (nel mio tentativo di ECS alcuni componenti sono solo raccolte di insiemi di proprietà). Una volta stabilito un componente, esso "

  • Sistemi - La vera carne programmata è qui. I sistemi di intelligenza artificiale sono costruiti e collegati, il rendering è raggiunto, le sequenze di animazioni stabilite, ecc ... Sto uscendo e lasciandole principalmente all'immaginazione, ma nell'esempio System.AI prende un sacco di proprietà e sputa fuori una funzione che viene utilizzato per aggiungere gestori di eventi all'oggetto che alla fine viene utilizzato nell'implementazione. La cosa fondamentale di System.AI è che copre più tipi di componenti. È possibile risolvere tutte le cose di intelligenza artificiale con un solo componente, ma farlo significa fraintendere il punto di rendere le cose granulari.

Mind the Goals: Vogliamo semplificare il collegamento di una sorta di interfaccia GUI per i non progettisti per modificare facilmente diversi tipi di roba maxing e abbinare i componenti all'interno di un paradigma che ha senso per loro, e vogliamo allontanarci da schemi di codice arbitrario popolari che sono molto più facili da scrivere di quanto non siano da modificare o mantenere.

Quindi in JS, forse qualcosa del genere. Gli sviluppatori di giochi, per favore, dimmi se ho sbagliato in modo orribile:

//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);
}

Ora, ogni volta che hai bisogno di un NPC, puoi costruire con npcBuilders.<npcName>();

Una GUI potrebbe collegarsi agli oggetti npcEntities e componenti e consentire ai progettisti di modificare vecchie entità o creare nuove entità semplicemente mescolando e abbinando i componenti (sebbene non vi sia alcun meccanismo per componenti non predefiniti ma componenti speciali potrebbero essere aggiunti al volo nel codice purché esistesse un componente definito per esso.


Guardandolo sei anni dopo, non sono sicuro di aver capito la mia risposta. Questo potrebbe essere migliorato?
Erik Reppen,

1

Ho letto su Entity Systems negli articoli che le persone gentili hanno fornito nei commenti, ma avevo ancora dei dubbi, quindi ho fatto un'altra domanda .

Prima di tutto, le mie definizioni erano sbagliate. Entità e componenti sono semplicemente stupidi detentori di dati, mentre i sistemi forniscono tutte le funzionalità.

Ho imparato abbastanza per coprire la maggior parte della mia domanda qui, quindi risponderò.

La Sceneclasse di cui parlavo non dovrebbe essere un sistema. Dovrebbe, tuttavia, essere un gestore centrale in grado di contenere tutte le entità, facilitare i messaggi e forse persino gestire i sistemi. Può anche funzionare come una sorta di fabbrica per le entità, e ho deciso di usarla così. Si può prendere qualsiasi tipo di entità, ma poi si deve alimentare a tale soggetto di un sistema adeguato (che, per motivi di prestazioni, non dovrebbe eseguire qualsiasi tipo di controlli, a meno che siano bit a bit).

Non dovrei usare alcun OOP durante l'implementazione di un ES, suggerisce Adam, ma non trovo alcun motivo per non avere oggetti con metodi sia per entità che per componenti, non solo stupidi titolari di dati.

Il Rendererpuò semplicemente essere implementato come un sistema. draw()Manterrebbe un elenco di oggetti disegnabili e chiamerebbe il metodo del componente di rendering ogni 16 ms.


1
"Entità e componenti sono semplicemente stupidi detentori di dati, mentre i sistemi forniscono tutte le funzionalità" "chiamano il metodo draw () del loro componente di rendering" sei ancora confuso, inoltre un metodo "draw" sconfigge del tutto la finalità del sistema di rendering. Inoltre non capisco perché il tuo grafico Scene non possa far parte del Renderer, è solo uno strumento conveniente, puoi sempre implementare il tuo componente "disegnabile" come nodo. Rendere il grafico della scena più responsabile della scena non è solo necessario e sono sicuro che sarà un disastro per il debug.
dreta,

@dreta, il renderer attualmente (implementazione non ES del motore) esegue trasformazioni, cambi di videocamera, elementi alfa e in futuro disegnerà vari effetti, la GUI e le ombre. Sembrava naturale raggruppare quella roba. La scena non dovrebbe essere responsabile della creazione di entità di memorizzazione? O qualcos'altro dovrebbe memorizzarli? La parte della creazione è probabilmente solo alcune righe di aggregazione dei componenti forniti dall'utente, non è in realtà "la creazione" di nulla, solo istanza.
jcora,

non tutti gli oggetti sono renderizzabili, non tutti gli oggetti possono essere scontrati o emettono un suono, con il tuo oggetto scena stai facendo un accoppiamento estremo, perché? questo sarà solo un dolore per scrivere e debug. Le entità vengono utilizzate per identificare un oggetto, i componenti contengono dati e i sistemi operano sui dati. Perché dovresti mettere insieme tutto questo invece di avere sistemi adeguati come RenderingSystem e SoundSystem e disturbare quei sistemi solo se un'entità ha tutti i componenti richiesti.
dreta,

1
il cast dell'ombra è solitamente associato a fonti di luce, anche se puoi semplicemente creare un componente "CastsShadow" e cercarlo durante il rendering di oggetti dinamici. se stai facendo 2D, questo è solo un problema di base per l'ordinazione, un semplice algoritmo di pittore risolverà questo problema per te. TBH ti stai preoccupando troppo presto. lo scoprirai quando è il momento di farlo e hai solo quello in mente, in questo momento ti stai solo confondendo. non puoi sperare di fare tutto bene la prima volta, semplicemente non succederà. attraverserai quel ponte quando ci arriverai.
dreta,

1
"Entità e componenti sono semplicemente stupidi detentori di dati, mentre i sistemi forniscono tutte le funzionalità." Non necessariamente. Sono negli approcci di alcune persone. Ma non altri. Guarda il motore Unity: tutto il comportamento è nei componenti.
Kylotan,

-2

Introduzione alla gestione delle dipendenze 101.

Questo corso presuppone che tu abbia una conoscenza di base dell'iniezione delle dipendenze e della progettazione del repository.

L'iniezione di dipendenza è solo un modo elegante per gli oggetti di parlare tra loro (tramite messaggi / segnali / delegati / qualunque cosa) senza essere direttamente accoppiati.

Segue la frase: " newè colla".

Lo dimostrerò in 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; }
    }
}

Questo è un sistema di base per input, movimento e rendering. La Playerclasse è l'entità in questo caso e i componenti sono le interfacce. La Playerclasse non sa nulla riguardo la struttura interna di come una concreta IRenderSystem, IMovementSystemo IInputSystemdi lavoro. Tuttavia, le interfacce forniscono un modo per Playerinviare segnali (ad es. Invocare Disegna su IRenderSystem) senza dipendere da come si ottiene il risultato finale.

Ad esempio, prendi la mia implementazione di IMovementSystem:

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; }
    }
}

MovementSystempuò avere le sue dipendenze e Playernon gliene importerebbe. Utilizzando le interfacce, è possibile creare una macchina a stati di gioco:

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();
        }
    }
}

E quello è l'inizio di un gioco adorabile (che è anche unit testabile).

E con alcune aggiunte e alcuni polimorfismi:

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();
        }
    }
}

Ora disponiamo di un sistema di entità / componente primitivo basato su aggregazione di interfacce e ereditarietà libera.


1
Questo è contro il design dei componenti :) Cosa faresti se vuoi che un giocatore emetta suoni e altri no?
Kikaimaru,

@Kikaimaru Passa in un ISoundSystem che non riproduce l'audio. cioè non fa niente.
Dustin Kingen,

3
-1, non perché è un codice errato, ma perché non è affatto rilevante per l'architettura basata sui componenti - in effetti è la proliferazione di interfacce come questa che i componenti cercano di evitare.
Kylotan,

@Kylotan Immagino che la mia comprensione debba essere sbagliata.
Dustin Kingen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.