Obecnie studiuję tworzenie gier i ćwiczę tworzenie gier.
W swoich grach używam dużo OOP. Na przykład każdy wystrzelony pocisk jest instancją Missile
obiektu i dodany do listy Missile
obiektów. Każdy czołg w grze jest Tank
przedmiotem. Itp.
Na tej podstawie opiera się cała konstrukcja programu. Na przykład posiadanie listy Missile
obiektów pozwala mi przesuwać pociski, rysować je itd., A posiadanie instancji Tank
obiektu dla każdego czołgu pozwala mi sprawdzić, czy każdy czołg koliduje z czymś itp.
Trudno mi sobie wyobrazić, jak można zaprogramować grę (która jest bardziej złożona niż Pac-Man) w języku innym niż OO. (Oczywiście bez lekceważenia programistów spoza OO). Nie tylko pod względem tego, jak długo to potrwa, ale przede wszystkim pod względem tego, jak można zaprojektować grę w ten sposób.
Nie wyobrażam sobie projektowania gry bez użycia programowania obiektowego, ponieważ całe moje rozumienie, jak projektować grę, opiera się na OOP.
Chciałbym zapytać: czy są jakieś gry, które nie są programowane przy użyciu OOP, w sposób podobny do tego, co opisałem powyżej? Czy są jakieś „profesjonalne” gry, w których OOP nie jest głównym czynnikiem w procesie rozwoju?
Jeśli tak, czy mógłbyś mi dać wyobrażenie o tym, jak na przykład można wdrożyć wykrywanie kolizji między czołgiem a liczbą pocisków N bez OOP?
źródło
Odpowiedzi:
Wtedy prawdopodobnie dobrze będzie, jeśli spróbujesz napisać jakieś programy w stylu innym niż OO. Nawet jeśli odkryjesz, że nie jest to dla ciebie pragmatyczne, prawdopodobnie nauczysz się wiele po drodze, co pomoże ci w przyszłości.
Styl OO jest całkiem odpowiedni dla gier, ponieważ prawie zawsze chodzi o manipulowanie stanowymi obiektami. Wiązka laserowa uderza robota, a stan robota zmienia się, a jego tożsamość pozostaje taka sama.
Możliwe jest jednak programowanie gier w funkcjonalnym stylu. W stylu funkcjonalnym stan sam się nie zmienia. Obiekty są niezmienne. Zamiast zmieniać przedmioty, zadajesz pytanie, jak wyglądałby wszechświat, gdybym to zmienił? a następnie stworzyć cały nowy wszechświat, który ma zmienioną właściwość. Oczywiście możesz ponownie wykorzystać wiele z wcześniej istniejącego wszechświata, ponieważ jest on niezmienny .
W programowaniu funkcjonalnym każda funkcja musi obliczyć swoją wartość zwrotną wyłącznie na podstawie przekazanych informacji; nie ma czytania ze „stanu globalnego”.
Jeśli to zrobisz, podstawowym problemem, który będziesz musiał rozwiązać, jest to, że każda aktualizacja nie jest destrukcyjna . Kiedy laser uderza w robota, nie zmieniasz jego stanu. Ostatecznie obliczasz zupełnie nowy wszechświat identyczny ze starym wszechświatem, z tym wyjątkiem, że robot ma inny stan; jeśli potrzebujesz tego starego wszechświata, nadal tam jest, bez zmian.
Ta seria artykułów na blogu zawiera więcej przemyśleń na temat pisania gier w funkcjonalnym stylu:
http://prog21.dadgum.com/23.html
Ten artykuł dotyczy w szczególności pytania „pocisk uderza w czołg”:
http://prog21.dadgum.com/189.html
W rzeczywistości po prostu przeczytaj cały blog. Tam są dobre rzeczy, a artykuły są krótkie.
źródło
Każdy program zorientowany obiektowo można przekształcić w program proceduralny, zastępując wszystkie klasy strukturami i przekształcając wszystkie funkcje składowe w samodzielne funkcje, które przyjmują obiekt
this
za argument.Więc
staje się
lub gdy ta funkcja jest trywialna, wystarczy
Główną różnicą między programowaniem obiektowym a programowaniem proceduralnym jest sposób traktowania danych. W OOP dane są inteligentne . Sam sobie zarządza i manipuluje. Ale w programowaniu proceduralnym dane są głupie . Sam nic nie robi i należy nim manipulować z zewnątrz.
Nawet jeśli rozważasz struktury zbyt obiektowe, możesz zastąpić jedną tablicę struktur wieloma tablicami, jedną dla wszystkiego, co byłoby zmienną pocisku. Więc
staje się
W tym projekcie funkcja, która wykonuje operację obejmującą wiele atrybutów tego samego pocisku, pobierałaby teraz indeks tablicy zamiast odwołania do struktury. Jego implementacja wyglądałaby następująco:
źródło
missile
jest instancją obiektu. W trybie innym niż OOP nie ma żadnych wystąpień, czy mam rację? Jeśli tak, to jak możesz ustawić setMissileVelocity (pocisk, 100)?Missile
, który jest konstrukcją z kilku dziedzin, jakx
,y
,angle
ivelocity
.Robię to w następujący sposób:
this
. Aby wykorzystaćthis
w podejściu innym niż OO, wystarczy przekazać w dowolnym przypadku (patrz następny punkt)this
jako pierwszy parametr.struct
s do swoich funkcji jakothis
, ale uważam, że najlepszym sposobem na osiągnięcie dobrej wydajności pamięci podręcznej dla obiektów, które są płodne, takich jak byty lub cząstki, jest po prostu przekazanie jednego indeksu do kilku tablic prymitywów lub małestruct
s. Tak więc ten indeks jest używany dla każdego pojedynczego elementu danych oryginalnej klasy. Na przykład, jeśli miałbyś...
Zastąpiłbyś to
Aby przekazać funkcję do indeksu, aby uzyskać to, co zwykle byłoby
this
.Pamiętaj, że możesz użyć macierzy pierwotnych jak wyżej lub macierzy
struct
s, w zależności od tego, jak najlepiej przeplatać dane w celu zapewnienia dobrej lokalizacji pamięci podręcznej (lokalizacji odniesienia). Oczywiście, przyzwoita wydajność pamięci podręcznej zależy od znacznie więcej - szczególnie od tego, na jakim języku / platformie piszesz swój kod - ale nawet w opartych na maszynach wirtualnych, dynamicznie przydzielanych językach, takich jak Java, duże tablice liniowe prymitywów mają tendencję do wyświetlać lepszą charakterystykę wydajności niż instancje obiektów. Głównym powodem jest to, że do obiektów można uzyskać dostęp przez odniesienie, co oznacza, że przeskakujesz całą pamięć, aby uzyskać dostęp do danych - nieefektywny w porównaniu do dostępu do operacji podstawowych z dużej tablicy.Aby uzyskać więcej informacji na temat budowania jednostek itp. Jako tablicy struktur lub prymitywów, zobacz Mick West's Evolve your Hierarchy .
źródło
Oprócz istniejących odpowiedzi możesz chcieć wiedzieć, jak robić polimorfizm w języku proceduralnym.
Istnieją dwa podejścia:
Przechowywanie typu
W tym przypadku struct ma pole dla identyfikatora typu, prawdopodobnie wyliczenie, które jest sprawdzane za pomocą
switch
instrukcji, kiedy należy wykonać akcję specyficzną dla typu.Drugi sposób to:
Przechowywanie wskaźników funkcji
Nie wspominałeś, w jakim języku programowania masz doświadczenie, ale w różnych językach są one również nazywane wywołaniami zwrotnymi, delegatami, zdarzeniami lub funkcjami wyższego rzędu lub po prostu obiektami funkcji.
W takim przypadku nie zapiszesz typu, ale zapiszesz wskaźnik / odwołanie do funkcji wykonującej określoną akcję. Gdy trzeba wykonać akcję specyficzną dla typu, wystarczy wywołać tę funkcję. Ten wygląda bardzo podobnie do zwykłych metod wirtualnych.
Umożliwiając niezależne ustawianie każdej funkcji, otrzymujesz wzorzec strategii wolny.
Odnośnie ostatniego akapitu z wykrywaniem kolizji. Myślę, że prawdopodobnie masz wiele rodzajów czołgów i masz wiele rodzajów pocisków, a każda kombinacja może potencjalnie mieć inny wynik, gdy się zderzą. Jeśli tego właśnie szukasz, masz problem, który nie został jeszcze rozwiązany przez nawet języki OOP: wiele metod wysyłania i wiele metod, które mogą mieć wiele
this
parametrów różnych typów. W przypadku tego problemu istnieją jeszcze dwie alternatywy:źródło