Il motore utilizza già molti singleton. Se qualcuno l'ha usato, allora dovrebbe avere familiarità con alcuni di loro:
La non familiarità non è il motivo per cui i singoli devono essere evitati.
Ci sono molte buone ragioni per cui Singleton è necessario o inevitabile. I framework di gioco usano spesso i singleton perché è inevitabile conseguenza dell'avere un solo hardware con stato. Non ha senso voler mai controllare questi hardware con più istanze dei rispettivi gestori. La superficie grafica è un hardware con stato esterno e l'inizializzazione accidentale di una seconda copia del sottosistema grafico è a dir poco disastrosa, poiché ora i due sottosistemi grafici si combatteranno su chi può disegnare e quando, sovrascrivendosi in modo incontrollabile. Allo stesso modo con il sistema di code degli eventi, si batteranno su chi ottiene gli eventi del mouse in modo non deterministico. Quando si ha a che fare con un hardware esterno con stato in cui ce n'è solo uno, è inevitabile singleton per prevenire conflitti.
Un altro posto in cui Singleton è ragionevole è con i gestori della cache. Le cache sono un caso speciale. Non dovrebbero davvero essere considerati un Singleton, anche quando usano tutte le stesse tecniche dei Singleton per rimanere in vita e vivere per sempre. I servizi di cache sono servizi trasparenti, non dovrebbero alterare il comportamento del programma, quindi se si sostituisce il servizio cache con una cache nulla, il programma dovrebbe comunque funzionare, tranne per il fatto che funziona solo più lentamente. Il motivo principale per cui i gestori di cache sono un'eccezione a singleton è perché non ha senso chiudere un servizio cache prima di chiudere l'applicazione stessa perché ciò eliminerà anche gli oggetti memorizzati nella cache, il che vanifica il punto di averlo come singleton.
Questi sono buoni motivi per cui questi servizi sono singoli. Tuttavia, nessuno dei buoni motivi per avere i singoli si applica alle classi che hai elencato.
Perché ne avrò bisogno in posti molto diversi nel mio gioco e l'accesso condiviso sarebbe molto utile.
Quelle non sono ragioni per i singoli. Queste sono le ragioni per i globali. È anche un segno di cattiva progettazione se è necessario passare molte cose intorno a vari sistemi. Dover passare le cose in giro indica un alto accoppiamento che può essere impedito da un buon design OO.
Solo guardando l'elenco delle lezioni nel tuo post che pensi debbano essere Singletons, posso dire che la metà di loro non dovrebbe essere in realtà singleton e l'altra metà sembra che non dovrebbero nemmeno essere lì in primo luogo. Il fatto che sia necessario far passare oggetti in giro sembra essere dovuto alla mancanza di incapsulamento corretto piuttosto che a casi di utilizzo effettivo per singoli.
Diamo un'occhiata alle tue lezioni poco a poco:
PlayerData (score, lives, ...)
LevelData (parameters per levels and level packs)
GameData (you may obtain some data from server to configure the game)
Solo dai nomi delle classi, direi che queste classi hanno l'odore di antipasto di Anemic Domain Model . Gli oggetti anemici di solito producono molti dati che devono essere passati, il che aumenta l'accoppiamento e rende ingombrante il resto del codice. Inoltre, la classe anemica nasconde il fatto che probabilmente stai ancora pensando in modo procedurale piuttosto che usare l'orientamento agli oggetti per incapsulare i dettagli.
IAP (for in purchases)
Ads (for showing ads)
Perché queste lezioni devono essere dei singoli? Mi sembra che queste dovrebbero essere classi di breve durata, che dovrebbero essere sollevate quando sono necessarie e decostruite quando l'utente termina l'acquisto o quando gli annunci non devono più essere mostrati.
EntityComponentSystemManager (mananges entity creation and manipulation)
In altre parole, un costruttore e un livello di servizio? Perché questa classe non ha confini o scopi chiari nemmeno lì in primo luogo?
PlayerProgress (passed levels, stars)
Perché il progresso del giocatore è separato dalla classe del Giocatore? La classe Player dovrebbe sapere come tenere traccia dei propri progressi, se si desidera implementare il tracciamento dei progressi in una classe diversa rispetto alla classe Player per la separazione delle responsabilità, PlayerProgress dovrebbe essere dietro Player.
Box2dManager (manages the physics world)
Non posso commentare ulteriormente questo argomento senza sapere cosa fa realmente questa classe, ma una cosa è chiara è che questa classe ha un nome mediocre.
Analytics (for collecting some analytics)
SocialConnection (Facebook and Twitter login, share, friend list, ...)
Questa sembra essere l'unica classe in cui Singleton può essere ragionevole, perché gli oggetti di connessione sociale non sono in realtà i tuoi oggetti. Le connessioni sociali sono solo un'ombra di oggetti esterni che vivono in un servizio remoto. In altre parole, questo è un tipo di cache. Assicurati di separare chiaramente la parte di cache con la parte di servizio del servizio esterno, poiché quest'ultima parte non dovrebbe essere un singleton.
Un modo per evitare di passare istanze di queste classi è usare il passaggio dei messaggi. Anziché effettuare una chiamata diretta alle istanze di queste classi, si invia invece un messaggio indirizzato al servizio Analitico e SocialConnection in modo asincrono e questi servizi sono abbonati per ricevere questi messaggi e agire sul messaggio. Poiché la coda degli eventi è già un singleton, trampolando la chiamata, si evita la necessità di passare un'istanza effettiva quando si comunica con un singleton.