Natknąłem się na to pytanie, projektując grę wideo w języku C #.
Jeśli weźmiemy pod uwagę gry takie jak Battlefield lub Call of Duty , setki, a nawet tysiące pocisków lecą w tym samym czasie. Zdarzenia są uruchamiane w sposób ciągły, a z tego, co wiem, pochłania to dużą moc obliczeniową… czy to prawda? Chcę wiedzieć, jak różni twórcy gier zarządzają pociskami (2D i 3D) i jaka jest najbardziej wydajna metoda dla każdego z nich.
Czytam pytanie W jaki sposób pociski są symulowane w grach wideo? ale nie wpływa to na działanie pocisków z perspektywy projektowania programu.
Miałem kilka pomysłów, ale każdy ma swoje wady:
Najbardziej wydajna metoda, o której mogłem pomyśleć (w przypadku gier 2D):
Powiedzmy, że miałem stworzyć klasę o nazwie Bullet, i jak długo użytkownik przytrzymuje przycisk, co 0,01 sekundy powstaje obiekt Bullet. Ten punkt ma:
1 prędkość
2 Pozycja początkowa miejsca, z którego jest strzelany
3 Struktura duszka
4 Efekt przy trafieniu
Ponieważ kula byłaby własną klasą, mogłaby sama zarządzać słuchaczami rysowania, poruszania się i akcji.
Czy procesorowi nie byłoby trudno przetworzyć tysiące takich obiektów, które są tworzone, a następnie niszczone (gdy wyzwalany jest efekt przy trafieniu)? Miejsce w pamięci RAM?
Wydajna metoda gier 3D - Inną myślą, którą miałem, było:
Powiedzmy, że tworzę klasę broni. Ta broń ma różne funkcje, z których niektóre:
1 Wykryj, dokąd celuje broń, i sprawdź, czy patrzy na cel
2 Uruchom animację strzelania z pistoletu
3 Ma metodę doDamage (), która wskazuje coś do odjęcia zdrowia od tego, na co celowana jest broń
4 Powiadamia klasę animacji pocisku po naciśnięciu przycisku
Mógłbym wtedy stworzyć klasę statyczną, powiedzmy BulletAnimation, która mogłaby otrzymać powiadomienie z miejsca, w którym uruchomiła się broń, gdzie jest ona skierowana (do celu pocisku) oraz informacje na temat odpowiedniego duszka i prędkości do użycia dla pocisku . Ta klasa następnie rysuje duszki (może na nowym wątku, idk) na podstawie obu pozycji i pożądanego duszka, aby zasymulować wystrzelenie pocisku z pistoletu.
Ta ostatnia wydaje się o wiele trudniejsza do kodowania i czy nie wymagałoby to dużej mocy obliczeniowej, aby stale wywoływać statyczne, aby zrobić to dla tysięcy pocisków jednocześnie? Trudno byłoby również uzyskać ciągłe aktualizacje pozycji początkowej i końcowej.
Moje pytanie brzmi: jaki jest najbardziej efektywny sposób, w jaki robią to twórcy gier? Czy ta metoda zmienia się z gier 2D na 3D?
pew-pew-pew
technologii :)Odpowiedzi:
Z pewnością rozumiem, dlaczego uważasz, że tak trudno byłoby je zasymulować, ale pociski (wszystkie pociski, naprawdę) są wystarczająco ograniczone, aby je ułatwić.
Na ogół są one symulowane jako pojedynczy punkt, a nie jako coś o objętości. Dzięki temu wykrywanie kolizji jest znacznie łatwiejsze, ponieważ teraz muszę tylko wykonywać kolizje z bardzo prostymi powierzchniami, takimi jak linia z okręgiem.
Wiemy, jak się przeprowadzą, więc nie ma zbyt wielu informacji, które musimy przechowywać lub obliczać. Twoja lista była dość dokładna, na ogół będziemy mieć z nią jeszcze kilka rzeczy, na przykład, kto strzelił kulą i jaki jest jej typ.
Ponieważ wszystkie pociski będą bardzo podobne, możemy je wstępnie przydzielić, aby uniknąć całego narzutu związanego z ich tworzeniem. Mogę przydzielić tablicę 1000 pocisków, a teraz można uzyskać do nich dostęp tylko za pomocą indeksu, a wszystkie są sekwencyjne w pamięci, więc ich przetwarzanie będzie szybkie.
Mają stały czas życia / zasięg, dzięki czemu mogę szybko wygasać stare pociski i bardzo szybko przetwarzać pamięć na nowe.
Gdy coś trafią, mogę je również wygasnąć, aby miały skończone życie.
Ponieważ wiemy, kiedy zostały utworzone, jeśli potrzebujemy nowych i nie mamy wolnych na naszej wstępnie przydzielonej liście, mogę po prostu złapać najstarsze i poddać je recyklingowi, a ludzie nie zauważą, że kule wygasną nieco wcześniej .
Są one renderowane jako modele (zwykle) lub jako modele o niskiej rozdzielczości i zajmują bardzo mało miejsca na ekranie, więc można je szybko renderować.
Biorąc to wszystko pod uwagę, pociski wydają się być stosunkowo tanie. Jeśli nasz budżet zostanie kiedykolwiek pochłonięty przez pociski i ich renderowanie, zwykle po prostu przeprojektujemy go, aby ograniczyć liczbę strzałów, które można oddać na raz (zobaczysz to w wielu starych grach zręcznościowych), używaj broni z wiązkami, które poruszają się natychmiast lub spowolnij szybkostrzelność, aby upewnić się, że nie przekraczamy budżetu.
źródło
num_characters * max_bullets_per_character
Prawdopodobnie jednym z najbardziej wydajnych sposobów wdrażania pocisków jest użycie tak zwanego hitcan . Jego implementacja jest raczej prosta - kiedy strzelasz, sprawdzasz, do czego celuje broń (być może używasz promienia, aby znaleźć najbliższy byt / obiekt / siatkę), a następnie „uderzasz” go, zadając obrażenia. Jeśli chcesz sprawić, by wyglądało to bardziej jak faktyczna, szybko poruszająca się niewidzialna kula została wystrzelona, możesz ją sfałszować, dodając niewielkie opóźnienie zależne od odległości przed spowodowaniem obrażeń.
Podejście to zasadniczo zakłada, że wystrzelony pocisk ma nieskończoną prędkość i jest zwykle stosowany do rodzajów broni, takich jak lasery i wiązki cząstek / armaty i może niektóre formy karabinów snajperskich .
Kolejnym podejściem byłoby modelowanie wystrzelonej kuli jako pocisku, który jest modelowany jako jego własny byt / obiekt, który podlega zderzeniu, i ewentualnie grawitacja i / lub opór powietrza, który zmienia jego kierunek i prędkość. Jest to bardziej złożone niż podejście hitcan ze względu na dodatkowe równania fizyki i bardziej wymagające zasobów, ponieważ istnieje rzeczywisty obiekt pocisku, ale może zapewnić bardziej realistyczne pociski.
Jeśli chodzi o zarządzanie kolizjami między pociskami opartymi na pociskach a innymi obiektami w grze, wykrywanie kolizji można znacznie uprościć, sortując obiekty na poczwórne lub oktaty . Oktawy są używane głównie w grach 3d, a czwórki mogą być używane w grach 2d lub 3d. Zaletą korzystania z jednego z tych drzew jest to, że można znacznie zmniejszyć liczbę możliwych kontroli kolizji. Na przykład, jeśli masz 20 obiektów aktywnych na poziomie, bez użycia jednego z tych drzew, będziesz musiał sprawdzić wszystkie 20 pod kątem kolizji z kulą. Dzieląc 20 obiektów między liście (końcowe węzły) drzewa, można zmniejszyć liczbę kontroli do liczby obiektów znajdujących się w tym samym liściu co kula.
Jeśli chodzi o te podejścia - hitcan i pocisk, oba mogą być swobodnie używane w grach 2d lub 3d. Zależy to bardziej od tego, czym jest broń i od tego, jak twórca zdecydował, że broń będzie działać.
źródło
Nie jestem bynajmniej ekspertem, ale aby odpowiedzieć na twoje pytanie, tak, potrzebowałbyś wielu z tych rzeczy, o których wspomniałeś.
Na przykład w 2D możesz mieć pozycję i prędkość pocisku. (Możesz także potrzebować dożywotniego lub maksymalnego dystansu, w zależności od tego, jak zaimplementujesz swoje pociski.) Zwykle wymagałoby to 2 (x, y) wartości. Jeśli byłyby zmiennoprzecinkowe, to 16 bajtów. Jeśli masz 100 pocisków, to tylko 1600 bajtów lub około 1,5 tys. To dziś nic na maszynie.
Następnie wspominasz duszki. Potrzebowałbyś tylko jednego duszka do reprezentowania każdej kuli. Jego rozmiar zależy od głębi bitowej, na której rysujesz, oraz od tego, jak duży powinien wyglądać na ekranie. Nawet nieskompresowany, powiedzmy, 256x256 w pełnej liczbie 32 bitów na kanał, to 1 MB dla duszka. (I to byłoby bardzo duże!) Narysowałbyś tego samego duszka w każdym miejscu pocisku, ale nie zajmuje to dodatkowej pamięci dla każdej kopii duszka. Podobnie byłoby w przypadku efektu przy trafieniu.
Wspominasz o strzelaniu co 0,01 sekundy. To byłoby 100 pocisków na sekundę z twojej broni. Nawet jak na futurystyczną broń to całkiem sporo! Zgodnie z tym artykułem wikipedia :
Tak by to było z prędkością śmigłowca szturmowego!
W przypadku dużego świata, takiego jak wspomniany w Battlefield / Call of Duty / itp., Mogą obliczyć wszystkie pozycje pocisków, ale nie mogą ich narysować, jeśli akcja jest daleko. Lub mogą ich nie symulować, dopóki się nie zbliżysz. (Muszę przyznać, że zgaduję trochę z tej strony, ponieważ nie pracowałem nad niczym tak dużym).
źródło
Myślę, że nie doceniasz szybkości komputerów. To był czasem problem na systemach latach 80. i 90.. Jest to częściowo spowodowane tym, że oryginalne Space Invaders nie pozwalają wystrzelić kolejnej kuli, dopóki obecna nie trafi. Niektóre gry cierpiały z powodu „spowolnienia”, jeśli na ekranie było zbyt wiele ikonek.
Ale w dzisiejszych czasach? Masz wystarczającą moc obliczeniową dla tysięcy operacji na piksel, które są wymagane do teksturowania i oświetlenia. Nie ma problemu z tysiącami ruchomych obiektów; pozwala to na zniszczenie terenu (np. Red Faction), gdzie każdy fragment przetwarza kolizję z innymi fragmentami i podąża za krzywą balistyczną.
Musisz być ostrożny algorytmicznie - nie możesz naiwnie podejść do sprawdzania każdego obiektu względem każdego innego obiektu, gdy masz tysiące obiektów. Pociski zasadniczo nie sprawdzają kolizji z innymi pociskami.
Mała anegdota: pierwsza wersja sieciowego Dooma (oryginał z lat 90.) wysyłała jeden pakiet przez sieć na każdy wystrzelony pocisk. Gdy jeden lub więcej graczy otrzyma karabin maszynowy, może to łatwo przytłoczyć sieć. Lata 90. były pełne ludzi nielegalnie grających w Dooma na uniwersytetach lub w sieciach pracy, mających kłopoty z administratorami sieci, gdy sieć stała się bezużyteczna.
źródło
Nie jestem ekspertem, ale w wolnym czasie pracowałem nad strzelanką 2D.
Moja metoda
Istnieją różne klasy punktorów między klientem a serwerem (nawet w trybie offline instancja serwera jest uruchamiana w osobnym procesie i połączona z „główną” grą).
Co tyknięcie (60 na sekundę) klient oblicza namiar między wskaźnikiem myszy gracza a środkiem ekranu (tam, gdzie jest jego postać) i jest to część informacji przesyłanych do serwera. Jeśli gracz również strzela w tym momencie (zakładając, że broń jest załadowana i gotowa), tworzona jest instancja pocisku po stronie serwera, z po prostu pewnymi współrzędnymi i podstawowymi obrażeniami (które wynikają ze statystyk strzelanej broni to). Instancja pocisku używa następnie niektórych funkcji matematycznych do obliczenia prędkości X i Y z łożyska zebranego od klienta.
Za każdym kolejnym tyknięciem pocisk porusza się o te współrzędne i zmniejsza zadawane obrażenia podstawowe o określoną wartość. Jeśli ta wartość spadnie poniżej 1 lub uderzy w solidny obiekt na świecie, instancja pocisku zostanie usunięta, a ponieważ kolizje punktów testowych są niewiarygodnie tanie w 2D, nawet broń szybkostrzelna ma znikomy wpływ na wydajność.
Jeśli chodzi o klienta, informacje o pociskach nie są faktycznie odbierane przez sieć (okazało się to marnotrawstwem w testach), zamiast tego w ramach aktualizacji per-tick każdy znak ma „odpaloną” wartość logiczną, która jeśli prawda, tworzy lokalną obiekt punktora, który działa prawie dokładnie tak samo jak serwer, z tą różnicą, że ma duszka.
Oznacza to, że chociaż kula, którą widzisz, nie jest w pełni dokładnym odwzorowaniem jej na serwerze, każda różnica byłaby ledwo zauważalna, jeśli w ogóle dla gracza, a korzyści sieciowe przeważają nad wszelkimi niespójnościami.
Uwaga na różne metody
Niektóre gry, w tym moja własna, przesuwają pociski za każdym kleszczem, jakby były obiektami fizycznymi, podczas gdy inne po prostu tworzą wektor w kierunku strzelania lub obliczają całą ścieżkę pocisku w utworzonym kleszczu, na przykład w Counter- Gry strajkowe. Istnieje kilka małych sztuczek po stronie klienta, aby to ukryć, takich jak animacja strzelania pociskami, ale dla wszystkich celów i celów każda kula jest tylko laserem .
W przypadku modeli 3D, które mogą mieć złożone trafienia, standardem jest PIERWSZE testowanie kolizji z prostą ramką ograniczającą, a jeśli to się powiedzie, przejdź do bardziej „szczegółowego” wykrywania kolizji.
źródło
To się nazywa wykrywanie kolizji. Komputery 8-bitowe zrobiły to, używając sprzętowo grafiki pocisków gracza. Współczesne silniki gier wykorzystują silniki fizyki i algebrę liniową. Bieżący kierunek broni jest reprezentowany jako wektor 3D. To zapewnia nieskończoną linię w kierunku ognia. Każdy poruszający się obiekt ma jedną lub więcej kulę ograniczającą, ponieważ jest to najprostszy obiekt do wykrycia kolizji z linią. Jeśli te dwa przecinają się, jest to trafienie, jeśli nie, nie ma trafienia. Ale sceneria może być przeszkodą, dlatego też należy to sprawdzić (przy użyciu hierarchicznych woluminów ograniczających). Najbliższym obiektem, który ma skrzyżowanie, jest ten, który został trafiony.
źródło