"The Game Object" - e design basato su componenti


25

Ho lavorato su alcuni progetti di hobby negli ultimi 3-4 anni. Solo semplici giochi 2D e 3D. Ma ultimamente ho iniziato un progetto più grande. Negli ultimi due mesi ho cercato di progettare una classe di oggetti di gioco che può essere la base di tutti i miei oggetti di gioco. Quindi, dopo molti tentativi, sono passato a Google che mi ha rapidamente indirizzato ad alcuni PDF e PowerPoint di GDC. E ora sto cercando di afferrare gli oggetti di gioco basati su componenti.

Capisco che il motore crea un oggetto di gioco e quindi attacca diversi componenti che gestiscono cose come la salute, la fisica, la rete e qualsiasi cosa tu li faccia fare. Ma quello che non capisco è come il componente X sappia se Y ha cambiato lo stato dell'oggetto. Come fa il PhysicsComponent a sapere se il giocatore è vivo, perché la salute è controllata dal HealthComponent ..? E in che modo HealthComponent interpreta "l'animazione del giocatore morto"?

Avevo l'impressione che fosse qualcosa del genere (in HealthComponent):

if(Health < 0) {
   AnimationComponent.PlayAnimation("played-died-animation")
}

Ma poi, come fa HealthComponent a sapere che l'oggetto di gioco a cui è collegato ha un componente AnimationComponent collegato? L'unica soluzione che vedo qui è

  1. Verifica se un componente Animation è collegato o meno (all'interno del codice componente o sul lato motore)

  2. I componenti richiedono altri componenti, ma ciò sembra contrastare l'intero progetto dei componenti.

  3. Scrivi come, HealthWithAnimationComponent, HealthNoAnimationComponent e così via, che sembra combattere ancora l'intera idea di progettazione dei componenti.


1
Adoro la domanda. Avrei dovuto chiedere lo stesso mesi fa, ma non ci sono mai riuscito. Un ulteriore problema che ho riscontrato è quando un oggetto di gioco ha più istanze dello stesso componente (ad esempio più animazioni). Sarebbe bello se le risposte potessero toccarlo. Ho finito per usare i messaggi per le notifiche, con le variabili condivise tra tutti i componenti di un oggetto Game (quindi non è necessario inviare un messaggio per ottenere un valore per una variabile).
ADB,

1
A seconda del tipo di gioco, probabilmente non avrai oggetti di gioco con componente di salute e nessun componente di animazione. E tutti questi oggetti di gioco sono probabilmente una rappresentazione di qualcosa come Unit. Quindi puoi buttare via il componente di salute e creare UnitComponent che avrebbe la salute del campo e conoscere tutti i componenti che l'unità deve essere. Questa granularità dei componenti non aiuta davvero nulla: è più realistico avere un componente per dominio (rendering, audio, fisica, logica di gioco).
Kikaimaru,

Risposte:


11

In tutti i tuoi esempi, c'è un problema terribile. Il componente di integrità deve conoscere ogni tipo di componente che potrebbe dover rispondere alla morte dell'entità. Pertanto, nessuno dei tuoi scenari è appropriato. La tua entità ha un componente di integrità. Ha un componente di animazione. Né dipendono né sanno dell'altro. Comunicano attraverso un sistema di messaggistica.

Quando il componente di salute rileva che l'entità è "morta", invia un messaggio "Sono morto". È responsabilità del componente di animazione rispondere a questo messaggio riproducendo l'animazione appropriata.

Il componente di integrità non invia il messaggio direttamente al componente di animazione. Forse lo trasmette a tutti i componenti di quell'entità, forse all'intero sistema; forse il componente di animazione deve far sapere al sistema di messaggistica che è interessato ai messaggi "Sono morto". Esistono molti modi per implementare il sistema di messaggistica. Indipendentemente dall'implementazione, il punto è che il componente di integrità e il componente di animazione non devono mai sapere o preoccuparsi se l'altro è presente e l'aggiunta di nuovi componenti non richiederà mai la modifica di quelli esistenti per inviare loro messaggi appropriati.


Okey, questo ha senso. Ma chi dichiara gli "stati" come "morti", o "portal-is-broken", ecc. Il componente o il motore? Perché l'aggiunta di uno stato "morto" a una cosa che non avrà mai il componente sanitario collegato mi sembra un po 'uno spreco. Immagino che mi tufferò e inizierò a testare un po 'di codice e vedere cosa funziona.
Hayer,

Michael e Patrick Hughes hanno la risposta giusta sopra. I componenti sono solo dati; quindi non è proprio il componente di salute che rileva quando l'entità è morta e invia il messaggio, è un pezzo di logica di livello superiore del gioco specifico. Come astrarre dipende da te. Lo stato di morte attuale non deve mai essere archiviato da nessuna parte. L'oggetto è morto se la sua salute è <0 e il componente di salute può incapsulare quel bit di logica di controllo dei dati senza interrompere un "nessun comportamento!" restrizione se si considerano solo comportamenti che modificano lo stato del componente.
Blecki,

Solo curioso, come gestiresti un Componente Movement? Quando rileva input, deve aumentare la velocità in PositionComponent. Come sarebbe il messaggio?
Tips48

8

Il modo in cui Artemis risolve il problema è di non eseguire l'elaborazione all'interno di Components. I componenti contengono solo i dati di cui hanno bisogno. I sistemi leggono più tipi di componenti e fanno qualunque elaborazione sia necessaria.

Quindi, nel tuo caso, potresti avere un RenderSystem che legge in HealthComponent (e altri) e riproduce le code con le animazioni appropriate. Separare i dati dalle funzioni in questo modo semplifica la gestione corretta delle dipendenze.


Questo finisce per essere un buon modo per gestire il problema: i componenti rappresentano proprietà mentre i sistemi collegano proprietà disparate e usano quelle per fare il lavoro. È un enorme cambiamento rispetto al tradizionale pensiero OOP e fa male alla testa di alcune persone =)
Patrick Hughes,

Okey, ora mi sono davvero perso. "Al contrario, in un ES, se hai 100 unità su un campo di battaglia, ognuna rappresentata da un'entità, allora hai zero copie di ogni metodo che può essere invocato su un'unità - perché Le entità non contengono metodi, né i componenti contengono metodi, ma hai un sistema esterno per ogni aspetto e quel sistema esterno contiene tutti i metodi che possono essere invocati su qualsiasi entità che possiede il componente che lo contrassegna come compatibile con questo sistema." Bene, dove sono archiviati i dati in un GunComponent? Come round, ecc. Se tutte le entità condividono lo stesso componente.
Hayer,

1
Per quanto ho capito, tutte le entità non condividono lo stesso componente, a ciascuna entità possono essere associate N istanze di componenti. Un sistema quindi interroga il gioco per un elenco di tutte le entità che hanno istanze componenti a cui tengono a loro associate e quindi esegue qualsiasi elaborazione su di esse
Jake Woods,

Questo sposta semplicemente il problema. Come fa un sistema a sapere quali componenti utilizzare? Un sistema potrebbe avere bisogno anche di altri sistemi (ad esempio il sistema StateMachine potrebbe voler richiedere un'animazione). Tuttavia, risolve il problema dell'OMS che possiede i dati. In effetti, un'implementazione più semplice sarebbe quella di avere un dizionario nell'oggetto di gioco e ogni sistema crea le sue variabili al suo interno.
ADB,

Sposta il problema ma in un luogo più sostenibile. I sistemi hanno i relativi componenti cablati. I sistemi possono comunicare tra loro tramite i componenti (StateMachine può impostare un valore del componente in cui l'Animazione legge per sapere cosa fare (o potrebbe generare un evento). L'approccio del dizionario suona come il modello delle proprietà che può anche funzionare. I componenti sono che le proprietà correlate sono raggruppate insieme e possono essere controllate staticamente. Nessun bizzarro errore perché hai aggiunto "Dammage" in un posto ma hai provato a recuperarlo usando "Damage" in un altro.
Michael

6

Nel tuo codice puoi trovare dei modi (li ho usati, forse esistono altri modi) per sapere se l'oggetto ha cambiato stato:

  1. Invia messaggio.
  2. Leggi direttamente i dati dal componente.

1) Verifica se un componente Animation è collegato o meno (all'interno del codice componente o sul lato motore)

Per questo ho usato, 1. Funzione HasComponent di GameObject, o 2. Quando si collega un componente è possibile verificare le dipendenze in alcune funzioni di costruzione, oppure 3. Se so per certo che l'oggetto ha questo componente, lo uso e basta.

2) I componenti richiedono altri componenti, ma ciò sembra contrastare l'intero progetto dei componenti.

In alcuni articoli ho letto che i componenti del sistema Ideal non dipendono l'uno dall'altro, ma nella vita reale non è così.

3) Scrivi come, HealthWithAnimationComponent, HealthNoAnimationComponent e così via, che sembra combattere ancora l'intera idea di progettazione dei componenti.

È una cattiva idea scrivere tali componenti. Nella mia app ho creato il componente Health più indipendente. Ora sto pensando a un modello Observer che notifica agli abbonati un evento specifico (ad es. "Hit", "heal" ecc.). Quindi AnimationComponent deve decidere da solo quando riprodurre l'animazione.

Ma quando ho letto l'articolo su CBES mi ha colpito, quindi sono molto felice ora quando uso CBES e ne scopro nuove possibilità.


1
Bene, google.no/… @ slide 16
hayer

@bobenko, si prega di fornire un collegamento all'articolo su CBES. Anche io ne sono molto interessante;)
Edward83,

1
E lambdor.net/?p=171 @ bottom, questo tipo di riassunto della mia domanda Come si possono definire diverse funzionalità in termini di componenti complessi, non elementari relativi? Quali sono i componenti più elementari? In che modo le componenti elementari sono diverse dalle funzioni pure? In che modo i componenti esistenti possono comunicare automaticamente con i nuovi messaggi dei nuovi componenti? Qual è il punto di ignorare un messaggio di cui un componente non è a conoscenza? Cosa è successo al modello input-process-output dopo tutto?
Hayer,

1
qui è buono risposta su CBE stackoverflow.com/a/3495647/903195~~V~~aux~~singular~~3rd la maggior parte di articoli che ho ricercati sono da questa risposta. Ho iniziato e ispirato con cowboyprogramming.com/2007/01/05/evolve-your-heirachy poi In Gems 5 (come ricordo) c'era un buon articolo con esempi.
Yevhen,

Ma per quanto riguarda un altro concetto di programmazione funzionale-reattiva, per me questa domanda è ancora aperta, ma potrebbe essere per te è una buona direzione per le ricerche.
Yevhen,

3

È come dicono Michael, Patrick Hughes e Blecki. La soluzione per evitare semplicemente di spostare il problema è abbandonare l'ideologia che causa il problema in primo luogo.

È meno OOD e più simile alla programmazione funzionale. Quando ho iniziato a sperimentare la progettazione basata su componenti, ho riscontrato questo problema lungo la strada. Ho cercato su Google ancora un po 'e ho trovato "Programmazione reattiva reattiva" come la soluzione.

Ora i miei componenti non sono altro che una raccolta di variabili e campi che descrivono il suo stato attuale. Poi ho un sacco di classi "Sistema" che aggiornano tutti i componenti che sono rilevanti per loro. La parte reattiva si ottiene eseguendo i sistemi in un ordine ben definito. Ciò garantisce che qualunque sistema sia il prossimo in linea per eseguire l'elaborazione e l'aggiornamento e qualsiasi componente ed entità che intenda leggere e aggiornare, sia sempre al lavoro su dati aggiornati.

Tuttavia, in un certo senso, potresti comunque affermare che il problema si è spostato ancora una volta. Perché cosa succede se non è semplice in quale ordine devono essere eseguiti i sistemi? E se ci fossero relazioni cicliche ed è solo una questione di tempo prima che tu stia fissando un casino di if-else e cambi istruzioni? È una forma implicita di messaggistica, no? A prima vista, penso che sia un piccolo rischio. Di solito, le cose vengono elaborate in ordine. Qualcosa come: Input giocatore -> Posizioni entità -> Rilevazione collisione -> Logica di gioco -> Rendering -> Ricomincia. In tal caso, avresti un sistema per ciascuno, fornisci a ciascun sistema un metodo update () e quindi eseguili in sequenza nel tuo gameloop.

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.