W każdej grze, którą stworzyłem, po prostu umieszczam wszystkie moje obiekty gry (pociski, samochody, graczy) na jednej liście tablic, którą przeglądam w celu losowania i aktualizacji. Kod aktualizacji dla każdej jednostki jest przechowywany w jej klasie.
Zastanawiam się, czy to właściwy sposób na załatwienie różnych spraw, czy szybszy i bardziej wydajny sposób?
software-engineering
Derek
źródło
źródło
Odpowiedzi:
Nie ma jednej właściwej drogi. To, co opisujesz, działa i jest prawdopodobnie wystarczająco szybkie, aby nie miało znaczenia, ponieważ wydajesz się pytać, ponieważ martwisz się projektem, a nie dlatego, że spowodowało to problemy z wydajnością. Jest to więc właściwy sposób.
Z pewnością można to zrobić inaczej, na przykład utrzymując jednorodną listę dla każdego typu obiektu lub upewniając się, że wszystko na liście heterogenicznej jest zgrupowane, dzięki czemu poprawiono spójność kodu w pamięci podręcznej lub zezwala się na równoległe aktualizacje. Trudno powiedzieć, czy zauważysz zauważalną poprawę wydajności, nie znając zakresu i skali gier.
Możesz także dodatkowo rozdzielić obowiązki swoich podmiotów - brzmi to tak, jakby podmioty zarówno aktualizowały się, jak i renderowały w tej chwili - aby lepiej postępować zgodnie z zasadą pojedynczej odpowiedzialności. Może to zwiększyć łatwość konserwacji i elastyczność twoich indywidualnych interfejsów poprzez oddzielenie ich od siebie. Ale znowu, czy dostrzegasz korzyść z dodatkowej pracy, która pociąga za sobą, trudno mi powiedzieć, wiedząc, co wiem o twoich grach (co w zasadzie jest niczym).
Radziłbym nie kłaść na to zbyt dużego nacisku i trzymać się z tyłu głowy, że może to być potencjalny obszar do poprawy, jeśli kiedykolwiek zauważysz problemy z wydajnością lub utrzymaniem.
źródło
Jak powiedzieli inni, jeśli jest wystarczająco szybki, to jest wystarczająco szybki. Jeśli zastanawiasz się, czy istnieje lepszy sposób, zadaj sobie pytanie
Czy czuję, że iteruję wszystkie obiekty, ale działam tylko na ich podzbiorze?
Jeśli tak, oznacza to, że możesz chcieć mieć wiele list zawierających tylko odpowiednie odniesienia. Na przykład, jeśli zapętlasz każdy obiekt gry, aby je renderować, ale trzeba tylko renderować podzbiór obiektów, warto zachować osobną listę do renderowania tylko dla tych obiektów, które muszą być renderowane.
Zmniejszyłoby to liczbę obiektów, przez które przechodzi się pętlę i pomija niepotrzebne kontrole, ponieważ wiadomo już, że wszystkie obiekty na liście mają zostać przetworzone.
Musisz go profilować, aby zobaczyć, czy osiągasz znaczny wzrost wydajności.
źródło
Zależy to prawie całkowicie od konkretnych potrzeb gry . Może działać idealnie w prostej grze, ale zawieść w bardziej złożonym systemie. Jeśli działa w twojej grze, nie przejmuj się nią lub staraj się ją przerobić, aż pojawi się taka potrzeba.
Jeśli chodzi o to, dlaczego proste podejście może się nie udać w niektórych sytuacjach, trudno podsumować to w jednym poście, ponieważ możliwości są nieograniczone i zależą wyłącznie od samych gier. Inne odpowiedzi wspominały na przykład, że możesz zgrupować obiekty współdzielące obowiązki razem na osobnej liście.
To jedna z najczęstszych zmian, które możesz wprowadzać w zależności od projektu gry. Zaryzykuję i opiszę kilka innych (bardziej złożonych) przykładów, ale pamiętaj, że wciąż istnieje wiele innych możliwych przyczyn i rozwiązań.
Na początek zwrócę uwagę, że aktualizacja obiektów gry i ich renderowanie może mieć różne wymagania w niektórych grach, dlatego też należy się z nimi obchodzić osobno. Niektóre możliwe problemy z aktualizacją i renderowaniem obiektów gry:
Aktualizowanie obiektów gry
Oto fragment architektury Game Engine Architecture, który polecam przeczytać:
Innymi słowy, w niektórych grach obiekty mogą zależeć od siebie i wymagać określonej kolejności aktualizacji. W tym scenariuszu może być konieczne opracowanie bardziej złożonej struktury niż lista do przechowywania obiektów i ich wzajemnych zależności.
Renderowanie obiektów gry
W niektórych grach sceny i obiekty tworzą hierarchię węzłów nadrzędny-podrzędny i powinny być renderowane we właściwej kolejności oraz z transformacjami względem ich rodziców. W takich przypadkach możesz potrzebować bardziej złożonej struktury, takiej jak wykres sceny (struktura drzewa) zamiast pojedynczej listy:
Innymi przyczynami mogą być na przykład użycie struktury danych partycjonowania przestrzennego w celu uporządkowania obiektów w sposób, który poprawia wydajność wykonywania wygładzania fragmentów widoku.
źródło
Odpowiedź brzmi „tak, w porządku”. Kiedy pracowałem nad dużymi symulacjami żelaza, w których wydajności nie można było negocjować, zdziwiłem się, widząc wszystko w jednym dużym globalnym układzie (BIG i FAT). Później pracowałem nad systemem OO i musiałem porównać oba. Chociaż było to bardzo brzydkie, wersja „jedna tablica do rządzenia nimi wszystkimi” w prostym, pojedynczym wątku starego C skompilowanego podczas debugowania była kilka razy szybsza niż „ładny” wielowątkowy system OO skompilowany -O2 w C ++. Myślę, że trochę miało to związek z doskonałą lokalizacją pamięci podręcznej (zarówno w przestrzeni, jak i czasie) w wersji z dużą ilością tablic.
Jeśli chcesz wydajności, górną granicą będzie jedna wielka globalna tablica C (z doświadczenia).
źródło
Zasadniczo to, co chcesz zrobić, to tworzyć listy w miarę potrzeb. Jeśli przerysujesz obiekty terenu za jednym razem, przydatna może być ich lista. Ale żeby było to warte czasu, potrzebowałbyś dziesiątków tysięcy z nich i wielu tysięcy rzeczy, które nie są terenem. W przeciwnym razie przeglądanie listy wszystkiego i używanie „if” do wyciągania obiektów terenowych jest wystarczająco szybkie.
Zawsze potrzebowałem jednej głównej listy, bez względu na to, ile innych list miałem. Zwykle robię z tego mapę, tablicę z kluczami lub słownik, aby mieć swobodny dostęp do poszczególnych elementów. Ale prosta lista jest o wiele prostsza; jeśli nie masz dostępu do obiektów gry losowo, nie potrzebujesz mapy. A okazjonalne sekwencyjne przeszukiwanie kilku tysięcy wpisów w celu znalezienia właściwego nie zajmie długo. Powiedziałbym więc, że cokolwiek jeszcze zrobicie, planujcie trzymać się głównej listy. Rozważ użycie mapy, jeśli stanie się duża. (Chociaż jeśli możesz używać indeksów zamiast kluczy, możesz trzymać się tablic i mieć coś znacznie lepszego niż Mapa. Zawsze kończyłem się dużymi przerwami między liczbami indeksów, więc musiałem się poddać).
Słowo o tablicach: są niewiarygodnie szybkie. Ale kiedy wstają z około 100 000 elementów, zaczynają pasować do Śmieciarzy. Jeśli możesz przydzielić przestrzeń raz, a potem jej nie dotykać, dobrze. Ale jeśli stale powiększasz tablicę, stale przydzielasz i zwalniasz duże fragmenty pamięci i możesz zawiesić grę przez kilka sekund, a nawet zawieść z powodu błędu pamięci.
Czyli szybszy i bardziej wydajny? Pewnie. Mapa losowego dostępu. W przypadku naprawdę dużych list lista połączona pobije tablicę, jeśli i kiedy nie potrzebujesz dostępu losowego. Wiele map / list do organizowania obiektów na różne sposoby, w zależności od potrzeb. Możesz zaktualizować aktualizację wielowątkową.
Ale to dużo pracy. Ta pojedyncza tablica jest szybka i prosta. Możesz dodawać inne listy i rzeczy tylko w razie potrzeby i prawdopodobnie nie będziesz ich potrzebować. Pamiętaj tylko, co może pójść nie tak, jeśli Twoja gra stanie się duża. Możesz mieć wersję testową z 10-krotnie większą liczbą obiektów niż w wersji rzeczywistej, aby dać Ci wyobrażenie, kiedy zmierzasz do kłopotów. (Rób to tylko wtedy, gdy gra staje się duża.) (I nie próbuj wielowątkowości bez zastanowienia się. Wszystko inne jest łatwe do rozpoczęcia i możesz zrobić tyle, ile chcesz. i wycofaj się w dowolnym momencie. Dzięki wielowątkowości zapłacisz wysoką cenę za wejście, opłaty za pozostanie w są wysokie i trudno jest się wycofać.)
Innymi słowy, po prostu rób to, co robisz. Twoim największym limitem jest prawdopodobnie twój własny czas, który najlepiej wykorzystujesz.
źródło
Użyłem nieco podobnej metody do prostych gier. Przechowywanie wszystkiego w jednej tablicy jest bardzo przydatne, gdy spełnione są następujące warunki:
W każdym razie zaimplementowałem to rozwiązanie jako tablicę o stałej wielkości, która zawiera
gameObject_ptr
strukturę. Struktura ta zawierała wskaźnik do samego obiektu gry oraz wartość logiczną wskazującą, czy obiekt był „żywy”.Wartość logiczna ma na celu przyspieszenie operacji tworzenia i usuwania. Zamiast niszczyć obiekty gry po ich usunięciu z symulacji, po prostu ustawiłbym flagę „żywy” na
false
.Podczas wykonywania obliczeń na obiektach kod przechodzi przez pętlę, wykonując obliczenia na obiektach oznaczonych jako „żywe” i renderowane są tylko obiekty oznaczone jako „żywe”.
Inną prostą optymalizacją jest uporządkowanie tablicy według typu obiektu. Na przykład wszystkie pociski mogą znajdować się w ostatnich n komórkach tablicy. Następnie, przechowując liczbę całkowitą z wartością indeksu lub wskaźnika do elementu tablicy, można odwoływać się do określonych typów przy obniżonym koszcie.
Nie trzeba dodawać, że to rozwiązanie nie jest dobre w przypadku gier z dużą ilością danych, ponieważ tablica będzie niedopuszczalnie duża. Nie nadaje się również do gier z ograniczeniami pamięci, które zabraniają pamięci nieużywanym obiektom. Najważniejsze jest to, że jeśli masz znany górny limit liczby obiektów w grze, które będziesz miał w danym momencie, nie ma nic złego w przechowywaniu ich w statycznej tablicy.
To jest mój pierwszy post! Mam nadzieję, że odpowie na twoje pytanie!
źródło
Jest to do przyjęcia, choć niezwykłe. Terminy takie jak „szybszy” i „bardziej wydajny” są względne; zazwyczaj organizujesz obiekty gry w osobne kategorie (więc jedna lista dla pocisków, jedna lista dla wrogów itp.), aby programowanie było lepiej zorganizowane (a przez to bardziej wydajne), ale to nie jest tak, że komputer szybciej zapętli wszystkie obiekty .
źródło
Mówisz pojedynczą tablicę i obiekty.
Więc (bez twojego kodu, żeby się przyjrzeć) Domyślam się, że wszystkie są powiązane z „uberGameObject”.
Więc może dwa problemy.
1) Możesz mieć obiekt „boga” - robi tony rzeczy, nie tak naprawdę podzielonych według tego, co robi lub czym jest.
2) Powtarzasz tę listę i przesyłasz tekst? lub rozpakowanie każdego. Ma to duży wpływ na wydajność.
rozważ rozbicie różnych typów (pociski, złoczyńcy itp.) na ich własne, mocno wpisane listy.
źródło
update()
).Mogę zaproponować kompromis między ogólną pojedynczą listą a osobnymi listami dla każdego typu obiektu.
Zachowaj odniesienie do jednego obiektu na wielu listach. Te listy są zdefiniowane przez podstawowe zachowania. Na przykład,
MarineSoldier
obiekt zostanie wymieniony wPhysicalObjList
,GraphicalObjList
,UserControlObjList
,HumanObjList
. Tak więc, kiedy przetwarzasz fizykę, przechodzisz iteracjęPhysicalObjList
, kiedy proces uszkadzający truciznę -HumanObjList
jeśli Żołnierz umrze - usuń ją,HumanObjList
ale trzymaj sięPhysicalObjList
. Aby ten mechanizm działał, musisz zorganizować automatyczne wstawianie / usuwanie obiektu podczas jego tworzenia / usuwania.Zalety tego podejścia:
AllObjectsList
z rzutowaniem przy aktualizacji)MegaAirHumanUnderwaterObject
zostałyby wstawione do odpowiednich list zamiast tworzenia nowej listy dla tego typu)PS: Jeśli chodzi o samodzielną aktualizację, napotkasz problem, gdy wiele obiektów wchodzi w interakcje, a każdy z nich zależy od innych. Aby rozwiązać ten problem, musimy wpływać na obiekt zewnętrznie, a nie w ramach własnej aktualizacji.
źródło