Jak bardzo obiektowe są gry wideo? [Zamknięte]

18

Zawsze zastanawiałem się, w jakim stopniu orientacja obiektowa jest wykorzystywana w grach wideo, szczególnie w dużych projektach 3D. Sądzę, że byłoby fajnie, że zarówno trolli werewolfdziedziczone z jego atrybuty enemyi nadpisałeś niektóre z nich. Ale może zajęcia są zbyt ciężkie, aby były praktyczne, nie wiem ...

vemv
źródło
2
W jaki sposób można określić ilościowo (a nawet obiektywnie zakwalifikować) orientację obiektową? Chcesz odpowiedzi w Meyers lub Dahl-Nygaards?
Oczywiście są one tak ciężkie, że język oparty w dużej mierze na OO jest standardem branżowym.
Kaczka komunistyczna
4
Jeśli masz na myśli C ++, został wybrany, ponieważ jest to dobry język do eksploracji bezpiecznego programowania ogólnego w szybki sposób; kilka funkcji OOP jest oczywiście niefortunnym błędnym projektowaniem utrzymywanym wyłącznie dla kompatybilności wstecznej. ;)

Odpowiedzi:

20

Aby przejść do twojego przykładu, wyjaśnię swoje przemyślenia na temat Entites w grach wideo. Nie będę omawiać ogólnej przewagi i wad obiektów i klas, ani ich całej roli w grach wideo.

Podmioty w małych grach wideo są w większości definiowane przez osobne klasy, w dużych grach często są definiowane w plikach. Istnieją dwa ogólne podejścia:

Rozszerzenie : w twoim przykładzie Trolli Werewolfrozszerzy się Enemy, co się wydłuży Entity. Entityzajmuje się podstawowymi rzeczami, takimi jak ruch, zdrowie itp., jednocześnie Enemyokreślając podklasy jako wrogów i robiąc inne rzeczy (Minecraft postępuje zgodnie z tym podejściem).

Oparte na komponentach : Drugie podejście (i moje ulubione) to jedna Entityklasa, która ma komponenty. Składnikiem może być Moveablelub Enemy. Te elementy zajmują się jedną rzeczą, taką jak ruch lub fizyka. Ta metoda przerywa długie łańcuchy rozszerzeń i minimalizuje ryzyko wystąpienia konfliktu. Na przykład: Jak możesz tworzyć nieporuszających się Wrogów, jeśli dziedziczą po ruchomej Postaci? .

W dużych grach wideo, takich jak World of Warcraft lub Starcraft 2, Entities nie są klasami, ale zestawami danych, które określają całą Entity. Skrypty są również ważne, ponieważ definiujesz to, czego Entity nie ma w klasie, ale w skrypcie (jeśli jest unikalny, to nie takie rzeczy jak przenoszenie).

Obecnie programuję RTS i stosuję podejście komponentowe z plikami deskryptorów i skryptów dla Entites, umiejętności, przedmiotów i wszystkiego innego dynamicznego w grze.

Marco
źródło
2
Brzmi interesująco. Jednak nie będę kłamał, nie wiem, co to componentjest a w tym kontekście. Link byłby bardzo mile widziany.
vemv
Komponent to jednostka odpowiedzialna za pojedynczą funkcjonalność. Podmiot oparty na komponencie będzie właścicielem komponentu (w takim przypadku), nie odziedziczy po nim. Własność oznacza, że ​​jednostka może dezaktywować jeden ze swoich składników, zmieniając swoje zachowanie. W klasycznym paradygmacie Extension zachowanie to wynika z „przynależności” do superklasy. Jednostka może być komponentem, a komponent może być modelowany jako klasa lub jako struktura, jeśli wolisz.
FxIII,
Komponent (czasami nazywany systemami Entity) - możesz przeszukiwać ten blog w poszukiwaniu pomysłów t-machine.org Istnieją warianty, ale podstawową ideą jest to, że komponent jest obiektem specjalnego przeznaczenia, a twój aktor łączy ze sobą kilka komponentów, takich jak budowanie modelu z Legos.
Patrick Hughes,
2
Założyłem blog na temat tworzenia gier i napisałem o tym mój pierwszy wpis na blogu. Nic nowego, ale z odrobiną kodu: jagamedev.wordpress.com/2011/06/26/…
Marco
23

Myślę, że fajnie byłoby, gdyby zarówno troll, jak i wilkołak odziedziczyli swoje atrybuty od wroga i nadpisali niektóre z nich.

Niestety to popularne w latach 90. podejście do polimorfizmu okazało się w praktyce złym pomysłem. Wyobraź sobie, że dodajesz „wilka” do listy wrogów - cóż, dzieli on pewne atrybuty z wilkołakiem, więc chciałbyś skonsolidować je we wspólnej klasie bazowej, np. „WolfLike”. Teraz dodajesz „Człowieka” do listy wrogów, ale wilkołaki są czasami ludźmi, więc dzielą też atrybuty, takie jak chodzenie na 2 nogach, zdolność do mówienia itp. Czy tworzysz wspólną „Humanoidalną” bazę dla ludzi i wilkołaków i czy musicie przestać wilkołaki wywodzące się z WolfLike? Czy też mnożymy dziedziczenie po obu - w którym przypadku, który atrybut ma pierwszeństwo, gdy coś w Humanoid koliduje z czymś w WolfLike?

Problem polega na tym, że próba modelowania świata rzeczywistego jako drzewa klas jest nieskuteczna. Weź 3 dowolne rzeczy, a prawdopodobnie znajdziesz 4 różne sposoby na podzielenie ich zachowania na współdzielone i nieudostępnione. Z tego powodu wiele osób zdecydowało się na model modułowy lub oparty na komponentach, w którym obiekt składa się z kilku mniejszych obiektów, które składają się na całość, a mniejsze obiekty można zamieniać lub zmieniać w celu uzyskania pożądanego zachowania. Tutaj możesz mieć tylko 1 klasę wroga, która zawiera podobiekty lub komponenty do tego, jak idzie (np. Bipedal vs. Quadrupedal), jego metody ataku (np. Ugryzienie, pazur, broń, pięść), jego skórę (zielony dla trolli, futrzany dla wilkołaków i wilków) itp.

Jest to nadal całkowicie zorientowane obiektowo, ale pojęcie tego, co jest użytecznym przedmiotem, różni się od tego, co zwykle nauczano w podręcznikach. Zamiast dużego drzewa dziedziczenia różnych klas abstrakcyjnych i kilku konkretnych klas na końcach drzewa, zazwyczaj masz tylko 1 konkretną klasę reprezentującą abstrakcyjne pojęcie (np. „Wróg”), ale która zawiera bardziej konkretne klasy reprezentujące bardziej abstrakcyjne pojęcia (np. ataki, rodzaj skóry). Czasami te drugie klasy można najlepiej wdrożyć poprzez 1 poziom dziedziczenia (np. Podstawowa klasa Ataku i kilka klas pochodnych dla określonych ataków), ale drzewo głębokiego dziedziczenia zniknęło.

Ale może zajęcia są zbyt ciężkie, aby były praktyczne, nie wiem ...

W większości współczesnych języków lekcje nie są „ciężkie”. Są po prostu innym sposobem pisania kodu i zwykle zawierają podejście proceduralne, które i tak napisalibyście, z wyjątkiem łatwiejszej składni. Ale na pewno nie pisz klasy, w której będzie działać funkcja. Klasy nie są z natury lepsze, tylko tam, gdzie ułatwiają zarządzanie lub zrozumienie kodu.

Kylotan
źródło
3
Uwielbiam tę odpowiedź :)
Czarek Tomczak
8

Nie jestem pewien, co rozumiesz przez „zbyt ciężki”, ale C ++ / OOP jest lingua franca w tworzeniu gier z tych samych dobrych powodów, które są używane gdzie indziej.

Mówienie o dmuchaniu w pamięć podręczną jest kwestią projektową, a nie funkcją językową. C ++ nie może być taki zły, ponieważ był używany w grach przez ostatnie 15-20 lat. Java nie może być taka zła, ponieważ jest używana w bardzo ciasnych środowiskach smartfonów.

Przejdźmy teraz do prawdziwego pytania: przez wiele lat hierarchie klasowe pogłębiły się i zaczęły cierpieć z powodu związanych z tym problemów. W późniejszych czasach hierarchie klas uległy spłaszczeniu, a kompozycja i inne projekty oparte na danych są raczej dziedziczeniem niż logiką.

To wszystko OOP i wciąż jest używane jako podstawa przy projektowaniu nowego oprogramowania, po prostu inne podejście do tego, co wcielają klasy.

Patrick Hughes
źródło
2

Klasy nie wiążą się z narzutami w czasie wykonywania. Polimorfizm w czasie wykonywania jest po prostu wywoływany przez wskaźnik funkcji. Te koszty ogólne są niezwykle minimalne w porównaniu z innymi kosztami, takimi jak wywołania losowania, synchronizacja współbieżności, dyskowe operacje we / wy, przełączniki trybu jądra, słaba lokalizacja pamięci, nadmierne wykorzystanie dynamicznej alokacji pamięci, zły wybór algorytmów, użycie języków skryptowych i listy trwa. Istnieje powód, dla którego orientacja obiektowa zasadniczo wyparła to, co było przed nią, a to dlatego, że jest znacznie lepsza.

DeadMG
źródło
Wysyłanie między różnymi typami polimorficznymi odbywa się w sposób, który zapewnia słabą lokalizację pamięci. Nawet w najlżejszych implementacjach, takich jak tabele C ++ lub buforowanie IMP Objective-C, jeśli faktycznie używasz polimorfizmu do radzenia sobie z obiektami różnego typu, wysadzisz swoje pamięci podręczne. Oczywiście, jeśli nie użyjesz polimorfizmu, to vtable pozostanie w twojej pamięci podręcznej lub wiadomość w pamięci podręcznej IMP prowadzi do właściwego miejsca, ale po co to przeszkadzać? A w Javie lub C # koszt jest wyższy, a w JavaScript lub Python wciąż większy.
1
@Joe Wreschnig: Jeśli używasz tych wolniejszych języków, koszt OO jest trywialny w porównaniu z innymi kosztami, takimi jak tłumaczenie ustne / JIT. Co ważniejsze, możesz teoretycznie stworzyć to, czego chcesz na temat złej lokalizacji pamięci, ale faktem jest, że przewidywanie gałęzi, wykonywanie zadań poza kolejnością i podobne funkcje procesora praktycznie całkowicie negują koszty. W przeciwnym razie, dlaczego tak wiele wysokowydajnego oprogramowania jest pisane przy użyciu orientacji obiektowej?
DeadMG
1

Należy pamiętać, że koncepcje kodu obiektowego są często cenniejsze niż ich implementacja w językach. W szczególności konieczność wykonania tysiąca wirtualnych wywołań aktualizacji dla encji w głównej pętli może spowodować bałagan w pamięci podręcznej w C ++ na platformach takich jak 360 lub PS3 - dlatego implementacja może uniknąć „słów kluczowych” wirtualnych, a nawet „klasowych” prędkości.

Często jednak będzie on nadal podążał za koncepcjami OO dziedziczenia i enkapsulacji - po prostu wdrażając je inaczej niż sam język (np. Ciekawie rekurencyjne szablony, kompozycja zamiast dziedziczenia (metoda oparta na komponentach opisana przez Marco)). Jeśli dziedziczenie można zawsze rozwiązać w czasie kompilacji, problemy z wydajnością znikają, ale kod może stać się nieco zawiły dzięki szablonom, niestandardowym implementacjom RTTI (ponownie wbudowane są zwykle niepraktycznie wolne) lub logice czasu kompilacji w makra itp.

W Objective-C na iOS / Mac występują podobne, ale gorsze problemy ze schematem przesyłania wiadomości (nawet w przypadku wymyślnego buforowania) ...

jheriko
źródło
0

Metoda, w której będę próbował użyć dziedziczenia klas i składników. (Po pierwsze, nie robię z Enemy klasy - robię z tego komponent.) Nie podoba mi się pomysł używania jednej ogólnej klasy Entity do wszystkiego, a następnie dodawania niezbędnych komponentów, aby rozwinąć byt. Zamiast tego wolę, aby klasa automatycznie dodawała komponenty (i wartości domyślne) do konstruktora. Następnie podklasy dodają dodatkowe komponenty do konstruktora, aby podklasa miała swoje komponenty i komponenty klasy nadrzędnej. Na przykład klasa broni dodałaby podstawowe komponenty wspólne dla wszystkich broni, a następnie podklasa Miecza dodałaby wszelkie dodatkowe komponenty specyficzne dla mieczy. Nadal zastanawiam się, czy użyć zasobów text / xml do zdefiniowania wartości encji (lub konkretnych mieczy na przykład), lub zrobić to wszystko w kodzie lub w odpowiednim miksie. Nadal pozwala to grze na wykorzystanie informacji o typie i polimorfizmie języka kodowego, a także sprawia, że ​​ObjectFactories jest znacznie prostszy.

Chris
źródło
3
Hej Chris. Dzielenie się doświadczeniami i planami jest świetne, ale tak naprawdę nie odpowiada bezpośrednio na pytanie. Myślę, że pytanie dotyczy bardziej tego, jak te rzeczy są zwykle wykonywane , gdy odpowiadasz, jak to planujesz . Są to podobne pytania, ale nie takie same.
MichaelHouse