Come aggiornare gli stati e le animazioni delle entità in un gioco basato su componenti?


10

Sto cercando di progettare un sistema di entità basato su componenti per scopi di apprendimento (e in seguito utilizzarlo su alcuni giochi) e sto avendo dei problemi quando si tratta di aggiornare gli stati delle entità.

Non voglio avere un metodo update () all'interno del componente per prevenire dipendenze tra i componenti.

Quello che attualmente ho in mente è che i componenti contengono dati e componenti di aggiornamento dei sistemi.

Quindi, se ho un semplice gioco 2D con alcune entità (ad esempio giocatore, nemico1, nemico2) che hanno componenti Trasforma, Movimento, Stato, Animazione e Rendering, penso che dovrei avere:

  • Un sistema di movimento che sposta tutti i componenti del movimento e aggiorna i componenti dello stato
  • E un RenderSystem che aggiorna i componenti di animazione (il componente di animazione dovrebbe avere un'animazione (ovvero un set di frame / trame) per ogni stato e aggiornarlo significa selezionare l'animazione corrispondente allo stato corrente (es. Jumping, moving_left, ecc.), E aggiornamento dell'indice dei frame). Quindi, RenderSystem aggiorna i componenti di rendering con la trama corrispondente al fotogramma corrente dell'Animazione di ciascuna entità e rende tutto sullo schermo.

Ho visto alcune implementazioni come il framework Artemis, ma non so come risolvere questa situazione:

Diciamo che il mio gioco ha le seguenti entità. Ogni entità ha un insieme di stati e un'animazione per ogni stato:

  • giocatore: "inattivo", "moving_right", "jumping"
  • nemico1: "moving_up", "moving_down"
  • nemico2: "moving_left", "moving_right"

Quali sono gli approcci più accettati al fine di aggiornare lo stato attuale di ciascuna entità? L'unica cosa a cui riesco a pensare è avere sistemi separati per ogni gruppo di entità e componenti separati di Stato e Animazione, quindi avrei PlayerState, PlayerAnimation, Enemy1State, Enemy1Animation ... PlayerMovementSystem, PlayerRenderingSystem ... ma penso che sia un male soluzione e rompe lo scopo di avere un sistema basato su componenti.

Come puoi vedere, sono abbastanza perso qui, quindi apprezzerei molto qualsiasi aiuto.

EDIT: Penso che la soluzione per far funzionare questo come intendo sia questa:

Il componente statistico e il componente animazione sono abbastanza generici da poter essere utilizzati per tutte le entità. I dati che contengono saranno il modificatore per cambiare cose come quali animazioni vengono riprodotte o quali stati sono disponibili. - Byte56

Ora sto cercando di capire come progettare questi 2 componenti in modo abbastanza generico da poterli riutilizzare. Avere un UID per ogni stato (ad es. Camminare, correre ...) e archiviare animazioni in una mappa nell'AnimationComponent codificato da questo identificatore può essere una buona soluzione?


Suppongo che tu abbia visto questo: cambiamenti di stato in entità o componenti ? La tua domanda è fondamentalmente diversa da quella?
MichaelHouse

@ Byte56 Sì, l'ho letto qualche ora fa. La soluzione che hai suggerito è simile all'idea che ho esposto qui. Ma il mio problema si presenta quando StateComponent e AnimationComponent non sono gli stessi per tutte le entità all'interno del sistema. Dovrei suddividere quel sistema in sistemi più piccoli che elaborano gruppi di entità con gli stessi stati e animazioni possibili? (vedi l'ultima parte del mio post originale per maggiori chiarimenti)
miviclin,

1
Fai statecomponente animationcomponentabbastanza generico per essere utilizzato per tutte le entità. I dati che contengono saranno il modificatore per cambiare cose come quali animazioni vengono riprodotte o quali stati sono disponibili.
MichaelHouse

Quando parli di dipendenza, vuoi dire dipendenza dei dati o dipendenza dell'ordine di esecuzione? Inoltre, nella tua soluzione proposta, il MovementSystem deve ora implementare tutti i diversi modi in cui qualcosa può muoversi? Sembra che stia rompendo l'idea del sistema basato su componenti ...
ADB,

@ADB Sto parlando della dipendenza dei dati. Per aggiornare l'animazione (ad es. Passare dall'animazione move_right all'animazione move_left) ho bisogno di conoscere lo stato attuale dell'entità e non vedo come rendere più generici questi 2 componenti.
miviclin,

Risposte:


5

IMHO il Movementcomponente dovrebbe contenere lo stato corrente ( Movement.state) e il Animationcomponente dovrebbe osservare le modifiche Movement.statee aggiornare di conseguenza la sua animazione corrente ( Animation.animation), usando una semplice ricerca di id stato per l'animazione (come suggerito alla fine dell'OP). Ovviamente questo significa Animationche dipenderà Movement.

Una struttura alternativa sarebbe quella di avere un Statecomponente generico , che Animationosserva e Movementmodifica, che è fondamentalmente controller di vista modello (movimento di animazione di stato in questo caso).

Un'altra alternativa sarebbe quella di fare in modo che l'entità invii un evento ai suoi componenti quando cambia il suo stato. Animationascolterebbe questo evento e aggiornerebbe la sua animazione di conseguenza. Ciò elimina la dipendenza, sebbene si possa sostenere che la versione dipendente abbia un design più trasparente.

In bocca al lupo.


L'animazione osserva lo stato e lo stato osserva il movimento ... Le dipendenze sono ancora lì, ma potrei provarlo. L'ultima alternativa sarebbe qualcosa del genere: Il movimento notifica cambiamenti all'entità e l'entità invia un evento allo Stato, e quindi lo stesso processo sarebbe ripetuto per Stato e Animazione? In che modo questo approccio può influire sulle prestazioni?
miviclin,

Primo caso: Movementsarebbe controllare State (non osservare). Ultimo caso: sì Movement, entity.dispatchEvent(...);o quasi, e tutti gli altri componenti che ascoltano quel tipo di evento lo riceveranno. Le prestazioni sono ovviamente peggiori delle pure chiamate di metodo, ma non molto. Ad esempio, è possibile raggruppare oggetti evento. A proposito, non è necessario utilizzare l'entità come "nodo eventi", è possibile utilizzare anche un "bus eventi" dedicato, lasciando completamente fuori la propria classe di entità.
Torious

2

A proposito del tuo problema, se lo STATO viene utilizzato solo nelle animazioni, non è nemmeno necessario esporlo ad altri componenti. Se ha più di un uso, è necessario esporlo.

Il sistema di Componenti / Sottosistemi che descrivi sembra più basato sulla gerarchia che sui componenti. Dopotutto, ciò che descrivi come componenti sono in realtà strutture di dati. Non significa che sia un cattivo sistema, solo che non penso che si adatti troppo bene all'approccio basato sui componenti.

Come hai notato, le dipendenze sono un grosso problema nei sistemi basati su componenti. Esistono diversi modi per gestirlo. Alcuni richiedono che ciascun componente dichiari le proprie dipendenze e controlli rigorosi. Altri richiedono componenti che implementano un'interfaccia specifica. Altri ancora passano il riferimento ai componenti dipendenti quando istanziano ciascuno di essi.

Indipendentemente dal metodo che usi, avrai bisogno di un GameObject di qualche tipo per fungere da raccolta di componenti. Ciò che offre GameObject può variare molto e puoi semplificare le tue dipendenze tra i componenti spingendo alcuni dati di uso frequente a livello di GameObject. Unity lo fa con la trasformazione, ad esempio, impone a tutti gli oggetti di gioco di averne uno.

Per quanto riguarda il problema si chiede di diversi stati / animazione per i diversi oggetti del gioco, ecco cosa mi farei. Innanzitutto, non sarei troppo fantasioso in questa fase dell'implementazione: implementa solo ciò di cui hai bisogno ora per farlo funzionare, quindi aggiungi campane e fischietti quando ne hai bisogno.

Quindi, inizierei con un componente "Stato": PlayerStateComponent, Enemy1State, Enemy2State. Il componente di stato si occuperebbe di cambiare lo stato al momento opportuno. Lo stato è qualcosa che tutti i tuoi oggetti avranno, quindi può risiedere nel GameObject.

Quindi, ci sarebbe un Componente Animazione. Questo avrebbe un dizionario di animazioni codificato per lo stato. In update (), cambia l'animazione se lo stato cambia.

C'è un ottimo articolo sulla costruzione di un framework che non riesco a trovare. Diceva che quando non si ha esperienza nel dominio, si dovrebbe scegliere un problema e fare l'implementazione più semplice che risolva il problema attuale . Quindi aggiungi un altro problema / caso d'uso ed espandi il framework man mano che procedi, quindi cresce organicamente. Mi piace molto questo approccio, in particolare quando lavori con un nuovo concetto mentre lo stai facendo.

L'implementazione che ho proposto è abbastanza ingenua, ma qui ci sono alcuni possibili miglioramenti quando aggiungi più casi d'uso:

  • sostituisci la variabile GameObject con un dizionario. Ogni componente utilizza il dizionario per memorizzare i valori. (assicurarsi di gestire la collisione correttamente ...)
  • sostituisci invece il dizionario dei valori semplici con riferimenti: class FloatVariable () {public value [...]}
  • Invece di più componenti di stato, creare un componente di stato generico in cui è possibile creare macchine a stato variabile. È necessario disporre di una serie generica di condizioni in base alle quali uno stato può cambiare: pressione dei tasti, input del mouse, modifiche delle variabili (è possibile collegarlo alla variabile Float sopra).

Questo approccio funziona, ho implementato qualcosa di simile un anno fa, ma il problema è che quasi ogni componente dipende da altri componenti, quindi mi sembra meno flessibile. Ho anche pensato di spingere i componenti più comuni (ad es. Trasformazione, rendering, stato ...) nell'entità, ma penso che ciò rompa lo scopo dei componenti perché alcuni di essi sono legati all'entità e alcune entità potrebbero non averne bisogno. Ecco perché sto cercando di ridisegnarlo con i sistemi responsabili dell'aggiornamento della logica in modo che i componenti non si sappiano nulla l'uno dell'altro poiché non si aggiornano da soli.
miviclin,

0

Oltre alla risposta di ADB è possibile utilizzare http://en.wikipedia.org/wiki/Dependency_injection , che aiuta quando è necessario costruire molti componenti passandoli come riferimenti ai loro costruttori. Ovviamente dipenderanno comunque l'uno dall'altro (se ciò è richiesto nella tua base di codice), ma puoi mettere tutta quella dipendenza in un posto in cui sono impostate le dipendenze e il resto del tuo codice non ha bisogno di sapere della dipendenza.

Questo approccio funziona bene anche se usi le interfacce perché ogni classe di componenti richiede solo ciò di cui ha bisogno o dove deve essere registrata e solo il framework di iniezione delle dipendenze (o il luogo in cui hai impostato tutto, di solito l'app) sa chi ha bisogno di cosa .

Per i sistemi semplici che potresti scappare senza usare DI o un codice pulito, le tue classi RenderingSystem sembrano come se dovessi chiamarle staticamente o almeno averle disponibili in ciascun componente, il che le rende praticamente dipendenti l'una dall'altra e difficili da cambiare. Se sei interessato ad un approccio più pulito, controlla i collegamenti del link DI wiki sopra e leggi il codice pulito: http://clean-code-developer.com/


Ho già un sistema in cui i componenti sono abbastanza dipendenti l'uno dall'altro. Ho fatto un uso pesante dell'iniezione di dipendenza lì, e sebbene la preferisca alle gerarchie profonde, sto cercando di crearne una nuova per evitare l'accoppiamento dei componenti, se possibile. Non chiamerei nulla staticamente. Avrei un ComponentManager a cui ogni sistema avrebbe accesso (ogni sistema dovrebbe avere un riferimento ad esso) e il RendererSystem avrebbe ottenuto tutti i componenti di animazione dal gestore dei componenti e avrebbe reso lo stato corrente di ogni animazione.
miviclin,
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.