Ho lavorato su un gioco di ruolo 2d per un po 'di tempo, e ho capito che ho preso alcune cattive decisioni di progettazione. Ci sono alcune cose in particolare che mi stanno causando problemi, quindi mi chiedevo che tipo di progetti altri usavano per superarli o che avrebbero usato.
Per un po 'di storia, ho iniziato a lavorarci nel mio tempo libero l'estate scorsa. Inizialmente stavo realizzando il gioco in C #, ma circa 3 mesi fa ho deciso di passare al C ++. Volevo avere una buona padronanza del C ++ poiché è passato un po 'di tempo da quando l'ho usato pesantemente e ho pensato che un progetto interessante come questo sarebbe stato un buon motivatore. Ho usato ampiamente la libreria boost e ho usato SFML per la grafica e FMOD per l'audio.
Ho scritto un bel po 'di codice, ma sto pensando di scartarlo e ricominciare da capo.
Ecco le principali aree di interesse che ho e ho voluto ottenere alcune opinioni sul modo corretto in cui gli altri le hanno risolte o le avrebbero risolte.
1. Dipendenze cicliche Quando stavo facendo il gioco in C #, non dovevo preoccuparmene poiché non è un problema. Passando al C ++, questo è diventato un problema piuttosto grave e mi ha fatto pensare che avrei potuto progettare le cose in modo errato. Non riesco davvero a immaginare come separare le mie lezioni e farle comunque fare quello che voglio. Ecco alcuni esempi di una catena di dipendenze:
Ho una classe di effetti di stato. La classe ha una serie di metodi (Applica / Non applica, Spunta, ecc.) Per applicare i suoi effetti a un personaggio. Per esempio,
virtual void TickCharacter(Character::BaseCharacter* character, Battles::BattleField *field, int ticks = 1);
Questa funzione viene chiamata ogni volta che il personaggio inflitto dall'effetto status fa un turno. Sarebbe utile per implementare effetti come Regen, Poison, ecc. Tuttavia, introduce anche dipendenze dalla classe BaseCharacter e dalla classe BattleField. Naturalmente, la classe BaseCharacter deve tenere traccia di quali effetti di stato sono attualmente attivi su di essi, quindi questa è una dipendenza ciclica. Il campo di battaglia deve tenere traccia delle parti in lotta e la classe del partito ha un elenco di Personaggi Base che introduce un'altra dipendenza ciclica.
2 - Eventi
In C # ho fatto ampio uso di delegati per agganciare eventi su personaggi, campi di battaglia ecc. (Ad esempio, c'era un delegato per quando cambiava la salute del personaggio, quando cambiava una statistica, quando veniva aggiunto / rimosso un effetto di stato, ecc. .) e il campo di battaglia / componenti grafici si aggancerebbero a quei delegati per far valere i loro effetti. In C ++, ho fatto qualcosa di simile. Ovviamente non esiste un equivalente diretto per i delegati C #, quindi invece ho creato qualcosa del genere:
typedef boost::function<void(BaseCharacter*, int oldvalue, int newvalue)> StatChangeFunction;
e nella mia classe di personaggi
std::map<std::string, StatChangeFunction> StatChangeEventHandlers;
ogni volta che cambiava la statistica del personaggio, ripetevo e chiamavo ogni StatChangeFunction sulla mappa. Mentre funziona, sono preoccupato che questo sia un cattivo approccio nel fare le cose.
3 - Grafica
Questa è la cosa più grande. Non è correlato alla libreria grafica che sto usando, ma è più una cosa concettuale. In C #, ho unito la grafica a molte delle mie lezioni che so essere un'idea terribile. Volendo farlo disaccoppiato questa volta ho provato un approccio diverso.
Al fine di implementare la mia grafica, immaginavo tutto ciò che riguardava la grafica nel gioco come una serie di schermi. Cioè c'è una schermata del titolo, una schermata di stato del personaggio, una schermata della mappa, una schermata di inventario, una schermata di battaglia, una schermata della GUI di battaglia, e fondamentalmente potrei impilare queste schermate l'una sopra l'altra, se necessario, per creare la grafica del gioco. Qualunque sia la schermata attiva, possiede l'input di gioco.
Ho progettato un gestore dello schermo che avrebbe spinto e pop gli schermi in base all'input dell'utente.
Ad esempio, se ti trovassi su una schermata della mappa (un gestore di input / visualizzatore per una mappa a tessere) e premi il pulsante di avvio, invierebbe una chiamata al gestore dello schermo per spingere una schermata del menu principale sulla schermata della mappa e contrassegnare la mappa schermo da non disegnare / aggiornare. Il giocatore dovrebbe spostarsi nel menu, che invierà più comandi al gestore dello schermo, a seconda dei casi, per inserire nuovi schermi nella pila di schermate, quindi farli apparire quando l'utente cambia schermate / annulla. Alla fine, quando il giocatore esce dal menu principale, lo apro e torno alla schermata della mappa, osservo che deve essere disegnato / aggiornato e andare da lì.
Gli schermi di battaglia sarebbero più complessi. Avrei uno schermo che fungerà da sfondo, uno schermo per visualizzare ogni parte in battaglia e uno schermo per visualizzare l'interfaccia utente per la battaglia. L'interfaccia utente si aggancerebbe agli eventi dei personaggi e li userebbe per determinare quando aggiornare / ridisegnare i componenti dell'interfaccia utente. Infine, ogni attacco che ha uno script di animazione disponibile chiamerebbe un livello aggiuntivo per animarsi prima di saltar fuori dallo stack dello schermo. In questo caso, ogni livello è costantemente contrassegnato come disegnabile e aggiornabile e ottengo una pila di schermate che gestiscono la mia grafica di battaglia.
Anche se non sono ancora riuscito a far funzionare perfettamente lo screen manager, penso che potrò farlo con un po 'di tempo. La mia domanda al riguardo è: è un approccio assolutamente utile? Se è un cattivo design che voglio sapere ora prima di investire troppo tempo a realizzare tutti gli schermi di cui avrò bisogno. Come costruisci la grafica per il tuo gioco?