Niedawno przeczytałem ten artykuł na temat Game Loops: http://www.koonsolo.com/news/dewitters-gameloop/
A ostatnia zalecana implementacja głęboko mnie dezorientuje. Nie rozumiem, jak to działa i wygląda na kompletny bałagan.
Rozumiem zasadę: aktualizuj grę ze stałą prędkością, a wszystko, co pozostanie, renderuje grę tyle razy, ile to możliwe.
Rozumiem, że nie możesz użyć:
- Uzyskaj dane wejściowe dla 25 tyknięć
- Renderuj grę dla 975 tyknięć
Podejść, ponieważ dostaniesz wkład w pierwszą część drugiej i byłoby to dziwne? Czy to właśnie dzieje się w tym artykule?
Głównie:
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)
Jak to w ogóle ważne?
Załóżmy jego wartości.
MAX_FRAMESKIP = 5
Załóżmy, że gra next_game_tick została przypisana chwilę po inicjalizacji, zanim główna pętla gry powie… 500.
Wreszcie, ponieważ używam SDL i OpenGL do mojej gry, a OpenGL służy tylko do renderowania, załóżmy, że GetTickCount()
zwraca czas od wywołania SDL_Init, co robi.
SDL_GetTicks -- Get the number of milliseconds since the SDL library initialization.
Źródło: http://www.libsdl.org/docs/html/sdlgetticks.html
Autor zakłada również, że:
DWORD next_game_tick = GetTickCount();
// GetTickCount() returns the current number of milliseconds
// that have elapsed since the system was started
Po rozwinięciu while
instrukcji otrzymujemy:
while( ( 750 > 500 ) && ( 0 < 5 ) )
750, ponieważ minął czas, odkąd next_game_tick
został przypisany. loops
wynosi zero, jak widać w artykule.
Więc weszliśmy w pętlę while, zróbmy logikę i zaakceptujmy dane wejściowe.
Yadayadayada.
Pod koniec pętli while, o której przypominam, znajduje się w naszej głównej pętli gry:
next_game_tick += SKIP_TICKS;
loops++;
Zaktualizujmy, jak wygląda następna iteracja kodu while
while( ( 1000 > 540 ) && ( 1 < 5 ) )
1000, ponieważ upłynęło czasu na uzyskanie danych wejściowych i zrobienie rzeczy, zanim dotarliśmy do kolejnego ineteracji pętli, w której wywoływana jest funkcja GetTickCount ().
540, ponieważ w kodzie 1000/25 = 40, zatem 500 + 40 = 540
1, ponieważ nasza pętla wykonała iterację raz
5 , wiesz dlaczego.
Skoro więc ta pętla While jest WYŁĄCZNIE zależna, MAX_FRAMESKIP
a nie zamierzona, w TICKS_PER_SECOND = 25;
jaki sposób gra powinna działać poprawnie?
Nie było dla mnie zaskoczeniem, że kiedy zaimplementowałem to w moim kodzie, mógłbym poprawnie dodać, ponieważ po prostu zmieniłem nazwę funkcji, aby obsłużyć dane wejściowe użytkownika i zwrócić grę do tego, co autor artykułu w swoim przykładowym kodzie, nic nie zrobił .
Umieściłem fprintf( stderr, "Test\n" );
wewnętrzną pętlę while, która nie jest drukowana do końca gry.
W jaki sposób ta pętla gry działa 25 razy na sekundę, gwarantowana, a jednocześnie renderuje tak szybko, jak to możliwe?
Dla mnie, chyba że brakuje mi czegoś OGROMNEGO, wygląda na to ... nic.
I czy ta struktura pętli while nie działa podobno 25 razy na sekundę, a następnie aktualizuje grę dokładnie tak, jak wspomniałem wcześniej na początku artykułu?
Jeśli tak jest, dlaczego nie moglibyśmy zrobić czegoś prostego, takiego jak:
while( loops < 25 )
{
getInput();
performLogic();
loops++;
}
drawGame();
I licz na interpolację w inny sposób.
Wybacz moje bardzo długie pytanie, ale ten artykuł wyrządził mi więcej szkody niż pożytku. Jestem teraz poważnie zdezorientowany - i nie mam pojęcia, jak wdrożyć właściwą pętlę gry z powodu wszystkich tych powstałych pytań.
źródło
Odpowiedzi:
Myślę, że autor popełnił drobny błąd:
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)
Powinien być
while( GetTickCount() < next_game_tick && loops < MAX_FRAMESKIP)
To znaczy: dopóki nie jest jeszcze czas na narysowanie naszej następnej klatki i chociaż nie pominęliśmy tyle klatek, co MAX_FRAMESKIP, powinniśmy poczekać.
Nie rozumiem też, dlaczego aktualizuje się
next_game_tick
w pętli i zakładam, że to kolejny błąd. Ponieważ na początku klatki można określić, kiedy powinna być następna klatka (przy użyciu stałej liczby klatek).next game tick
Nie zależy od tego, ile czasu nam zostało po uwzględnieniu aktualizacji i renderingu.Autor popełnia także kolejny powszechny błąd
Oznacza to wielokrotne renderowanie tej samej klatki. Autor jest nawet świadomy tego:
Powoduje to jedynie, że procesor graficzny wykonuje niepotrzebną pracę, a jeśli renderowanie trwa dłużej niż oczekiwano, może to spowodować rozpoczęcie pracy na następnej ramce później niż planowano, dlatego lepiej poddać się systemowi operacyjnemu i poczekać.
źródło
Może najlepiej, jeśli trochę to uproszczę:
while
Pętla wewnątrz pętli głównej służy do uruchamiania kroków symulacji od miejsca, w którym kiedykolwiek była, do miejsca, w którym powinna być teraz.update_game()
funkcja powinna zawsze zakładać, żeSKIP_TICKS
od ostatniego wywołania upłynęło tylko tyle czasu. Dzięki temu fizyka gry będzie działała ze stałą prędkością na powolnym i szybkim sprzęcie.Zwiększanie
next_game_tick
o liczbęSKIP_TICKS
ruchów przybliża go do bieżącego czasu. Kiedy staje się większy niż obecny czas, przerywa (current > next_game_tick
) i mainloop kontynuuje renderowanie bieżącej ramki.Po renderowaniu następne wywołanie
GetTickCount()
zwróci nowy bieżący czas. Jeśli ten czas jest dłuższy niżnext_game_tick
oznacza to, że jesteśmy już za krokami 1-N w symulacji i powinniśmy nadrobić zaległości, wykonując każdy krok w symulacji z tą samą stałą prędkością. W takim przypadku, jeśli jest niższy, po prostu renderowałby ponownie tę samą ramkę (chyba że występuje interpolacja).Oryginalny kod ograniczył liczbę pętli, jeśli jesteśmy zbyt daleko (
MAX_FRAMESKIP
). To tylko sprawia, że faktycznie pokazuje coś i nie wygląda na zablokowane, jeśli na przykład wznawia się od zawieszenia lubGetTickCount()
wstrzymuje grę na długi czas (zakładając, że nie zatrzyma się w tym czasie), dopóki nie dogoni z czasem.Aby pozbyć się bezużytecznego renderowania tej samej ramki, jeśli nie używasz interpolacji w środku
display_game()
, możesz owinąć ją wewnątrz, jeśli instrukcja taka jak:Jest to również dobry artykuł na ten temat: http://gafferongames.com/game-physics/fix-your-timestep/
fprintf
Być może powód, dla którego wyniki po zakończeniu gry mogą być takie, że nie zostały wypłukane.Przepraszam, za mój angielski.
źródło
Jego kod wygląda na całkowicie poprawny.
Rozważ
while
pętlę ostatniego zestawu:Mam zainstalowany system, który mówi: „gdy gra jest uruchomiona, sprawdź aktualny czas - jeśli jest większy niż nasz licznik klatek na sekundę, i pominęliśmy rysowanie mniej niż 5 klatek, następnie pomiń rysowanie i po prostu zaktualizuj dane wejściowe i fizyka: w przeciwnym razie narysuj scenę i rozpocznij następną iterację aktualizacji ”
Po każdej aktualizacji zwiększa się czas „następnej_ramki” o idealną liczbę klatek na sekundę. Następnie ponownie sprawdź swój czas. Jeśli twój aktualny czas jest teraz krótszy niż czas, kiedy należy zaktualizować następną ramkę, pomiń aktualizację i narysuj to, co masz.
Jeśli Twój aktualny czas jest większy (wyobraź sobie, że ostatni proces losowania zajął naprawdę dużo czasu, ponieważ gdzieś było trochę czkawki lub garść zbierania śmieci w zarządzanym języku lub implementacja pamięci zarządzanej w C ++ lub cokolwiek innego), to losowanie jest pomijane i
next_frame
aktualizuje się o kolejną dodatkową klatkę czasu, dopóki aktualizacje nie nadążą za tym, gdzie powinniśmy być na zegarze, lub pominiemy rysowanie wystarczającej liczby klatek, które absolutnie MUSIMY narysować, aby gracz mógł zobaczyć co oni robią.Jeśli twoja maszyna jest superszybka lub gra jest super-prosta,
current_time
może to być rzadszenext_frame
, co oznacza, że nie aktualizujesz podczas tych punktów.Tam właśnie pojawia się interpolacja. Podobnie, możesz mieć oddzielny bool, zadeklarowany poza pętlami, aby zadeklarować „brudną” przestrzeń.
Wewnątrz pętli aktualizacji ustawiłeś
dirty = true
, co oznacza, że faktycznie wykonałeś aktualizację.Następnie zamiast dzwonić
draw()
, powiedziałbyś:Wtedy nie tracisz żadnej interpolacji dla płynnego ruchu, ale upewniasz się, że aktualizujesz tylko wtedy, gdy faktycznie się dzieje (zamiast interpolacji między stanami).
Jeśli jesteś odważny, znajdziesz artykuł zatytułowany „Napraw swój czas!” przez GafferOnGames.
Podchodzi do problemu nieco inaczej, ale uważam, że jest to ładniejsze rozwiązanie, które robi to samo (w zależności od funkcji twojego języka i tego, jak bardzo zależy ci na obliczeniach fizycznych gry).
źródło