Mise à jour (récapitulation)
Depuis que j'ai écrit une réponse assez verbeuse, voici ce que cela se résume à:
- Les espaces de noms sont bons, utilisez-les chaque fois que cela a du sens
- L'utilisation
inGameIO
et les playerIO
classes constitueraient probablement une violation du SRP. Cela signifie probablement que vous associez la façon dont vous gérez les E / S avec la logique d'application.
- Avoir quelques classes d'E / S génériques, qui sont utilisées (ou parfois partagées) par les classes de gestionnaire. Ces classes de gestionnaire traduiraient alors l'entrée brute dans un format que votre logique d'application peut comprendre.
- Il en va de même pour la sortie: cela peut être fait par des classes assez génériques, mais passez l'état du jeu via un objet gestionnaire / mappeur qui traduit l'état de jeu interne en quelque chose que les classes génériques d'E / S peuvent gérer.
Je pense que vous regardez cela dans le mauvais sens. Vous séparez l'IO en fonction des composants de l'application, alors que - pour moi - il est plus logique d'avoir des classes d'E / S distinctes en fonction de la source et du "type" d'E / S.
Avoir des KeyboardIO
classes de base / génériques MouseIO
pour commencer, puis en fonction du moment et de l'endroit où vous en avez besoin, ont des sous-classes qui gèrent différemment les E / S.
Par exemple, la saisie de texte est quelque chose que vous voudrez probablement gérer différemment des contrôles en jeu. Vous vous retrouverez à vouloir mapper certaines clés différemment selon chaque cas d'utilisation, mais ce mappage ne fait pas partie de l'IO lui-même, c'est la façon dont vous gérez l'IO.
S'en tenir au SRP, j'aurais quelques classes que je peux utiliser pour les entrées-sorties clavier. Selon la situation, je souhaiterai probablement interagir différemment avec ces classes, mais leur seul travail est de me dire ce que fait l'utilisateur.
J'injecterais ensuite ces objets dans un objet gestionnaire qui mapperait le IO brut sur quelque chose avec lequel ma logique d'application peut fonctionner (par exemple: l'utilisateur appuie sur "w" , le gestionnaire le mappe MOVE_FORWARD
).
Ces gestionnaires, à leur tour, sont utilisés pour faire bouger les personnages et dessiner l'écran en conséquence. Une simplification grossière, mais l'essentiel est ce genre de structure:
[ 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
Ce que nous avons maintenant, c'est une classe qui est responsable de l'IO du clavier dans sa forme brute. Une autre classe qui traduit ces données en quelque chose que le moteur de jeu peut réellement donner un sens, ces données sont ensuite utilisées pour mettre à jour l'état de tous les composants impliqués, et enfin, une classe distincte se chargera de la sortie à l'écran.
Chaque classe a un seul travail: la gestion des entrées au clavier est effectuée par une classe qui ne sait pas / ne prend pas soin / doit savoir ce que signifie l'entrée qu'elle traite. Tout ce qu'il fait, c'est savoir comment obtenir l'entrée (tamponnée, non tamponnée, ...).
Le gestionnaire traduit cela en une représentation interne pour le reste de l'application pour donner un sens à ces informations.
Le moteur de jeu prend les données qui ont été traduites et les utilise pour informer tous les composants pertinents que quelque chose se passe. Chacun de ces composants ne fait qu'une chose, qu'il s'agisse de contrôles de collision ou de changements d'animation de personnage, peu importe, c'est à chaque objet individuel.
Ces objets retransmettent ensuite leur état et ces données sont transmises à Game.Screen
, qui est essentiellement un gestionnaire d'E / S inversé. Il mappe la représentation interne sur quelque chose que le IO.Screen
composant peut utiliser pour générer la sortie réelle.