Parlerò da un po 'di esperienza, passando da una progettazione OO rigida a una progettazione Entity-Component-System (ECS).
Qualche tempo fa ero proprio come te , avevo un sacco di diversi tipi di cose che avevano proprietà simili e ho costruito vari oggetti e ho provato a usare l'eredità per risolverlo. Una persona molto intelligente mi ha detto di non farlo, e invece usa Entity-Component-System.
Ora, ECS è un grande concetto ed è difficile da ottenere. C'è molto lavoro da fare per costruire correttamente entità, componenti e sistemi. Prima di poterlo fare, però, dobbiamo definire i termini.
- Entità : questa è la cosa , il giocatore, l'animale, l'NPC, qualunque cosa . È una cosa che necessita di componenti collegati ad esso.
- Componente : questo è l' attributo o la proprietà , come un "Nome" o "Età", o "Genitori", nel tuo caso.
- Sistema : questa è la logica dietro un componente o un comportamento . In genere, si crea un sistema per componente, ma ciò non è sempre possibile. Inoltre, a volte i sistemi devono influenzare altri sistemi.
Quindi, ecco dove andrei con questo:
Innanzitutto, creane uno ID
per i tuoi personaggi. An int
, Guid
qualunque cosa tu voglia. Questa è "Entità".
In secondo luogo, inizia a pensare ai diversi comportamenti che stai vivendo. Cose come "Family Tree" - questo è un comportamento. Invece di modellarlo come attributi sull'entità, costruisci un sistema che contiene tutte quelle informazioni . Il sistema può quindi decidere cosa farne.
Allo stesso modo, vogliamo costruire un sistema per "Il personaggio è vivo o morto?" Questo è uno dei sistemi più importanti nel tuo progetto, perché influenza tutti gli altri. Alcuni sistemi possono eliminare i caratteri "morti" (come il sistema "sprite"), altri sistemi possono riorganizzare internamente le cose per supportare meglio il nuovo stato.
Costruirai un sistema "Sprite" o "Disegno" o "Rendering", per esempio. Questo sistema avrà la responsabilità di determinare con quale personaggio deve essere visualizzato lo sprite e come visualizzarlo. Quindi, quando un personaggio muore, rimuovili.
Inoltre, un sistema "AI" in grado di dire a un personaggio cosa fare, dove andare, ecc. Questo dovrebbe interagire con molti altri sistemi e prendere decisioni basate su di essi. Ancora una volta, i personaggi morti possono probabilmente essere rimossi da questo sistema, dal momento che non stanno più facendo nulla.
Il tuo sistema "Nome" e il sistema "Albero familiare" dovrebbero probabilmente mantenere il personaggio (vivo o morto) in memoria. Questo sistema deve richiamare quelle informazioni, indipendentemente dallo stato del personaggio. (Jim è ancora Jim, anche dopo che lo seppelliamo.)
Questo ti dà anche il vantaggio di cambiare quando un sistema reagisce in modo più efficiente: il sistema ha il proprio timer. Alcuni sistemi devono sparare rapidamente, altri no. È qui che iniziamo a capire cosa fa funzionare un gioco in modo efficiente. Non abbiamo bisogno di ricalcolare il tempo ogni millisecondo, probabilmente possiamo farlo ogni 5 o giù di lì.
Ti offre anche una leva più creativa: puoi costruire un sistema "Pathfinder" in grado di gestire il calcolo di un percorso da A a B e aggiornarlo quando necessario, consentendo al sistema di movimento di dire "dove devo vai dopo? " Ora possiamo separare completamente queste preoccupazioni e ragionare su di esse in modo più efficace. Il movimento non ha bisogno di trovare la strada, deve solo arrivarci.
Ti consigliamo di esporre alcune parti di un sistema verso l'esterno. Nel tuo Pathfinder
sistema probabilmente vorrai un Vector2 NextPosition(int entity)
. In questo modo, è possibile mantenere tali elementi in matrici o elenchi strettamente controllati. Puoi utilizzare struct
tipi più piccoli, che possono aiutarti a mantenere i componenti in blocchi di memoria più piccoli e contigui, che possono rendere gli aggiornamenti di sistema molto più veloci. (Soprattutto se le influenze esterne a un sistema sono minime, ora deve solo preoccuparsi del suo stato interno, come Name
.)
Ma, e non posso sottolinearlo abbastanza, ora Entity
è solo un ID
, compresi riquadri, oggetti, ecc. Se un'entità non appartiene a un sistema, il sistema non lo rintraccerà. Ciò significa che possiamo creare i nostri oggetti "Albero", archiviarli nei sistemi Sprite
e Movement
(gli alberi non si muoveranno, ma hanno un componente "Posizione") e tenerli fuori dagli altri sistemi. Non abbiamo più bisogno di un elenco speciale per gli alberi, poiché il rendering di un albero non è diverso da un personaggio, a parte il paperdolling. (Che il Sprite
sistema può controllare o che il Paperdoll
sistema può controllare.) Ora il nostro NextPosition
può essere leggermente riscritto: Vector2? NextPosition(int entity)
e può restituire una null
posizione per le entità a cui non importa. Lo applichiamo anche al nostro NameSystem.GetName(int entity)
, ritorna null
per alberi e rocce.
Lo porterò alla fine, ma l'idea qui è di darti un po 'di background su ECS e come puoi davvero sfruttarlo per darti un design migliore sul tuo gioco. È possibile aumentare le prestazioni, disaccoppiare elementi non correlati e mantenere le cose in modo più organizzato. (Questo si abbina bene anche con linguaggi / configurazioni funzionali, come F # e LINQ, che consiglio vivamente di controllare F # se non l'hai già fatto, si abbina molto bene con C # quando li usi insieme.)