Organizzare un sistema di entità con gestori di componenti esterni?


13

Sto progettando un motore di gioco per un gioco sparatutto multiplayer 2D top-down, che voglio essere ragionevolmente riutilizzabile per altri giochi sparatutto top-down. Al momento sto pensando a come dovrebbe essere progettato qualcosa come un sistema di entità in esso. Per prima cosa ho pensato a questo:

Ho una classe chiamata EntityManager. Dovrebbe implementare un metodo chiamato Update e un altro chiamato Draw. Il motivo per cui ho separato Logic e Rendering è perché posso omettere il metodo Draw se eseguo un server autonomo.

EntityManager possiede un elenco di oggetti di tipo BaseEntity. Ogni entità possiede un elenco di componenti come EntityModel (la rappresentazione disegnabile di un'entità), EntityNetworkInterface ed EntityPhysicalBody.

EntityManager possiede anche un elenco di gestori componenti come EntityRenderManager, EntityNetworkManager ed EntityPhysicsManager. Ogni gestore componenti mantiene i riferimenti ai componenti entità. Esistono vari motivi per spostare questo codice fuori dalla propria classe dell'entità e farlo collettivamente. Ad esempio, sto usando una libreria di fisica esterna, Box2D, per il gioco. In Box2D, aggiungi prima i corpi e le forme a un mondo (di proprietà dell'EntityPhysicsManager in questo caso) e aggiungi i callback di collisione (che verrebbero inviati all'oggetto entità stesso nel mio sistema). Quindi esegui una funzione che simula tutto nel sistema. Trovo difficile trovare una soluzione migliore per farlo che farlo in un componente esterno come questo.

La creazione dell'entità avviene in questo modo: EntityManager implementa il metodo RegisterEntity (entityClass, factory) che registra come creare un'entità di quella classe. Implementa anche il metodo CreateEntity (entityClass) che restituirebbe un oggetto di tipo BaseEntity.

Ora arriva il mio problema: come verrebbe registrato il riferimento a un componente ai gestori componenti? Non ho idea di come farei riferimento ai gestori dei componenti da una fabbrica / chiusura.


Non so se forse questo dovrebbe essere un sistema ibrido, ma sembra che i tuoi "gestori" siano quelli che ho generalmente sentito chiamare "sistemi"; cioè l'Entità è un ID astratto; un componente è un pool di dati; e ciò che definisci "manager" è ciò che generalmente viene definito "Sistema". Sto interpretando correttamente il vocabolario?
BRPocock,


Dai un'occhiata a gamadu.com/artemis e vedi se i loro metodi rispondono alla tua domanda.
Patrick Hughes,

1
Non esiste un modo per progettare un sistema di entità in quanto c'è poco consenso sulla sua definizione. Ciò che @BRPocock descrive e anche ciò che Artemis usa è stato descritto in modo più approfondito su questo blog: t-machine.org/index.php/category/entity-systems insieme a un wiki: entity-systems.wikidot.com
user8363

Risposte:


6

I sistemi dovrebbero archiviare una coppia chiave-valore di Entity to Component in una sorta di mappa, oggetto dizionario o array associativo (a seconda della lingua utilizzata). Inoltre, quando crei il tuo Entity Object, non mi preoccuperei di memorizzarlo in un gestore a meno che non sia necessario essere in grado di annullare la registrazione da uno qualsiasi dei Sistemi. Un'entità è un composto di componenti, ma non dovrebbe gestire nessuno degli aggiornamenti dei componenti. Ciò dovrebbe essere gestito dai sistemi. Tratta invece la tua Entità come una chiave che è mappata a tutti i componenti che contiene nei sistemi, così come un hub di comunicazione per far dialogare tra loro quei componenti.

Il bello dei modelli Entity-Component-System è che puoi implementare abbastanza facilmente la capacità di passare i messaggi da un componente al resto dei componenti di un'entità. Ciò consente a un componente di parlare con un altro componente senza sapere chi sia quel componente o come gestirlo. Passa invece un messaggio e consente al componente di cambiare se stesso (se esiste)

Ad esempio, un sistema di posizione non contiene molto codice, ma tiene traccia degli oggetti Entity associati ai loro componenti di posizione. Ma quando una posizione cambia, possono inviare un messaggio all'entità interessata, che a sua volta viene trasmessa a tutti i componenti di tale entità. Una posizione cambia per qualsiasi motivo? Il sistema di posizione invia a Entity un messaggio che dice che la posizione è cambiata, e da qualche parte, il componente di rendering dell'immagine di quell'entità ottiene quel messaggio e si aggiorna dove verrà disegnato successivamente.

Al contrario, un sistema fisico deve sapere cosa stanno facendo tutti i suoi oggetti; Deve essere in grado di vedere tutti gli oggetti del mondo per testare le collisioni. Quando si verifica una collisione, aggiorna il componente di direzione dell'entità inviando una sorta di "Messaggio di cambio di direzione" all'entità invece di fare riferimento direttamente al componente dell'entità. Questo disaccoppia il manager dalla necessità di sapere come cambiare direzione usando un messaggio invece di fare affidamento su un componente specifico che si trova lì (che potrebbe non essere affatto lì, nel qual caso il messaggio cadrà solo per non udenti invece di un errore si verifica perché un oggetto previsto era assente).

Noterai un enorme vantaggio da questo poiché hai menzionato che hai un'interfaccia di rete. Un componente di rete ascolta tutti i messaggi in arrivo che tutti gli altri dovrebbero conoscere. Adora i pettegolezzi. Quindi, quando il Sistema di rete si aggiorna, i componenti di Rete inviano quei messaggi ad altri Sistemi di Rete su altri computer client, che quindi reinviano quei messaggi a tutti gli altri componenti per aggiornare le posizioni dei giocatori, ecc. Potrebbe essere necessaria una logica speciale in modo che solo determinate entità possano inviare messaggi attraverso la rete, ma questa è la bellezza del sistema, puoi semplicemente controllare la logica registrandoti le cose giuste.

In breve:

Entity è una composizione di componenti che possono ricevere messaggi. L'entità può ricevere messaggi, delegando tali messaggi a tutti i loro componenti per aggiornarli. (Posizione modificata Messaggio, Direzione cambio velocità, ecc.) È come una cassetta postale centrale che tutti i componenti possono ascoltare gli uni dagli altri invece di parlare direttamente tra loro.

Il componente è una piccola parte di un'entità che memorizza un certo stato dell'entità. Questi sono in grado di analizzare determinati messaggi e gettarne altri. Ad esempio, un "Componente di direzione" si occuperebbe solo di "Messaggi di cambio di direzione" ma non di "Messaggi di cambio di posizione". I componenti aggiornano il proprio stato in base ai messaggi, quindi aggiornano gli stati di altri componenti inviando messaggi dal proprio sistema.

Il sistema gestisce tutti i componenti di un determinato tipo ed è responsabile dell'aggiornamento di tali componenti per ciascun frame, nonché dell'invio di messaggi dai componenti che gestiscono alle entità a cui appartengono i componenti

I sistemi potrebbero essere in grado di aggiornare tutti i loro componenti in parallelo e archiviare tutti i messaggi mentre vanno. Quindi, quando l'esecuzione dei metodi di aggiornamento di tutti i sistemi è completa, si chiede a ciascun sistema di inviare i propri messaggi in un ordine specifico. I controlli prima possibile, seguiti da Fisica, seguiti da direzione, posizione, rendering, ecc. Importa in quale ordine vengono inviati poiché un Cambio di direzione fisica dovrebbe sempre pesare un cambio di direzione basato sul controllo.

Spero che sia di aiuto. È un inferno di un modello di progettazione, ma è ridicolmente potente se fatto bene.


0

Sto usando un sistema simile nel mio motore e il modo in cui l'ho fatto è che ogni Entità contiene un elenco di Componenti. Da EntityManager, posso interrogare ciascuna delle Entità e vedere quali contengono un determinato Componente. Esempio:

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

Ovviamente questo non è il codice esatto (in realtà avresti bisogno di una funzione modello per verificare la presenza di diversi tipi di componenti, piuttosto che usare typeof) ma il concetto è lì. Quindi potresti prendere quei risultati, fare riferimento al componente che stai cercando e registrarlo con la tua fabbrica. Ciò impedisce qualsiasi accoppiamento diretto tra i componenti e i loro gestori.


3
Avvertimento: nel momento in cui la tua Entità contiene dati, è un oggetto, non un'entità ... Uno perde la maggior parte dei benefici paralelizzanti (sic?) Di ECS in questa struttura. I sistemi E / C / S "puri" sono relazionali, non orientati agli oggetti ... Non che sia necessariamente "cattivo" per alcuni casi, ma certamente "rompe il modello relazionale"
BRPocock

2
Non sono sicuro di capirti. La mia comprensione (e, per favore, correggimi se sbaglio) è che l'Entity-Component-System di base ha una classe Entity che ospita i componenti e potrebbe avere un ID, un nome o un identificatore. Penso che potremmo avere un malinteso su cosa intendo per "tipo" di Entità. Quando dico Entity "type", mi riferisco ai tipi di Component. Vale a dire, un'entità è di tipo "Sprite" se contiene un componente Sprite.
Mike Cluck,

1
In un puro sistema Entità / Componente, un'entità è generalmente atomica: ad es typedef long long int Entity. un Componente è un record (potrebbe essere implementato come una classe di oggetti, o semplicemente a struct) che ha un riferimento all'entità a cui è collegato; e un sistema sarebbe un metodo o una raccolta di metodi. Il modello ECS non è molto compatibile con il modello OOP, sebbene un Componente possa essere un oggetto (principalmente) solo dati, e un Sistema un oggetto singleton solo codice il cui stato risiede nei componenti ... sebbene i sistemi "ibridi" siano più comuni di quelli "puri", perdono molti dei benefici innati.
BRPocock,

2
@BRPocock re sistemi di entità "pure". Penso che un'entità come oggetto vada perfettamente bene, non deve essere un semplice ID. Una cosa è la rappresentazione serializzata, un'altra la disposizione in memoria di un oggetto / un concetto / un'entità. Fino a quando è possibile mantenere il controllo dei dati, non si dovrebbe essere legati al codice non idiomatico solo perché è il modo "puro".
Raine,

1
@BRPocock è un avvertimento corretto, ma per sistemi di entità simili a "t-machine". Capisco perché, ma quelli non sono gli unici modi per modellare entità basate su componenti. gli attori sono un'alternativa interessante. Tendo ad apprezzarli di più, specialmente per entità puramente logiche.
Raine,

0

1) Al tuo metodo Factory dovrebbe essere passato un riferimento a EntityManager che lo ha chiamato (userò C # come esempio):

delegate BaseEntity EntityFactory(EntityManager manager);

2) Chiedi a CreateEntity di ricevere anche un ID (ad es. Una stringa, un numero intero, dipende da te) oltre alla classe / al tipo di entità e registra automaticamente l'entità creata su un dizionario usando quell'id come chiave:

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) Aggiungi un Getter a EntityManager per ottenere qualsiasi entità per ID:

class EntityManager
{
    // Rest of class omitted

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

E questo è tutto ciò che serve per fare riferimento a qualsiasi ComponentManager all'interno del metodo Factory. Per esempio:

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

Oltre a Id puoi anche usare una sorta di proprietà Type (un enum personalizzato, o semplicemente fare affidamento sul sistema dei tipi di linguaggio) e creare un getter che restituisca tutte le BaseEntities di un certo tipo.


1
Non essere pedanti, ma ancora ... in un puro sistema di Entità (relazionale), le entità non hanno alcun tipo, tranne quello impartito loro in virtù dei loro componenti ...
BRPocock

@BRPocock: potresti creare un esempio che segue la pura virtù?
Zolomon,

1
@Raine Forse non ho esperienza diretta con questo, ma è quello che ho letto. E ci sono ottimizzazioni che puoi implementare per ridurre il tempo impiegato a cercare componenti per ID. Per quanto riguarda la coerenza della cache, penso che abbia senso dal momento che stai memorizzando dati dello stesso tipo contigui in memoria, specialmente quando i tuoi componenti sono proprietà leggere o semplici. Ho letto che una singola mancanza di cache su PS3 può essere costosa come un migliaio di istruzioni della CPU, e questo approccio di archiviazione contigua di dati di tipo simile è una tecnica di ottimizzazione molto comune nello sviluppo di giochi moderni.
David Gouveia,

2
In ref: sistema di entità "puro": l'ID entità è in genere qualcosa di simile a typedef unsigned long long int EntityID;:; l'ideale è che ciascun sistema possa vivere su una CPU o un host separati e che richieda solo il recupero di componenti rilevanti per / attivi in ​​quel sistema. Con un oggetto Entity, potrebbe essere necessario creare un'istanza dello stesso oggetto Entity su ciascun host, rendendo più difficile il ridimensionamento. Un modello puro di sistema componente-entità divide l'elaborazione tra nodi (processi, CPU o host) per sistema, piuttosto che per entità, in genere.
BRPocock,

1
@DavidGouveia ha menzionato "ottimizzazioni ... ricerca di entità per ID". In effetti, i (pochi) sistemi che ho implementato in questo modo, tendono a non farlo. Più spesso, selezionare i componenti in base a un modello che indica che sono interessanti per un determinato sistema, utilizzando Entità (ID) solo per i join tra componenti.
BRPocock,
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.