Wiem, że singletony są złe, mój stary silnik gry używał singletonowego obiektu „Game”, który obsługuje wszystko, od przechowywania wszystkich danych do rzeczywistej pętli gry. Teraz robię nowy.
Problem polega na tym, aby narysować coś w SFML, którego używasz window.draw(sprite)
tam, gdzie jest okno sf::RenderWindow
. Widzę tutaj 2 opcje:
- Zrób singletonowy obiekt gry, który pobiera każdy byt w grze (co użyłem wcześniej)
- Uczyń to konstruktorem dla encji:
Entity(x, y, window, view, ...etc)
(to jest po prostu śmieszne i denerwujące)
Jaki byłby właściwy sposób, aby to zrobić, utrzymując konstruktor encji tylko na x i y?
Mógłbym spróbować śledzić wszystko, co robię w głównej pętli gry, i po prostu ręcznie narysować ich duszka w pętli gry, ale to też wydaje się niechlujne, a także chcę absolutnej pełnej kontroli nad całą funkcją losowania dla bytu.
c++
design-patterns
oop
Akumulator
źródło
źródło
Odpowiedzi:
Przechowuj tylko dane potrzebne do wyrenderowania duszka wewnątrz każdej encji, a następnie pobierz je z encji i przekaż do okna do renderowania. Nie trzeba przechowywać żadnych okien ani przeglądać danych w jednostkach.
Można mieć najwyższego poziomu gry lub silnika klasy, która trzyma poziom klasy (posiada wszystkie podmioty obecnie stosowane), a Renderer klasa (zawiera okna widok i niczego innego do renderowania).
Tak więc pętla aktualizacji gry w klasie najwyższego poziomu może wyglądać następująco:
źródło
Logger::getInstance().Log(...)
zamiast po prostuLog(...)
? Po co inicjować klasę losowo, gdy zapyta się, czy można to zrobić ręcznie tylko raz? Globalna funkcja odwołująca się do globalnych danych statycznych jest o wiele prostsza do utworzenia i użycia.Proste podejście polega na stworzeniu czegoś, co kiedyś było
Singleton<T>
globalneT
. Globały też mają problemy, ale nie stanowią one dodatkowej pracy i kodu, aby wymusić trywialne ograniczenie. Jest to w zasadzie jedyne rozwiązanie, które nie będzie wymagało (potencjalnie) dotykania konstruktora encji.Trudniejszym, ale być może lepszym podejściem jest przekazanie swoich zależności tam, gdzie ich potrzebujesz . Tak, może to obejmować przekazanie
Window *
zbioru obiektów (takich jak Twoja istota) w sposób, który wygląda obrzydliwie. Fakt, że wygląda obrzydliwie, powinien ci coś powiedzieć: twój projekt może być obrzydliwy.Powodem jest to, że jest to trudniejsze (poza włączeniem większego pisania), ponieważ często prowadzi to do refaktoryzacji interfejsów, tak że to, co „musisz” przekazać, jest potrzebne mniejszej liczbie klas na poziomie liści. Powoduje to, że wiele brzydoty nieodłącznie związanej z przekazywaniem renderera do wszystkiego zniknie, a także poprawia ogólną łatwość konserwacji twojego kodu poprzez zmniejszenie ilości zależności i sprzężeń, których zakres stałeś się bardzo oczywisty, przyjmując zależności jako parametry . Kiedy zależności były singletonami lub globałami, mniej oczywiste było, w jaki sposób wzajemnie powiązane były twoje systemy.
Ale jest to potencjalnie duże przedsięwzięcie. Robienie tego do systemu po fakcie może być wręcz bolesne. Może być o wiele bardziej pragmatyczne, aby po prostu zostawić swój system samemu sobie, z singletonem, na razie (szczególnie jeśli próbujesz faktycznie wysłać grę, która w przeciwnym razie działa dobrze; gracze na ogół nie będą się przejmować, jeśli masz singleton lub cztery tam).
Jeśli chcesz spróbować zrobić to z istniejącym projektem, może być konieczne opublikowanie o wiele więcej szczegółów na temat bieżącej implementacji, ponieważ tak naprawdę nie ma ogólnej listy kontrolnej do wprowadzenia tych zmian. Lub przyjdź i porozmawiaj o tym na czacie .
Z tego, co napisałeś, uważam, że dużym krokiem w kierunku „bez singletonu” byłoby uniknięcie konieczności dostępu twoich bytów do okna lub widoku. Sugeruje to, że same się rysują, a ty nie musisz, by istoty same się rysowały . Możesz przyjąć metodologię, w której podmioty zawierają po prostu informacje, które by na to pozwoliłydo narysowania przez jakiś system zewnętrzny (który ma okno i odnośniki do widoku). Istota po prostu ujawnia swoją pozycję i duszka, którego powinien użyć (lub jakieś odniesienie do tego duszka, jeśli chcesz buforować rzeczywiste duszki w samym module renderującym, aby uniknąć powielania instancji). Mechanizm renderujący jest po prostu proszony o narysowanie konkretnej listy encji, przez które przechodzi przez pętlę, odczytuje dane i używa wewnętrznego obiektu okna do wywołania
draw
duszka wyszukanego encji.źródło
Dziedzicz z sf :: RenderWindow
SFML faktycznie zachęca do dziedziczenia po klasach.
Stąd tworzysz funkcje rysowania elementów dla elementów rysujących.
Teraz możesz to zrobić:
Możesz nawet zrobić to o krok dalej, jeśli Twoje Entity będą przechowywać własne unikalne duszki, sprawiając, że Entity będą dziedziczyć po sf :: Sprite.
Teraz
sf::RenderWindow
mogą po prostu rysować encje, a encje mają teraz funkcje takie jaksetTexture()
isetColor()
. Istota może nawet używać pozycji duszka jako własnej pozycji, co pozwala używaćsetPosition()
funkcji zarówno do poruszania Jednostki, jak i jej duszka.W końcu jest całkiem fajnie, jeśli masz tylko:
Poniżej kilka szybkich przykładowych implementacji
LUB
źródło
Unikasz singletonów w rozwoju gier w taki sam sposób, w jaki unikasz ich w każdym innym oprogramowaniu: przechodzisz zależności .
Z tym z drogi, można wybrać przekazać zależności bezpośrednio jako nagie typów (jak
int
,Window*
itp) lub możesz przekazać je w jednym lub więcej typów niestandardowych otoki (jakEntityInitializationOptions
).Ten pierwszy sposób może być irytujący (jak się dowiedziałeś), podczas gdy ten drugi pozwoli ci przekazać wszystko w jednym obiekcie i zmodyfikować pola (a nawet specjalizować typ opcji) bez konieczności zmieniania konstruktora encji. Myślę, że ten drugi sposób jest lepszy.
źródło
Singletony nie są złe. Zamiast tego są łatwe do nadużyć. Z drugiej strony globale są jeszcze łatwiejsze do nadużyć i mają wiele innych problemów.
Jedynym uzasadnionym powodem zastąpienia singletona globalnym jest uspokojenie religijnych hejterów singletonów.
Problemem jest projekt, który obejmuje klasy, w których istnieje tylko jedna globalna instancja, i które muszą być dostępne z każdego miejsca. To się rozpada, gdy tylko pojawi się wiele wystąpień singletonu, na przykład w grze, gdy wdrażasz podzielony ekran, lub w wystarczająco dużej aplikacji dla przedsiębiorstw, gdy zauważysz, że pojedynczy rejestrator nie zawsze jest tak świetnym pomysłem .
Podsumowując, jeśli naprawdę masz klasę, w której masz jedną globalną instancję, której nie można rozsądnie przekazać przez odniesienie , singleton jest często jednym z lepszych rozwiązań w puli rozwiązań nieoptymalnych.
źródło
Wstrzykiwać zależności. Zaletą tego jest to, że możesz teraz tworzyć różne typy tych zależności za pośrednictwem fabryki. Niestety wyrywanie singletonów z klasy, która ich używa, przypomina ciągnięcie kota za tylne nogi po dywanie. Ale jeśli je wstrzykujesz, możesz wymieniać implementacje, być może w locie.
Teraz możesz wstrzykiwać różne rodzaje okien. Umożliwia to pisanie testów na RenderSystem z różnymi typami okien, dzięki czemu można zobaczyć, jak RenderSystem się zepsuje lub wykona. Nie jest to możliwe ani trudniejsze, jeśli używasz singletonów bezpośrednio w „RenderSystem”.
Teraz jest bardziej testowalny, modułowy, a także oddzielony od konkretnej implementacji. To zależy tylko od interfejsu, a nie konkretnej implementacji.
źródło