Zastanawiałem się, czy istnieje jakakolwiek szkoda, gdy moja pętla gry działa tak szybko, jak pozwala na to system?
Obecnie mam pętlę, która mierząc upływ czasu w nanosekundach, uruchamia logikę gry i renderuje logikę z predefiniowanymi prędkościami bez problemu. W rzeczywistości każda logika, którą wykonuję w pętli, jest taktowana na określoną liczbę wywołań co sekundę.
Sama pętla działa jednak tak szybko, jak chce, a na mojej maszynie dochodzi do około 11,7 miliona pętli na sekundę.
Pętla (prosty pseudokod):
while(!isGameOver){
if(canPollInputs){
pollInputs()
}
while(canStepLogic){
stepLogic()
}
if(canRender){
render()
}
}
Moje pytanie jest w zasadzie, czy ta prosta pętla, jeśli nie działa z kontrolowaną prędkością, może wyrządzić szkodę systemowi?
Edycja: Oznacza to, że moja logika działa 30 razy na sekundę (30 tps), mój renderer działa przy 60 fps, odpytuję dane wejściowe 100 razy na sekundę, a także jest trochę logiki, aby poradzić sobie z logiką lub renderowaniem trwającym dłużej niż oczekiwano . Ale sama pętla nie jest dławiona.
Edycja: użycie Thread.sleep()
np. Przepustnicy głównej pętli w dół do 250 pętli na sekundę prowadzi do zmniejszenia, ale pętle działają z prędkością około 570 pętli na sekundę zamiast pożądanych 250 (doda kod, gdy będę na komputerze stacjonarnym ..)
Edycja: Proszę bardzo, działająca gra java w celu wyjaśnienia rzeczy. Możesz też z niego korzystać, ale nie rób sobie tego;)
private void gameLoop() {
// Time that must elapse before a new run
double timePerPoll = 1000000000l / targetPPS;
double timePerTick = 1000000000l / targetTPS;
double timePerFrame = 1000000000l / targetFPS;
int maxFrameSkip = (int) ( (1000000000l / MINIMUM_FPS) / timePerTick);
int achievedPPS = 0;
int achievedFPS = 0;
int achievedTPS = 0;
long timer = TimeUtils.getMillis();
int loops = 0;
int achievedLoops = 0;
long currTime = 0l;
long loopTime = 0l;
long accumulatorPPS = 0l;
long accumulatorTPS = 0l;
long accumulatorFPS = 0l;
long lastTime = TimeUtils.getNano();
while(!isRequestedToStop) {
currTime = TimeUtils.getNano();
loopTime = currTime - lastTime;
lastTime = currTime;
loops = 0;
accumulatorPPS += loopTime;
accumulatorTPS += loopTime;
accumulatorFPS += loopTime;
if(accumulatorPPS >= timePerPoll) {
pollInputs();
playerLogic();
achievedPPS++;
accumulatorPPS -= timePerPoll;
}
while(accumulatorTPS >= timePerTick && loops < maxFrameSkip) {
tick();
achievedTPS++;
accumulatorTPS -= timePerTick;
loops++;
}
// Max 1 render per loop so player movement stays fluent
if(accumulatorFPS >= timePerFrame) {
render();
achievedFPS++;
accumulatorFPS -= timePerFrame;
}
if(TimeUtils.getDeltaMillis(timer) > 1000) {
timer += 1000;
logger.debug(achievedTPS + " TPS, " + achievedFPS + " FPS, "
+ achievedPPS + " Polls, " + achievedLoops + " Loops");
achievedTPS = 0;
achievedFPS = 0;
achievedLoops = 0;
}
achievedLoops++;
}
}
Jak widać, prawie nie ma kodu uruchamianego w każdej pętli, ale zawsze określony wybór na podstawie czasu, który upłynął. Pytanie dotyczy tej „pętli roboczej” i tego, jak wpływa ona na system.
źródło
Odpowiedzi:
Spowoduje to, że jeden rdzeń procesora będzie zawsze działał na 100%. Zwykle nie powoduje to szkód w systemie. Procesory są zaprojektowane do pracy w 100% przez wiele godzin. Ale na urządzeniu mobilnym szybko rozładuje baterię i podgrzeje urządzenie, co prawdopodobnie będzie cię kosztować około gwiazdek w ocenach sklepów. Na komputerze stacjonarnym nie stanowi to większego problemu, ale zużywa więcej energii elektrycznej użytkowników, powoduje szybsze obracanie się wentylatora procesora, co może powodować hałas i marnować cykle procesora, które mogłyby zostać wykorzystane przez inne procesy. Chociaż nie są to wady krytyczne, nadal mają zły styl, dlatego należy ich unikać, jeśli to możliwe.
Nie powiedziałeś nic o tym, jak twoja pętla logiczna gry działa wewnętrznie, ale kiedy używasz podejścia delta-time (każde obliczenie, które wykonujesz, bierze pod uwagę czas od ostatniego połączenia), masz do czynienia z bardzo małą deltą- wartości czasu. Oznacza to, że możesz napotkać problemy z niedokładnością zmiennoprzecinkową, co może powodować różnego rodzaju dziwne zachowania. Ponadto rozdzielczość czasomierza systemowego jest często ograniczona, więc gdy twoja pętla logiczna jest zbyt szybka, możesz uzyskać wartość delta-t równą zero, co może spowodować podział przez zero, co spowoduje awarię.
Aby złagodzić ten problem, należy ograniczyć liczbę klatek na sekundę (grafikę i logikę) do maksimum tego, co może dostrzec ludzkie oko. To, o ile jest kwestionowane, zależy od rodzaju animacji i rodzaju wyświetlanego obrazu, ale szacunki wahają się od 40 FPS do 120 FPS. Oznacza to, że musisz ustawić minimalny czas wykonania każdej pętli między 20 ms a 8 ms. Jeśli iteracja pętli zakończy się szybciej, pozwól wątkowi procesora
sleep
na pozostały okres czasu.źródło
Marnujesz cykle procesora. Oznacza to krótszy czas pracy baterii w notebookach, tabletach i telefonach, wyższe rachunki za prąd, więcej ciepła wytwarzanego przez maszynę, głośniejsze wentylatory. Ponadto możesz spożywać cykle z innych ważnych procesów systemowych (np. Serwer okien może się popsuć), co może mieć wpływ na rozgrywkę. Niektóre programy planujące w dzisiejszych systemach wyprzedzających wielozadaniowość również nakładają kary na aplikacje, które wykorzystują zbyt wiele cykli, więc możesz być szybki, a potem nagle zobaczyć dziwne szarpnięcie, gdy system jest dławiony.
Wielu zapalonych graczy buduje również niestandardowe komputery od zera, które są na granicy specyfikacji ich wentylatora, w którym to przypadku gorący dzień i ciepło generowane przez grę mogą spowodować uruchomienie zabezpieczeń w maszynie (w najlepszym wypadku) lub nawet przegrzanie części i umrzeć (w najgorszym przypadku). Więc jeśli to twoi odbiorcy docelowi, możesz chcieć upewnić się, że zawsze pozostawiasz trochę miejsca „na górze”.
Wreszcie, istnieją problemy z grą, w których możesz być korzystniejszy dla graczy z szybszymi maszynami niż z wolniejszymi. Ograniczenie pętli gry do określonej częstotliwości i użycie dodatkowych cykli wyłącznie w aspektach niezwiązanych z rozgrywką (takich jak renderowanie grafiki o wyższej wierności, efekty dźwięku przestrzennego itp.) Sprawią, że gra będzie bardziej uczciwa.
źródło
Nie powinieneś mieć roztrzęsionego ruchu / rozgrywki
Istnieją dwa sposoby implementacji logiki gry - związane z czasem rzeczywistym lub związane z liczbą „tur” / kroków przetwarzania. Jeśli coś w twojej grze porusza się w lewo, to czy ruch będzie inny, jeśli twoja stepLogic () była nazywana 100 zamiast 50 razy?
Jeśli Twój kod traktuje upływający czas wyraźnie wszędzie i robi to poprawnie, może być w porządku; ale jeśli w kodzie jest coś, co zależy od liczby „kroków”, otrzymasz niepożądane efekty uboczne.
Po pierwsze, prędkość rzeczy, które powinny się ciągle poruszać, będzie się nieprzewidywalnie zmieniać (w zależności od użycia procesora), a to powoduje bardzo irytujące sterowanie - nie możesz wykonać precyzyjnego uderzenia lub skoku, jeśli nagle gra przyspieszy lub zwolni. Nawet w przypadku gier bez „drgania” wygląda denerwująco i roztrzęsiony, jeśli wszystko nie idzie gładko.
Po drugie, możesz mieć problemy ze zdolnościami postaci w zależności od szybkości komputera - klasycznym przykładem jest stary problem Quake'a, w którym maksymalny zasięg skoków był przypadkowo zależny od twojej fps.
Te problemy mogą się pojawiać, nawet jeśli próbujesz mieć kod niezależny od fps, nagromadzenie błędów zaokrąglania może często powodować takie błędy.
źródło
Jedyną potencjalną szkodą jest zużycie energii, więc nie rób tego na urządzeniach mobilnych. I odwrotnie, wiele systemów wbudowanych spędza całe swoje życie w pętli, czekając, aż coś się wydarzy.
Kiedyś całkowicie normalne było renderowanie klatek gry tak szybko, jak to możliwe, czasami nawet bez timera, który kompensowałby rozgrywkę przy różnych prędkościach procesora. Wyścigowa gra Bullfroga, Hi-Octane, była szczególnie złą ofiarą tego i podejrzewam, że o tym mówi plakat z Civ2.
Radzę przynajmniej sprawdzać dane wejściowe przy każdym przejściu, jeśli korzystasz z systemu Windows lub podobnego, aby zapewnić szybką reakcję na dane wejściowe.
źródło
swap
/present
/flip
do czekania na sygnał vsync, aby regulować pętlę gry.