Update (Rückblick)
Da ich eine ziemlich ausführliche Antwort geschrieben habe, läuft alles auf Folgendes hinaus:
- Namespaces sind gut, verwenden Sie sie, wann immer es Sinn macht
- Verwendung
inGameIO
und playerIO
Klassen würden wahrscheinlich einen Verstoß gegen die SRP darstellen. Dies bedeutet wahrscheinlich, dass Sie die Art und Weise, wie Sie mit E / A umgehen, mit der Anwendungslogik koppeln.
- Haben Sie ein paar generische E / A-Klassen, die von Handlerklassen verwendet (oder manchmal gemeinsam genutzt) werden. Diese Handlerklassen würden dann die rohen Eingaben in ein Format übersetzen, aus dem Ihre Anwendungslogik einen Sinn machen kann.
- Gleiches gilt für die Ausgabe: Dies kann von relativ generischen Klassen durchgeführt werden, aber der Spielstatus wird über ein Handler- / Mapper-Objekt übergeben, das den internen Spielstatus in etwas übersetzt, das die generischen E / A-Klassen verarbeiten können.
Ich denke, Sie sehen das falsch. Sie trennen die E / A in Abhängigkeit von den Komponenten der Anwendung, während es für mich sinnvoller ist, getrennte E / A-Klassen basierend auf der Quelle und dem "Typ" der E / A zu haben .
Wenn Sie einige Basis- / generische KeyboardIO
Klassen haben MouseIO
, mit denen Sie beginnen sollen, und die dann darauf basieren, wann und wo Sie sie benötigen, verfügen Sie über Unterklassen, die diese E / A unterschiedlich behandeln.
Zum Beispiel ist die Texteingabe etwas, mit dem Sie wahrscheinlich anders umgehen möchten als mit spielinternen Steuerelementen. Sie werden feststellen, dass Sie bestimmte Schlüssel je nach Anwendungsfall unterschiedlich zuordnen möchten, aber diese Zuordnung ist nicht Teil der E / A selbst, sondern die Art und Weise, wie Sie mit der E / A umgehen.
Wenn ich mich an die SRP halte, hätte ich ein paar Klassen, die ich für die Tastatur-E / A verwenden kann. Abhängig von der Situation möchte ich wahrscheinlich auf unterschiedliche Weise mit diesen Klassen interagieren, aber ihre einzige Aufgabe ist es, mir mitzuteilen, was der Benutzer tut.
Ich würde diese Objekte dann in ein Handler-Objekt einfügen, das entweder die rohe E / A auf etwas abbildet, mit dem meine Anwendungslogik arbeiten kann (z. B .: Benutzer drückt "w" , der Handler ordnet das zu MOVE_FORWARD
).
Diese Handler werden wiederum verwendet, um die Zeichen in Bewegung zu versetzen und den Bildschirm entsprechend zu zeichnen. Eine grobe Vereinfachung, aber der Kern davon ist diese Art von Struktur:
[ 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
Was wir jetzt haben, ist eine Klasse, die für die Tastatur-E / A in ihrer Rohform verantwortlich ist. Eine andere Klasse, die diese Daten in etwas übersetzt, woraus die Spiel-Engine tatsächlich Sinn machen kann, verwendet diese Daten dann, um den Status aller beteiligten Komponenten zu aktualisieren, und schließlich kümmert sich eine separate Klasse um die Ausgabe auf dem Bildschirm.
Jede einzelne Klasse hat einen einzelnen Job: Die Bearbeitung der Tastatureingaben erfolgt durch eine Klasse, die nicht weiß / sich darum kümmert / wissen muss, was die Eingabe bedeutet, die sie verarbeitet. Es muss nur wissen, wie die Eingabe abgerufen wird (gepuffert, ungepuffert, ...).
Der Handler übersetzt dies in eine interne Darstellung für den Rest der Anwendung, um diese Informationen zu verstehen.
Die Game-Engine verwendet die übersetzten Daten, um alle relevanten Komponenten über das Geschehen zu informieren. Jede dieser Komponenten macht nur eine Sache, egal ob es sich um Kollisionsprüfungen oder Änderungen der Charakteranimation handelt, es spielt keine Rolle, das hängt von jedem einzelnen Objekt ab.
Diese Objekte geben dann ihren Zustand zurück, und diese Daten werden an Game.Screen
einen inversen E / A-Handler übergeben. Es ordnet die interne Darstellung einem Element zu, mit dem die IO.Screen
Komponente die eigentliche Ausgabe generieren kann.