Jak ominąć obiekt boga GameManager?

51

Właśnie przeczytałem odpowiedź na pytanie dotyczące struktury kodu gry . Zastanawiałem się nad wszechobecną GameManagerklasą i tym, jak często staje się problemem w środowisku produkcyjnym. Pozwól mi to opisać.

Po pierwsze, jest prototypowanie. Nikt nie dba o pisanie świetnego kodu, staramy się po prostu coś uruchomić, aby sprawdzić, czy gra się sumuje.

Potem jest zielone światło i ktoś stara się posprzątać GameManager. Prawdopodobnie do przechowywania kilku GameStates, może do przechowywania kilku GameObjects, nic wielkiego, naprawdę. Słodki, mały menedżer.

W spokojnej krainie przedprodukcyjnej gra dobrze się kształtuje. Coders ma odpowiednie noce spania i mnóstwo pomysłów na architekturę dzięki wspaniałym wzorom projektowym.

Następnie rozpoczyna się produkcja i wkrótce nadejdzie czas na kryzys. Zrównoważona dieta już dawno minęła, narzędzie do śledzenia błędów pęka z problemami, ludzie są zestresowani, a gra musi zostać wydana wczoraj.

W tym momencie zwykle GameManagerjest naprawdę duży bałagan (pozostać uprzejmy).

Powód tego jest prosty. Po tym wszystkim, pisząc grę, dobrze ... cały kod źródłowy jest faktycznie tutaj, aby zarządzać w grę . Łatwo jest po prostu dodać tę małą dodatkową funkcję lub poprawkę błędu w GameManager, gdzie i tak wszystko jest już zapisane. Gdy czas staje się problemem, nie ma sposobu, aby napisać osobną klasę lub podzielić tego gigantycznego menedżera na podrzędne.

Oczywiście jest to klasyczny anty-wzór: obiekt boga . To zła rzecz, ból do scalenia, ból do utrzymania, ból do zrozumienia, ból do transformacji.

Co sugerujesz, aby temu zapobiec?

EDYTOWAĆ

Wiem, że kuszenie jest obwinianie nazwy. Oczywiście, tworzenie GameManagerklasy nie jest świetnym pomysłem. Ale to samo może przydarzyć się czysty GameApplicationlub GameStateMachineczy GameSystemże kończy się przewód-tape-ulubionym do końca projektu. Bez względu na nazwę, masz tę klasę gdzieś w kodzie gry, na razie to tylko zarodek: po prostu nie wiesz jeszcze, jakim potworem się stanie. Więc nie oczekuję odpowiedzi „winić nazewnictwo”. Chcę sposobu, aby temu zapobiec, architektury kodowania i / lub procesu, który zespół może śledzić, wiedząc, że stanie się to w pewnym momencie produkcji. Po prostu źle jest wyrzucić, powiedzmy, miesiąc poprawek i funkcji z ostatniej chwili, tylko dlatego, że wszystkie są teraz w jednym ogromnym, niemożliwym do utrzymania menedżerze.

Laurent Couvidou
źródło
Bez względu na to, czy nazywa się GameManager, ControllerOfTheGame, czy jakkolwiek chcesz to nazwać, mam na myśli unikanie klasy, która jest odpowiedzialna za kontrolowanie logiki całej gry. Wiesz, że robisz to, kiedy to robisz, niezależnie od tego, co planujesz nazywać. Wydaje mi się, że w mojej ostatniej grze miałem kontroler poziomów, który zaczynał od utrzymania stanu poziomu do zapisania, ale jeśli zatrzymałem się i pomyślałem o tym, to było oczywiste, że ten obiekt miał obsługiwać więcej szczegółów niż powinien.
brandon
@brandon Pozwól, że sformułuję moje pytanie za Ciebie: w jaki sposób kontrolujesz logikę całej gry bez narażania się na GameManagerefekt, który opisałem powyżej?
Laurent Couvidou
Zależy od logiki. Oczywiste rzeczy, takie jak gracz, wróg, struktura itp., Które wyraźnie mają własną logikę, zostają przypisane do odpowiednich klas. To, o co chyba najbardziej pytasz, to kontrolowanie stanu gry. Zazwyczaj istnieje klasa, która śledzi stan całej gry, ale z mojego doświadczenia jest to jedna z ostatnich rzeczy, o które musisz się martwić podczas prototypowania, a jej cel powinien być bardzo jasny. Zasadniczo albo robisz wstępne planowanie przed prototypowaniem, albo zaczynasz od zera po uruchomieniu produkcji, aby uniknąć używania śmieciowych klas, które były tylko do testowania.
brandon
Co ciekawe, XNA tworzy dla ciebie tę potencjalną katastrofę. Domyślnie klasa gier jest tworzona, aby zarządzać wszystkim. Zawiera główne metody aktualizacji, rysowania, inicjowania i ładowania. W rzeczywistości jestem w trakcie migracji do nowego projektu, ponieważ moja (nie tak złożona) gra stała się zbyt trudna do zarządzania ze wszystkim, co było w tej klasie. Ponadto samouczki Microsoft wydają się zachęcać.
Fibericon,

Odpowiedzi:

23

Potem jest zielone światło i ktoś próbuje napisać GameManagera. Prawdopodobnie do przechowywania wielu GameStates, może do przechowywania kilku GameObjects, nic wielkiego, naprawdę. Słodki, mały menedżer.

Wiesz, kiedy to czytałem, miałem w głowie małe alarmy. Obiekt o nazwie „GameManager” nigdy nie będzie słodki ani mały. I ktoś to zrobił, aby wyczyścić kod? Jak to wcześniej wyglądało? OK, żartujmy na bok: nazwa klasy powinna być wyraźnym wskazaniem tego, co robi klasa, i to powinno być jedno (aka: zasada pojedynczej odpowiedzialności ).

Ponadto możesz nadal mieć obiekt taki jak GameManager, ale najwyraźniej istnieje on na bardzo wysokim poziomie i powinien zajmować się zadaniami na wysokim poziomie. Prowadzisz katalog przedmiotów do gry? Być może. Ułatwienie komunikacji między obiektami gry? Pewnie. Obliczanie kolizji między obiektami? Nie! Z tego powodu nazwa Menedżera jest niezadowolona - jest zbyt szeroka i pozwala na wiele nadużyć pod tym sztandarem.

Szybka reguła dotycząca wielkości klas: jeśli napotkasz kilkaset wierszy kodu na klasę, coś zaczyna się nie udać. Bez nadgorliwości, wszystko ponad, powiedzmy, 300 LOC jest dla mnie zapachem kodu, a jeśli przekroczysz 1000, powinny zadzwonić dzwonki ostrzegawcze. Wierząc, że w jakiś sposób łatwiej jest zrozumieć 1000 linii kodu niż 4 dobrze ustrukturyzowane klasy po 250, każda oszukasz siebie.

Gdy czas staje się problemem, nie ma sposobu, aby napisać osobną klasę lub podzielić tego gigantycznego menedżera na podrzędne.

Myślę, że dzieje się tak tylko dlatego, że problem może rozprzestrzeniać się do punktu, w którym wszystko jest kompletnym bałaganem. Praktyka refaktoryzacji jest naprawdę tym, czego szukasz - musisz ciągle ulepszać projekt kodu w małych krokach .

Co sugerujesz, aby temu zapobiec?

Problem nie jest technologiczny, więc nie powinieneś szukać rozwiązań technologicznych. Problem w tym, że w twoim zespole jest tendencja do tworzenia monolitycznych fragmentów kodu i przekonanie, że w jakiś sposób korzystne jest w średnim / długim okresie takie działanie. Wydaje się również, że zespołowi brakuje silnej przewagi architektonicznej, która sterowałaby architekturą gry (a przynajmniej ta osoba jest zbyt zajęta, aby wykonać to zadanie). Zasadniczo jedynym wyjściem jest przekonanie członków zespołu, że takie myślenie jest złe. Nikomu nie sprzyja. Jakość produktu pogorszy się, a zespół spędzi jeszcze więcej nocy na naprawie.

Dobrą wiadomością jest to, że natychmiastowe, wymierne korzyści z pisania czystego kodu są tak wspaniałe, że prawie wszyscy programiści bardzo szybko zdają sobie z tego sprawę. Przekonaj zespół, aby pracował przez chwilę w ten sposób, a wyniki zrobią resztę.

Trudność polega na tym, że rozwijanie wyczucia tego, co stanowi zły kod (i talent do szybkiego wymyślania lepszego projektu), jest jedną z trudniejszych umiejętności do nauczenia się w rozwoju. Moja sugestia opiera się na nadziei, że w zespole jest ktoś wystarczająco starszy, kto może to zrobić - o wiele łatwiej przekonać ludzi w ten sposób.

Edycja - trochę więcej informacji:

Ogólnie rzecz biorąc, nie sądzę, aby twój problem ograniczał się do tworzenia gier. U jego podstaw leży problem inżynierii oprogramowania, stąd moje komentarze w tym kierunku. To, co może się różnić, to natura branży tworzenia gier, nie jestem pewien, czy jej wyniki i termin są zorientowane bardziej niż inne rodzaje rozwoju.

Jednak, szczególnie w przypadku tworzenia gier, przyjęta odpowiedź na to pytanie na StackOverflow dotycząca wskazówek dotyczących „szczególnie architektury gier” mówi:

Przestrzegaj stałych zasad projektowania obiektowego ....

To jest dokładnie to, co mówię. Kiedy jestem pod presją, również piszę duże fragmenty kodu, ale wywierciłem sobie w głowie, że to dług techniczny. To, co zwykle działa dla mnie dobrze, to spędzić pierwszą połowę (lub trzy czwarte) dnia na produkcji dużej ilości kodu średniej jakości, a następnie usiąść i pomyśleć o tym przez chwilę; Zrobię trochę projektowania w mojej głowie lub na papierze / tablicy, o tym, jak nieco poprawić kod. Często dostrzegam powtarzający się kod i jestem w stanie zmniejszyć całkowitą liczbę wierszy kodu, rozkładając różne elementy, jednocześnie poprawiając czytelność. Tym razem zainwestowane pieniądze zwracają się tak szybko, że nazywanie tego „inwestycją” brzmi głupio - dość często wybieram błędy, które mogłyby zmarnować połowę mojego dnia (tydzień później), gdybym na to pozwolił.

  • Napraw rzeczy w tym samym dniu, w którym je kodujesz.
  • Będziesz zadowolony, że zrobiłeś to w ciągu kilku godzin.

Dojście do przekonania, że ​​powyższe jest trudne; Udało mi się to zrobić dla własnej pracy tylko dlatego, że ciągle doświadczałem efektów. Mimo to wciąż trudno mi usprawiedliwić poprawianie kodu, gdy mogę więcej rezygnować ... Zdecydowanie rozumiem, skąd pochodzisz. Niestety, ta rada jest może zbyt ogólna i niełatwa do zrobienia. Jednak mocno w to wierzę! :)

Aby odpowiedzieć na konkretny przykład:

Clean Code wuja Boba wykonuje niesamowitą pracę, podsumowując, jak wygląda kod dobrej jakości. Zgadzam się z prawie całą jego zawartością. Tak więc, kiedy myślę o twoim przykładzie klasy menedżera LOC na 30 000 osób, nie mogę się zgodzić z częścią dotyczącą „dobrego powodu”. Nie chcę zabrzmieć obraźliwie, ale takie myślenie doprowadzi do problemu. Nie ma dobrego powodu, aby mieć tak dużo kodu w jednym pliku - to prawie 1000 stron tekstu! Wszelkie korzyści związane z lokalizacją (szybkość wykonywania lub „prostota” projektu) zostaną natychmiast unieważnione przez deweloperów, którzy całkowicie zapadną w pamięć, próbując poradzić sobie z tą potwornością, i to nawet zanim omówimy połączenie itp.

Jeśli nie jesteś przekonany, moją najlepszą propozycją byłoby wziąć kopię powyższej książki i przejrzeć ją. Zastosowanie tego rodzaju myślenia prowadzi do tego, że ludzie dobrowolnie tworzą czysty, zrozumiały kod, który ma ładną strukturę.

Daniel B.
źródło
To nie jest wada, którą kiedyś widziałem. Widziałem to w kilku zespołach dla kilku projektów. Widziałem wielu utalentowanych, ustrukturyzowanych ludzi zmagających się z tym. Gdy producent znajduje się pod presją, limit 300 wierszy kodu nie jest tym, na czym mu zależy. Gracz też nie btw. Jasne, że to miłe i słodkie, ale diabeł tkwi w szczegółach. Najgorszy przypadek GameManager, jaki do tej pory widziałem, to około 30 000 linii kodu i jestem pewien, że większość z nich była tutaj z bardzo dobrego powodu. Jest to oczywiście ekstremalne, ale takie rzeczy dzieją się w prawdziwym świecie. Proszę o sposób na ograniczenie tego GameManagerefektu.
Laurent Couvidou
@lorancou - Dodałem sporo dodatkowych informacji do wpisu, to było trochę za dużo na komentarz, mam nadzieję, że daje to trochę więcej pomysłów, nawet jeśli nie mogę wskazać żadnej konkretnej architektury przykłady
Daniel B
Ach, nie przeczytałem tej książki, więc to zdecydowanie dobra sugestia. Aha, i nie miałem na myśli, że istnieje dobry powód, aby dodać kod w klasie złożonej z tysięcy wierszy. Miałem na myśli, że cały kod w tego rodzaju obiekcie bożym robi coś pożytecznego. Prawdopodobnie napisałem to trochę za szybko;)
Laurent Couvidou 16.04. O
@lorancou Hehe, to dobrze ... w ten sposób leży szaleństwo :)
Daniel B
„Praktyka refaktoryzacji jest naprawdę tym, czego szukasz - musisz ciągle ulepszać projekt kodu w małych krokach.”. Myślę, że masz bardzo mocną stronę. Bałagan przyciąga bałagan, to jest prawdziwy powód tego, więc bałagan musi być zwalczany w perspektywie długoterminowej. Przyjmuję tę odpowiedź, ale to nie znaczy, że w innych nie ma żadnej wartości!
Laurent Couvidou
7

To nie jest tak naprawdę problem z programowaniem gier, ale ogólnie z programowaniem.

cały kod źródłowy jest tutaj, aby zarządzać grą.

Dlatego powinieneś zmarszczyć brwi przy każdym koderze zorientowanym obiektowo, który sugeruje klasy z „Menedżerem” w nazwie. Szczerze mówiąc, myślę, że praktycznie niemożliwe jest unikanie obiektów „półbogów” w grze, ale nazywamy je po prostu „systemem”.

Każde oprogramowanie może ulec zabrudzeniu, gdy zbliża się termin. To część pracy. I nie ma nic z natury złego w „ programowaniu kanałów ”, szczególnie gdy musisz wysłać swój produkt w ciągu kilku godzin. Nie możesz całkowicie pozbyć się tego problemu, możesz go po prostu zredukować.

Oczywiście, jeśli masz ochotę umieścić różne rzeczy w GameManager, oznacza to, że nie masz bardzo zdrowej bazy kodu. Mogą występować dwa problemy:

  • Twój kod jest zbyt skomplikowany. Zbyt wiele wzorów, przedwczesna optymalizacja, nikt już nie rozumie przepływu. Staraj się używać więcej TDD, jeśli możesz, ponieważ jest to bardzo dobre rozwiązanie do walki ze złożonością.

  • Twój kod nie ma dobrej struktury. Używasz (ab) zmiennej globals, klasy nie są luźno powiązane, nie ma żadnego interfejsu, żadnego systemu zdarzeń i tak dalej.

Jeśli kod wydaje się w porządku, musisz po prostu pamiętać dla każdego projektu „co poszło nie tak” i unikać go następnym razem.

Wreszcie może to być także problem zarządzania zespołem: czy było wystarczająco dużo czasu? Czy wszyscy wiedzieli o architekturze, której szukasz? Kto, do diabła, zezwolił na zatwierdzenie z klasą ze słowem „Menedżer”?

Raveline
źródło
Nie ma nic złego w programowaniu taśmy izolacyjnej, daję ci to. Ale jestem całkiem pewien - klasy menedżerskie są wszędzie w silnikach gier, przynajmniej je widziałem. Są użyteczne, dopóki nie urosną zbytnio ... Co jest nie tak z a SceneManagerlub a NetworkManager? Nie rozumiem, dlaczego powinniśmy im zapobiegać, ani w jaki sposób zastępowanie -Manager przez -System pomaga. Szukam sugestii dla architektury zastępczej dla tego, co zwykle przechodzi w GameManagercoś mniej podatnego na wzdęcia.
Laurent Couvidou
Co jest nie tak z SceneManager? Jaka jest różnica między Sceną a Menedżerem scen? Sieć i menedżer sieci? Z drugiej strony: nie sądzę, że jest to kwestia architektury, ale jeśli możesz podać konkretny przykład czegoś, co jest w GameManager i nie powinno tam być, łatwiej byłoby zaproponować rozwiązanie.
Raveline
OK, więc zapomnij o opcji -Manager. Powiedzmy, że masz klasę, która obsługuje twoje stany gry, nazwijmy to GameStatesCollection. Na początku będziesz miał tylko kilka stanów gry i sposoby przełączania się między nimi. Potem są ekrany ładowania, które komplikują to wszystko. Następnie jakiś programista musi poradzić sobie z błędem, gdy użytkownik wysuwa dysk podczas przełączania z poziomu_A na poziom_B, i jest to jedyny sposób, aby go naprawić bez spędzania pół dnia refaktoryzacji GameStatesCollection. Bum, obiekt boga.
Laurent Couvidou
5

Przynosząc jedno możliwe rozwiązanie z bardziej korporacyjnego świata: czy sprawdziłeś odwrócenie iniekcji kontroli / zależności?

Zasadniczo otrzymujesz ten szkielet, który jest pojemnikiem na wszystko inne w twojej grze. I tylko on wie, jak połączyć wszystko razem i jakie są relacje między twoimi przedmiotami. Żaden z twoich obiektów nie tworzy niczego w swoich konstruktorach. Zamiast tworzyć to, czego potrzebują, proszą o to, czego potrzebują w ramach IoC; po utworzeniu środowisko IoC „wie”, jaki obiekt jest potrzebny do zaspokojenia zależności i wypełnia ją. Luźne sprzęgło FTW.

Gdy klasa EnemyTreasure prosi kontener o obiekt GameLevel, środowisko wie, jaką instancję ma mu dać. Skąd to wie? Może wcześniej to utworzył, może poprosi o pobliską GameLevelFactory. Chodzi o to, że EnemyTreasure nie wie, skąd pochodzi instancja GameLevel. Wie tylko, że jego zależność jest teraz zaspokojona i może żyć dalej.

Och, widzę, że twoja klasa PlayerSprite implementuje IUpdateable. Cóż, to świetnie, ponieważ framework automatycznie subskrybuje każdy obiekt IUpdateable Timer, czyli tam, gdzie jest główna pętla gry. Każdy Timer :: tick () automatycznie wywołuje wszystkie metody IUpdatedable :: update (). Łatwe, prawda?

Realistycznie nie potrzebujesz tej ramy bighugeohmygod, aby zrobić to za Ciebie: możesz sam zrobić ważne części. Chodzi o to, że jeśli tworzysz obiekty typu -Manager, istnieje duże prawdopodobieństwo, że nie rozdzielasz poprawnie swoich obowiązków między klasy lub gdzieś brakuje niektórych klas.

drhayes
źródło
Ciekawe, kiedy pierwszy raz usłyszałem o IoC. To może być zbyt bighugeohmygod do programowania gier, ale sprawdzę to!
Laurent Couvidou
IoC, czy nie nazywaliśmy tego zdrowym rozsądkiem?
Brice
Mając połowę szansy, inżynier stworzy szkielet z czegokolwiek. (= Poza tym nie jestem zwolennikiem korzystania z frameworka, jestem zwolennikiem wzoru architektonicznego.
drhayes
3

W przeszłości zazwyczaj kończę na klasie boga, jeśli wykonuję następujące czynności:

  • usiądź przy edytorze gier / IDE / notatniku, zanim wypiszę schemat klasy (lub tylko szkic głównych obiektów)
  • Zacznij od bardzo niejasnego pomysłu na grę i od razu ją koduj, a następnie zacznij iterować nowe pomysły
  • utwórz plik klasy o nazwie GameManager, gdy nie tworzę gry opartej na stanie, takiej jak strategia turowa, w której GameManager jest w rzeczywistości obiektem domeny
  • wcześnie nie przejmuj się organizacją kodu

Może to budzić kontrowersje, które śmieję się, jeśli tak jest, ale nawet w prototypowaniu nie mogę wymyślić, że nie powinieneś pisać bardzo podstawowego schematu klas przed napisaniem grywalnego prototypu. Zwykle testuję pomysły techniczne przed planowaniem, ale o to chodzi. Więc gdybym chciał stworzyć portal, napisałbym bardzo prosty kod, aby uruchomić portale, a następnie powiedziałbym, że jest świetny czas na drobne planowanie, a potem mogę pracować nad prototypem grywalnym.

brandon
źródło
1

Wiem, że to pytanie jest już stare, ale pomyślałem, że dodam 2 centy.

Podczas projektowania gier prawie zawsze mam obiekt Game. Jest to jednak bardzo wysoki poziom i sam tak naprawdę „nic nie robi”.

Na przykład nakazuje Renderowanie klasy Graphics, ale nie ma nic wspólnego z procesem renderowania. Może poprosić menedżera poziomów, aby załadował nowy poziom, ale nie ma nic wspólnego z procesem ładowania.

Krótko mówiąc, używam przedmiotu półboga, aby koordynować użycie innych klas.

OMGtechy
źródło
2
To wcale nie jest bardzo boskie - nazywa się to abstrakcją. :)
Vaughan Hilts