Actualización (resumen)
Como he escrito una respuesta bastante detallada, esto es lo que todo se reduce a:
- Los espacios de nombres son buenos, úselos siempre que tenga sentido
- Usar
inGameIO
y playerIO
clases probablemente constituiría una violación del SRP. Probablemente significa que está acoplando la forma en que maneja IO con la lógica de la aplicación.
- Tenga un par de clases de IO genéricas, que las clases de controladores usan (o a veces comparten). Estas clases de manejador luego traducirían la entrada sin formato a un formato que la lógica de su aplicación pueda tener sentido.
- Lo mismo ocurre con la salida: esto puede hacerse mediante clases bastante genéricas, pero pasa el estado del juego a través de un objeto controlador / mapeador que traduce el estado interno del juego en algo que las clases genéricas de IO pueden manejar.
Creo que estás viendo esto de manera incorrecta. Está separando el IO en función de los componentes de la aplicación, mientras que, para mí, tiene más sentido tener clases de IO separadas basadas en la fuente y el "tipo" de IO.
Tener algunas KeyboardIO
clases base / genéricas MouseIO
para comenzar, y luego basarse en cuándo y dónde las necesita, tener subclases que manejen dicho IO de manera diferente.
Por ejemplo, el ingreso de texto es algo que probablemente quieras manejar de manera diferente a los controles del juego. Tendrás ganas de asignar ciertas teclas de manera diferente dependiendo de cada caso de uso, pero esa asignación no es parte del IO en sí, es cómo manejas el IO.
Siguiendo el SRP, tendría un par de clases que puedo usar para el teclado IO. Dependiendo de la situación, probablemente quiera interactuar con estas clases de manera diferente, pero su único trabajo es decirme qué está haciendo el usuario.
Luego inyectaría estos objetos en un objeto de controlador que mapearía el IO sin procesar en algo con lo que la lógica de mi aplicación pueda trabajar (por ejemplo: el usuario presiona "w" , el controlador lo mapea MOVE_FORWARD
).
Estos controladores, a su vez, se utilizan para hacer que los personajes se muevan y dibujar la pantalla en consecuencia. Una simplificación excesiva, pero lo esencial es este tipo de estructura:
[ 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
Lo que tenemos ahora es una clase que es responsable del teclado IO en su forma cruda. Otra clase que traduce estos datos en algo que el motor del juego realmente puede tener sentido, estos datos se utilizan para actualizar el estado de todos los componentes involucrados y, finalmente, una clase separada se encargará de la salida a la pantalla.
Cada clase tiene un solo trabajo: el manejo de la entrada del teclado se realiza por una clase que no sabe / no importa / tiene que saber qué significa la entrada que está procesando. Todo lo que hace es saber cómo obtener la entrada (con búfer, sin búfer, ...).
El controlador traduce esto en una representación interna para que el resto de la aplicación tenga sentido de esta información.
El motor del juego toma los datos que se tradujeron y los utiliza para notificar a todos los componentes relevantes que algo está sucediendo. Cada uno de estos componentes hace solo una cosa, ya sean controles de colisión o cambios en la animación de personajes, no importa, eso depende de cada objeto individual.
Estos objetos luego retransmiten su estado, y estos datos se pasan a Game.Screen
, que es esencialmente un manejador de E / S inverso. Mapea la representación interna en algo que el IO.Screen
componente puede usar para generar la salida real.