Aggiornamento (riepilogo)
Da quando ho scritto una risposta piuttosto dettagliata, ecco cosa si riduce a:
- Gli spazi dei nomi sono buoni, usali ogni volta che ha senso
- L'uso
inGameIO
e le playerIO
classi costituirebbero probabilmente una violazione dell'SRP. Probabilmente significa che stai accoppiando il modo in cui gestisci IO con la logica dell'applicazione.
- Avere un paio di classi IO generiche, che sono usate (o talvolta condivise) dalle classi gestore. Queste classi di gestori traducono quindi l'input non elaborato in un formato comprensibile per la logica dell'applicazione.
- Lo stesso vale per l'output: questo può essere fatto da classi abbastanza generiche, ma passa lo stato del gioco attraverso un oggetto handler / mapper che traduce lo stato del gioco interno in qualcosa che le classi IO generiche possono gestire.
Penso che tu lo stia guardando nel modo sbagliato. Stai separando l'IO in funzione dei componenti dell'applicazione, mentre - per me - ha più senso avere classi IO separate in base alla sorgente e al "tipo" di IO.
Avere alcune KeyboardIO
classi MouseIO
di base / generiche con cui iniziare, e quindi in base a quando e dove sono necessarie, hanno sottoclassi che gestiscono l'IO in modo diverso.
Ad esempio, l'immissione di testo è qualcosa che probabilmente vorresti gestire diversamente dai controlli di gioco. Ti ritroverai a voler mappare determinate chiavi in modo diverso a seconda di ciascun caso d'uso, ma quella mappatura non fa parte dell'IO stesso, è il modo in cui gestisci l'IO.
Seguendo l'SRP, avrei un paio di classi che posso usare per l'IO della tastiera. A seconda della situazione, probabilmente vorrò interagire con queste classi in modo diverso, ma il loro unico compito è dirmi cosa sta facendo l'utente.
Inietterei quindi questi oggetti in un oggetto gestore che mapperebbe l'IO grezzo su qualcosa con cui la mia logica applicativa può funzionare (ad esempio: l'utente preme "w" , il gestore lo mappa suMOVE_FORWARD
).
Questi gestori, a loro volta, sono usati per far muovere i personaggi e per disegnare lo schermo di conseguenza. Una grossolana semplificazione, ma l'essenza di questo è questo tipo di struttura:
[ IO.Keyboard.InGame ] // generic, if SoC and SRP are strongly adhered to, changing this component should be fairly easy to do
||
==> [ Controls.Keyboard.InGameMapper ]
[ Game.Engine ] <- Controls.Keyboard.InGameMapper
<- IO.Screen
<- ... all sorts of stuff here
InGameMapper.move() //returns MOVE_FORWARD or something
||
==> 1. Game.updateStuff();//do all the things you need to do to move the character in the given direction
2. Game.Screen.SetState(GameState); //translate the game state (inverse handler)
3. IO.Screen.draw();//generate actual output
Ciò che abbiamo ora è una classe che è responsabile dell'IO della tastiera nella sua forma grezza. Un'altra classe che traduce questi dati in qualcosa che il motore di gioco può effettivamente dare un senso, questi dati vengono quindi utilizzati per aggiornare lo stato di tutti i componenti coinvolti e, infine, una classe separata si occuperà dell'output sullo schermo.
Ogni singola classe ha un singolo lavoro: la gestione dell'input da tastiera viene eseguita da una classe che non sa / cura / deve sapere cosa significa l'input che sta elaborando. Tutto quello che fa è sapere come ottenere l'input (bufferizzato, senza buffer, ...).
Il gestore lo traduce in una rappresentazione interna per il resto dell'applicazione per dare un senso a queste informazioni.
Il motore di gioco prende i dati tradotti e li utilizza per avvisare tutti i componenti rilevanti che qualcosa sta succedendo. Ognuno di questi componenti fa solo una cosa, che si tratti di controlli di collisione o modifiche dell'animazione del personaggio, non importa, dipende da ogni singolo oggetto.
Questi oggetti quindi ritrasmettono il loro stato e questi dati vengono passati a Game.Screen
, che in sostanza è un gestore IO inverso. Mappa la rappresentazione interna su qualcosa che il IO.Screen
componente può usare per generare l'output effettivo.