Co się właściwie dzieje u niekończącego się biegacza?

107

Na przykład słynna gra Flappy Bird lub cokolwiek, co naprawdę się sortuje, to gracz (w tym przypadku ptak lub kamera, w zależności od tego, co wolisz) porusza się do przodu lub cały świat porusza się do tyłu (ptak zmienia tylko pozycję Y i ma stała pozycja X)?

Lendrit Ibrahimi
źródło
1
Z tego pytania i jego odpowiedzi usunęliśmy wiele komentarzy spoza tematu. Przeprowadziliśmy również kilka rozsądnych dyskusji na czacie, w niektórych przypadkach wielokrotnie. Mimo to kilku użytkowników odczuwa potrzebę zignorowania faktu, że komentarze nie są przeznaczone do długich dyskusji i są kontynuowane. W związku z tym pytanie zostało tymczasowo zablokowane.
Josh

Odpowiedzi:

146

Nie zgadzam się z odpowiedzią Filipa ; a przynajmniej z tym, jak to przedstawił. Sprawia wrażenie, że lepszym pomysłem jest poruszanie się po świecie wokół gracza; kiedy jest dokładnie odwrotnie. Oto moja własna odpowiedź ...


Obie opcje mogą działać, ale generalnie złym pomysłem jest „odwrócenie fizyki” poprzez przenoszenie świata wokół gracza, a nie gracza na całym świecie.


Utrata wydajności / odpady:

Świat zwykle będzie miał wiele przedmiotów; wielu, jeśli nie większość, statycznych lub śpiących. Gracz będzie miał jeden lub względnie mało obiektów. Poruszanie całym światem wokół gracza oznacza przenoszenie wszystkiego na scenie oprócz gracza. Obiekty statyczne, śpiące obiekty dynamiczne, aktywne obiekty dynamiczne, źródła światła, źródła dźwięku itp .; wszystkie muszą zostać przeniesione.

To (oczywiście) znacznie droższe niż przenoszenie tylko tego, co się faktycznie porusza (gracz i może jeszcze kilka innych rzeczy).


Konserwowalność i rozszerzalność:

Poruszanie się po świecie wokół gracza sprawia, że ​​świat (i wszystko w nim) jest punktem, w którym rzeczy dzieją się najbardziej aktywnie. Każdy błąd lub zmiana w systemie oznacza, że ​​potencjalnie wszystko się zmienia. To nie jest dobry sposób na robienie rzeczy; chcesz, aby błędy / zmiany były jak najbardziej odizolowane, abyś nie dostał nieoczekiwanego zachowania w miejscu, w którym nie wprowadziłeś zmian.

Z tym podejściem wiąże się także wiele innych problemów. Na przykład łamie wiele założeń dotyczących tego, jak rzeczy mają działać na silniku. Na przykład nie będziesz mógł używać dynamiki RigidBodydo niczego innego niż odtwarzacz; ponieważ obiekt z dołączonym RigidBodynie ustawionym kinematyki będzie zachowywał się nieoczekiwanie podczas ustawiania pozycji / obrotu / skali (co robiłbyś dla każdej klatki, dla każdego obiektu w scenie, z wyjątkiem odtwarzacza 😨)


Odpowiedzią jest więc tylko przesunąć gracza!

Cóż ... tak i nie . Jak wspomniano w odpowiedzi Filipa, w grze typu „nieskończony biegacz” (lub dowolnej grze z dużym bezproblemowym obszarem do eksploracji) zbytnie oddalanie się od pochodzenia ostatecznie wprowadziłoby zauważalne FPPE ( błędy precyzji zmiennoprzecinkowej ), a ponadto przepełnienie typu liczbowego, albo powodując awarię gry, albo, w gruncie rzeczy, dym świat gry ... pęk na sterydach! 😵 (ponieważ do tego momentu FPPE sprawi, że gra będzie już na „normalnym” crackie)


Rzeczywiste rozwiązanie:

Nie rób ani i rób oba! Powinieneś utrzymać świat w bezruchu i poruszać nim gracza. Ale „zrootuj” zarówno gracza, jak i świat, gdy gracz zacznie się zbyt daleko od korzenia (pozycji [0, 0, 0]) sceny.

Jeśli utrzymasz względną pozycję rzeczy (gracza względem otaczającego go świata) i wykonasz ten proces w ramach pojedynczej aktualizacji ramki, (rzeczywisty) gracz nawet tego nie zauważy!

Aby to zrobić, masz dwie podstawowe opcje:

  1. Przenieś gracza do korzenia sceny i przenieś fragment świata na jego nową pozycję względem gracza.
  2. Pomyśl o świecie jak o siatce; przesuń część siatki, w której znajduje się gracz, do korzenia i przenieś gracza do nowej pozycji względem tej części siatki.

Oto przykład tego procesu w akcji


Ale jak daleko jest za daleko?

Jeśli spojrzysz na kod źródłowy Unity, używają one 1e-5( 0.00001) jako podstawy do rozważenia dwóch wartości zmiennoprzecinkowych „równych”, wewnątrz Vector2i Vector3(typy danych odpowiedzialne za pozycje obiektów, obrót i skale [euler-]). Ponieważ utrata precyzji zmiennoprzecinkowej odbywa się w obie strony od zera, można bezpiecznie założyć, że wszystko pod jednostkami 1e+5( 100000) oddalonymi od katalogu głównego / początku sceny jest bezpieczne do pracy.

Ale! Od...

  1. Bardziej odpowiednie jest stworzenie systemu do automatycznego zarządzania tymi procesami ponownego rootowania.
  2. Jakakolwiek jest Twoja gra, nie ma potrzeby, aby ciągła „część” świata miała 100 000 jednostek (metrów [?]).

... to prawdopodobnie dobrym pomysłem jest ponowne rootowanie znacznie wcześniej / częściej niż ten znak 100000 jednostek. Przykładowy film, który podałem, wydaje się robić to na przykład co 1000 jednostek.

XenoRo
źródło
2
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu . Użyj tego czatu zamiast zamieszczać tutaj więcej komentarzy.
Alexandre Vaillancourt
> To (oczywiście) znacznie droższe niż przenoszenie tylko tego, co faktycznie się porusza <Czy to? Podczas renderowania, to i tak trzeba zrobić mnożenie macierzy z matrycy aparatu na wszystkich obiektach, mając aparat stały na tożsamość byłoby mniej kosztowne w ten sposób.
Matsemann,
Ten moment, w którym tworzenie gier staje się rozwojem jedności ...
LJ ᛃ
@ Matsemann - Gdybyś poszedł na czat, zauważyłbyś, że to nie jest nowy punkt. Jak już wyjaśniono, renderuj scenę NOT-EQUALS; są różnymi i prawie całkowicie niezależnymi podmiotami i rurociągami. Wykonanie inwersji układu odniesienia w sposobie pozycjonowania obiektów w scenie będzie oznaczało, że będziesz miał cięższy proces na obu końcach , a nie tylko w renderowaniu. --- Ponadto, nawet jeśli występ był równie w obrocie, jak twierdzą, wszystkie inne problemy z inwersją będzie nadal pozostają nierozwiązane, co sprawia, że podejście jeszcze gorzej niż ponowne zakorzenienie jeden.
XenoRo,
@LJ ᛃ Pytanie dotyczyło konkretnie jedności (patrz tagi OP przed edycjami D. Everharda [ usuwanie defektu ]), więc odpowiedź została dostosowana, aby to odzwierciedlić . --- Ale oto najlepsza część: Be it Unity, Unreal, CryEngine, Source itp. ... Najlepsze i / lub najpopularniejsze silniki działają w ten sposób (i robią to z jakiegoś powodu), więc odpowiedź jest nie tylko ogólnie rzecz biorąc, są one całkowicie poprawne , ale tworzone punkty są wciąż na szczycie dokładności . Ogólnie rzecz biorąc, jeśli odwrócisz układ odniesienia fizyki, możesz spodziewać się problemów, szczególnie na obiektach dynamicznych.
XenoRo,
89

Obie opcje działają.

Ale jeśli chcesz, aby niekończący się biegacz był naprawdę nieskończony, będziesz musiał zatrzymać gracza i ruszać światem. W przeciwnym razie w końcu osiągniesz granice zmiennych używanych do przechowywania pozycji X. Liczba całkowita ostatecznie się przepełni, a zmienna zmiennoprzecinkowa stanie się coraz mniej dokładna, co po pewnym czasie sprawi, że gra będzie wyglądać niezręcznie. Możesz jednak uniknąć tego problemu, używając wystarczająco dużego typu, aby nikt nie napotkał tych problemów w czasie, który można odtworzyć w jednej sesji (gdy gracz porusza się o 1000 pikseli na sekundę, 32-bitowa liczba całkowita przepełni się po 49 dniach).

Rób więc wszystko, co wydaje ci się bardziej intuicyjne.

Philipp
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Josh
1
Nie sądzę, że to coś rozwiązuje. Zamiast utrzymywać pozycję gracza (i kamery), utrzymujesz pozycję przewijania na świecie - jak to inaczej?
Agent_L,
1
@Agent_L Ogólnie światy są generowane fragmentarycznie, a każdy element ma swoją pozycję X / Y. Kiedy pionek dostanie się za gracza wystarczająco dużo (np. Poza ekranem), jest usuwany i są generowane tylko z pewnym wyprzedzeniem, więc zawsze znajdują się w stosunkowo niewielkim zasięgu - jedna gra, którą stworzyłem, nigdy nie miała współrzędnej niż ~ 1000 lub mniej niż 0, mimo że jestem nieskończonym, losowo generowanym biegaczem, z tego powodu - śledziłem pozycję gracza w każdym kawałku i indeks każdego kawałka w sekwencji.
Nic Hartley,
Nie kupuję koncepcji „zmiennego przepełnienia”. Jeśli gracz się nie porusza, wówczas „tło” zostanie ujemnie zrównoważone o tę samą kwotę, o jaką gracz zostałby przesunięty, gdyby się poruszał. Oba przypadki napotkałyby dokładnie ten sam problem (w przeciwnych kierunkach) i oba wymagałyby specjalnego postępowania na granicy przepełnienia.
spędzić
2
przy prędkości 1000 pikseli na sekundę przekroczenie 64-bitowej liczby całkowitej zajęłoby ponad 586 milionów lat. Jest to naprawdę problem, jeśli używasz naprawdę małych typów, takich jak 16-bitowe liczby całkowite.
Lyndon White,
38

Opierając się na odpowiedzi XenoRo , zamiast opisywanej przez siebie metody ponownego rootowania , można wykonać następujące czynności:

Stwórz okrągły bufor części generowanej nieskończonej mapy, przez którą porusza się twoja postać z pozycją zaktualizowaną o arytmetykę modulo (więc po prostu biegaj wokół okrągłego bufora). Zacznij wymieniać części bufora, gdy tylko postać opuści fragment. Równanie aktualizacji odtwarzacza wyglądałoby mniej więcej tak:

player.position = (player.position + player.velocity) % worldBuffer.width;

oto obrazowy przykład tego, o czym mówię:

wprowadź opis zdjęcia tutaj

Oto przykład tego, co dzieje się na zawijaniu na końcu.

wprowadź opis zdjęcia tutaj

Dzięki tej metodzie nigdy nie napotykasz błędów precyzji, ale może być konieczne utworzenie bardzo dużego bufora, jeśli zamierzasz to zrobić w 3D z bardzo dużej odległości widzenia (ponieważ nadal będziesz musiał widzieć przed sobą) ). Jeśli jest to ptak flappy, Twój rozmiar prawdopodobnie będzie tak duży, jak to konieczne, aby pomieścić jedną scenę na jednym ekranie, a bufor może być bardzo mały.

Zauważ, że zaczniesz uzyskiwać powtarzające się wyniki z DOWOLNYM prng, a maksymalna liczba niepowtarzalnych sekwencji generacji PRNG jest zwykle mniejsza niż długość pow (2, liczba bitów użytych wewnętrznie), w przypadku merzenne twister to nie jest duży problem, ponieważ wykorzystuje 2,5k stanu wewnętrznego, ale jeśli używasz małego wariantu, masz 2 ^ 127-1 iteracji przed powtórzeniem (lub gorzej), to wciąż jest to astronomicznie duża liczba . Możesz naprawić problemy z powtarzającym się okresem, nawet jeśli twój PRNG ma krótki okres dzięki dobrym funkcjom mieszania lawinowego dla nasion (gdy dodajesz więcej stanu pośrednio) wielokrotnie.

opa
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Josh
15

Jak już wspomniano i zaakceptowano, tak naprawdę zależy to od zakresu i stylu twojej gry, ale ponieważ nie zostało to wspomniane: FlappyBird przesuwa przeszkodę po ekranie, a nie gracz na całym świecie.

Spawn tworzy obiekty poza ekranem ze stałą prędkością w Vector2.leftkierunku.

Stephan
źródło
3
Działa to dla FlappyBird, ponieważ jest to bardzo prosta gra. Jak wspomniano w mojej odpowiedzi, ta sama technika, choć wciąż możliwa , byłaby bardzo problematyczna w przypadku wszystkiego, co jest bardziej złożone (z większą ilością obiektów / detali świata), jak na przykład Subway Surfer.
XenoRo,
15
Zgadzam się, a twoja odpowiedź jest bardzo dokładna. Po prostu nie widziałem, żeby ktokolwiek wspominał, z której metody trzepoczący ptak faktycznie korzysta, więc pomyślałem, że go dodam.
Stephan