W komentarzach zamieściłem skróconą wersję mojej starej odpowiedzi:
Użycie DrawableGameComponentpowoduje zablokowanie jednego zestawu sygnatur metod i określonego zachowanego modelu danych. Zazwyczaj są to początkowo zły wybór, a blokada znacznie utrudnia ewolucję kodu w przyszłości.
W tym miejscu postaram się zilustrować, dlaczego tak jest, zamieszczając kod z mojego obecnego projektu.
Obiekt główny utrzymujący stan świata gry ma następującą Updatemetodę:
void Update(UpdateContext updateContext, MultiInputState currentInput)
Istnieje wiele punktów wejścia do Update, w zależności od tego, czy gra działa w sieci, czy nie. Możliwe jest na przykład przywrócenie stanu gry do poprzedniego punktu w czasie, a następnie ponowna symulacja.
Istnieją również dodatkowe metody podobne do aktualizacji, takie jak:
void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)
W stanie gry znajduje się lista aktorów do aktualizacji. Ale to coś więcej niż zwykłe Updatepołączenie. Istnieją dodatkowe przejścia przed i po zajęciach z fizyki. Jest też kilka fantazyjnych rzeczy do odrodzenia odradzania / niszczenia aktorów, a także przejścia poziomów.
Poza stanem gry istnieje interfejs użytkownika, który musi być zarządzany niezależnie. Ale niektóre ekrany w interfejsie użytkownika muszą także działać w kontekście sieci.
Do rysowania, ze względu na charakter gry, istnieje niestandardowy system sortowania i usuwania, który pobiera dane wejściowe z tych metod:
virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)
A potem, gdy zorientuje się, co narysować, automatycznie wywołuje:
virtual void Draw(DrawContext drawContext, int tag)
Ten tagparametr jest wymagany, ponieważ niektórzy aktorzy mogą trzymać się innych aktorów - i dlatego muszą mieć możliwość rysowania zarówno powyżej, jak i poniżej trzymanego aktora. System sortowania zapewnia, że dzieje się to we właściwej kolejności.
Do rysowania jest także system menu. I hierarchiczny system do rysowania interfejsu użytkownika, wokół interfejsu, wokół gry.
Teraz porównaj te wymagania z tym, co DrawableGameComponentzapewnia:
public class DrawableGameComponent
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
public int UpdateOrder { get; set; }
public int DrawOrder { get; set; }
}
Nie ma rozsądnego sposobu na przejście z systemu DrawableGameComponentdo systemu, który opisałem powyżej. (I nie są to nietypowe wymagania dla gry o nawet niewielkiej złożoności).
Należy również zauważyć, że wymagania te nie pojawiły się po prostu na początku projektu. Ewoluowały one przez wiele miesięcy prac rozwojowych.
To, co zazwyczaj robią ludzie, jeśli zaczynają w dół DrawableGameComponenttrasy, to zaczynają budować na hakach:
Zamiast dodawać metody, robią brzydkie rzucanie w dół do własnego typu podstawowego (dlaczego po prostu nie użyć tego bezpośrednio?).
Zamiast zastępować kod do sortowania i wywoływania Draw/ Update, robią bardzo powolne rzeczy, aby zmieniać numery porządkowe w locie.
A co najgorsze: zamiast dodawać parametry do metod, zaczynają „przekazywać” „parametry” w lokalizacjach pamięci, które nie są stosem ( Servicespopularne jest użycie do tego celu). Jest to skomplikowane, podatne na błędy, powolne, delikatne, rzadko bezpieczne dla wątków i może stać się problemem, jeśli dodasz sieć, stworzysz narzędzia zewnętrzne i tak dalej.
To również sprawia, ponowne wykorzystanie kodu trudniej , bo teraz twoje Zależności są ukryte wewnątrz „składnik”, zamiast opublikowany na granicy API.
Lub prawie widzą, jak szalone to wszystko jest i zaczynają robić „menedżerów”, DrawableGameComponentaby obejść te problemy. Tyle że nadal mają te problemy na granicach „menedżera”.
Niezmiennie tych „menedżerów” można łatwo zastąpić serią wywołań metod w pożądanej kolejności. Wszystko inne jest zbyt skomplikowane.
Oczywiście, po uruchomieniu zatrudniających te hacki, to wtedy trwa prawdziwa praca, aby cofnąć te hacki raz zdajesz sobie sprawę, trzeba więcej niż to, co DrawableGameComponentoferuje. Stajesz się „ zamknięty ”. Pokusa często polega na tym, aby nadal nakładać się na hacki - co w praktyce cię ugrzęźnie i ograniczy rodzaje gier, które możesz stworzyć do stosunkowo uproszczonych.
Wydaje się, że wiele osób osiąga ten punkt i odczuwa trzewną reakcję - być może dlatego, że używa DrawableGameComponenti nie chce zaakceptować, że jest „zły”. Trzymają się więc faktu, że można przynajmniej „ sprawić” , że będzie to możliwe .
Oczywiście można to zrobić do „pracy”! Jesteśmy programistami - sprawianie, by rzeczy działały pod nietypowymi ograniczeniami, jest praktycznie naszą pracą. Ale to ograniczenie nie jest konieczne, użyteczne ani racjonalne ...
Co jest szczególnie flabbergasting o to, że jest zdumiewająco trywialny bocznym kroku w tym cały problem. Tutaj dam ci nawet kod:
public class Actor
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
}
List<Actor> actors = new List<Actor>();
foreach(var actor in actors)
actor.Update(gameTime);
foreach(var actor in actors)
actor.Draw(gameTime);
(Łatwo jest dodać sortowanie według DrawOrderi UpdateOrder. Jednym prostym sposobem jest utrzymanie dwóch list i używanie List<T>.Sort(). Jest także banalne w obsłudze Load/ UnloadContent.)
Nie jest ważne, czym tak naprawdę jest ten kod . Ważne jest to, że mogę go trywialnie modyfikować .
Kiedy zdaję sobie sprawę, że potrzebuję innego systemu pomiaru czasu gameTimelub potrzebuję więcej parametrów (lub całego UpdateContextobiektu zawierającego około 15 rzeczy), albo potrzebuję zupełnie innej kolejności operacji do rysowania, albo potrzebuję dodatkowych metod w przypadku różnych przepustek aktualizacji mogę po prostu to zrobić .
I mogę to zrobić natychmiast. Nie muszę się martwić wybraniem DrawableGameComponentkodu. Lub gorzej: zhakuj go w taki sposób, że będzie to jeszcze trudniejsze, gdy pojawi się taka potrzeba.
Jest naprawdę tylko jeden scenariusz, w którym jest to dobry wybór DrawableGameComponent: i to jest, jeśli dosłownie tworzysz i publikujesz oprogramowanie pośredniczące typu X przeciągnij i upuść . Co było jego pierwotnym celem. Co - dodam - nikt nie robi!
(Podobnie, naprawdę warto używać go tylkoServices przy tworzeniu niestandardowych typów treści ContentManager.)