W komentarzach zamieściłem skróconą wersję mojej starej odpowiedzi:
Użycie DrawableGameComponent
powoduje 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ą Update
metodę:
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 Update
połą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 tag
parametr 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 DrawableGameComponent
zapewnia:
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 DrawableGameComponent
do 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ół DrawableGameComponent
trasy, 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 ( Services
popularne 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”, DrawableGameComponent
aby 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 DrawableGameComponent
oferuje. 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 DrawableGameComponent
i 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 DrawOrder
i 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 gameTime
lub potrzebuję więcej parametrów (lub całego UpdateContext
obiektu 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 DrawableGameComponent
kodu. 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
.)