Update (recap)
Since I've written a rather verbose answer, here's what it all boils down to:
- Namespaces are good, use them whenever it makes sense
- Using
inGameIO
and playerIO
classes would likely constitute a breach of the SRP. It likely means you're coupling the way you handle IO with the application logic.
- Have a couple of generic IO classes, that are used (or sometimes shared) by handler classes. These handler classes would then translate the raw input into a format your application logic can make sense of.
- Same goes for the output: this can be done by fairly generic classes, but pass the game state through a handler/mapper object that translates the internal game state into something the generic IO classes can handle.
I think you're looking at this in the wrong way. You're separating out the IO in function of the components of the application, whereas - to me- it makes more sense to have separate IO classes based on the source, and "type" of IO.
Having some base/generic KeyboardIO
classes MouseIO
to start off with, and then based on when and where you need them, have subclasses that handle said IO differently.
For example, text input is something you probably want to handle differently to in-game controls. You'll find yourself wanting to map certain keys differently depending on each use case, but that mapping isn't part of the IO itself, it's how you're handling the IO.
Sticking to the SRP, I'd have a couple of classes that I can use for keyboard IO. Depending on the situation, I'll probably want to interact with these classes differently, but their only job is to tell me what the user is doing.
I'd then inject these objects into a handler object that would either map the raw IO onto something that my application logic can work with (eg: user presses "w", the handler maps that onto MOVE_FORWARD
).
These handlers, in turn are used to make the characters move, and draw the screen accordingly. A gross oversimplification, but the gist of it is this kind of 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
What we have now is a class that is responsible for the keyboard IO in its raw form. Another class that translates this data into something the game engine can actually make sense of, this data is then used to update the state of all of the components involved, and finally, a separate class will take care of the output to the screen.
Every single class has a single job: handling keyboard input is done by a class that doesn't know/care/has to know what the input it's processing means. All it does is know how to get the input (buffered, unbuffered, ...).
The handler translates this into an internal representation for the rest of the application to make sense of this info.
The game engine takes the data that was translated, and uses it to notify all of the relevant components that something is going on. Each of these components do just one thing, whether that be collision checks, or character animation changes, it doesn't matter, that's down to each individual object.
These objects then relay their state back, and this data is passed to Game.Screen
, which is in essence an inverse IO handler. It maps the internal representation onto something the IO.Screen
component can use to generate the actual output.