Aktualizacja (podsumowanie)
Ponieważ napisałem dość pełną odpowiedź, oto, co sprowadza się do:
- Przestrzenie nazw są dobre, używaj ich, kiedy ma to sens
- Używanie
inGameIO
i playerIO
klasy prawdopodobnie stanowiłyby naruszenie SRP. Prawdopodobnie oznacza to, że łączysz sposób obsługi IO z logiką aplikacji.
- Mają kilka ogólnych klas we / wy, które są używane (lub czasem współużytkowane) przez klasy obsługi. Te klasy procedur obsługi tłumaczyłyby następnie surowe dane wejściowe na format, jaki może mieć logika aplikacji.
- To samo dotyczy danych wyjściowych: może to być zrobione przez dość ogólne klasy, ale przekazanie stanu gry przez obiekt procedury obsługi / mapowania, który przekształca wewnętrzny stan gry w coś, co mogą obsłużyć ogólne klasy we / wy.
Myślę, że patrzysz na to w niewłaściwy sposób. Oddzielasz IO w zależności od komponentów aplikacji, podczas gdy - dla mnie - bardziej sensowne jest posiadanie oddzielnych klas IO opartych na źródle i „typie” IO.
Posiadanie pewnych podstawowych / ogólnych KeyboardIO
klas MouseIO
na początek, a następnie opartych na tym, kiedy i gdzie ich potrzebujesz, mają podklasy, które inaczej obsługują wspomniane IO.
Na przykład wprowadzanie tekstu jest czymś, co prawdopodobnie chcesz obsługiwać inaczej niż sterowanie w grze. Przekonasz się, że chcesz mapować niektóre klucze w różny sposób w zależności od każdego przypadku użycia, ale to mapowanie nie jest częścią samego IO, to sposób, w jaki obsługujesz IO.
Trzymając się SRP, miałbym kilka klas, których mogę użyć do operacji na klawiaturze. W zależności od sytuacji prawdopodobnie będę chciał wchodzić w interakcje z tymi klasami w różny sposób, ale ich jedynym zadaniem jest powiedzenie mi, co robi użytkownik.
Następnie wstrzyknąłem te obiekty do obiektu obsługi, który albo odwzorowałby surowe IO na coś, z czym mogłaby współpracować moja logika aplikacji (np. Użytkownik naciska „w” , program obsługi odwzorowuje to na MOVE_FORWARD
).
Te uchwyty z kolei służą do poruszania postaci i odpowiednio rysują ekran. Rażące uproszczenie, ale istotą tego jest taka struktura:
[ 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
To, co mamy teraz, to klasa odpowiedzialna za IO klawiatury w jej surowej formie. Kolejna klasa, która tłumaczy te dane na coś, co silnik gry może właściwie zrozumieć, dane te są następnie wykorzystywane do aktualizacji stanu wszystkich zaangażowanych komponentów, a na koniec osobna klasa zajmie się wyjściem na ekran.
Każda klasa ma jedno zadanie: obsługa wprowadzania z klawiatury odbywa się przez klasę, która nie wie / nie obchodzi / musi wiedzieć, co oznacza przetwarzanie, które przetwarza. Wystarczy wiedzieć, jak uzyskać dane wejściowe (buforowane, niebuforowane, ...).
Program obsługi tłumaczy to na wewnętrzną reprezentację reszty aplikacji, aby zrozumieć te informacje.
Silnik gry pobiera przetłumaczone dane i wykorzystuje je do powiadomienia wszystkich odpowiednich komponentów, że coś się dzieje. Każdy z tych elementów wykonuje tylko jedną rzecz, czy to kontrole kolizji, czy zmiany animacji postaci, to nie ma znaczenia, to zależy od każdego obiektu.
Obiekty te następnie przekazują swój stan z powrotem, a dane są przekazywane do Game.Screen
, co w istocie jest odwrotnym modułem obsługi We / Wy. Odwzorowuje wewnętrzną reprezentację na coś, co IO.Screen
składnik może wykorzystać do wygenerowania rzeczywistych wyników.