Ho lavorato sullo stesso motore di coderanger. Ho un punto di vista diverso. :)
Innanzitutto, non avevamo uno stack di FSM - avevamo uno stack di stati. Una pila di stati crea un unico FSM. Non so come sarebbe uno stack di FSM. Probabilmente troppo complicato per fare qualcosa di pratico.
Il mio più grande problema con la nostra Global State Machine era che era una pila di stati e non un insieme di stati. Ciò significa, ad esempio, ... / MainMenu / Il caricamento era diverso da ... / Loading / MainMenu, a seconda che il menu principale fosse attivo prima o dopo la schermata di caricamento (il gioco è asincrono e il caricamento è principalmente guidato dal server ).
Come due esempi di cose questo ha reso brutto:
- Ha portato ad esempio allo stato LoadingGameplay, quindi hai avuto Base / Loading e Base / Gameplay / LoadingGameplay per il caricamento all'interno dello stato Gameplay, che ha dovuto ripetere gran parte del codice nello stato di caricamento normale (ma non tutti, e aggiungerne altri ).
- Avevamo diverse funzioni come "se nel creatore del personaggio vai al gameplay; se nel gameplay vai alla selezione del personaggio; se nella selezione del personaggio torna indietro al login", perché volevamo mostrare le stesse finestre dell'interfaccia in stati diversi ma fare il Indietro / Avanti i pulsanti funzionano ancora.
Nonostante il nome, non era molto "globale". La maggior parte dei sistemi di gioco interni non lo utilizzavano per tracciare i propri stati interni, perché non volevano che i loro stati si confondessero con altri sistemi. Altri, ad esempio il sistema di interfaccia utente, potrebbero usarlo ma solo per copiare lo stato nei propri sistemi di stato locale. (Vorrei in particolare mettere in guardia contro il sistema per gli stati dell'interfaccia utente. Lo stato dell'interfaccia utente non è uno stack, è in realtà un DAG e cercare di forzare qualsiasi altra struttura su di esso renderà solo le interfacce utente frustranti da usare.)
A cosa serviva era isolare le attività per l'integrazione del codice dai programmatori dell'infrastruttura che non sapevano come fosse effettivamente strutturato il flusso di gioco, quindi si poteva dire al tizio che scriveva il patcher "metti il tuo codice in Client_Patch_Update" e il tizio che scriveva la grafica caricando "inserisci il tuo codice in Client_MapTransfer_OnEnter", e potremmo scambiare determinati flussi logici senza troppi problemi.
In un progetto secondario, ho avuto più fortuna con uno stato impostato anziché uno stack , non avendo paura di creare più macchine per sistemi non correlati e rifiutando di lasciarmi cadere nella trappola di avere uno "stato globale", che è davvero solo un modo complicato per sincronizzare le cose attraverso variabili globali - Certo, finirai per farlo vicino a una scadenza, ma non progettare con quello come obiettivo . Fondamentalmente, lo stato in un gioco non è uno stack e gli stati in un gioco non sono tutti correlati.
Inoltre, come indicano i puntatori a funzione e il comportamento non locale, il GSM ha reso più difficile il debug delle cose, anche se il debug di quel tipo di grandi transizioni di stato non era molto divertente prima di noi. I set di stati invece di stack di stato non aiutano davvero questo, ma dovresti esserne consapevole. Le funzioni virtuali anziché i puntatori di funzione possono alleviarlo in qualche modo.