• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Pętla stałokrokowa - tworzenie gier.

+1 głos
348 wizyt
pytanie zadane 26 maja 2016 w C i C++ przez Kyoya Początkujący (260 p.)
Witam. Moje pytanie, a raczej prośba jest krótka i rzeczowa. Mam problem ze zrozumieniem mechanizmu działania gier, a mianowicie nie jestem w stanie znaleźć żadnego szczegółowego wyjaśnienia dotyczącego zagadnienia pętli stałokrokowej.Przeczytałem już masę artykułów, zarówno w języku polskim i angielskim. Szczerze mówiąc, żaden z nich nie był w stanie mi pomóc. Nie zależy mi na gotowym kodzie, tylko na możliwości zrozumienia w pełni całego zamysłu konceptualnego, który jest związany z tym zagadnieniem. Jeśli ktoś z Was, jest bardziej obeznany w tym temacie, proszę o wyjaśnienie mi tego pojęcia, albo podzielenie się jakimś artykułem, który Wam pomógł - być może jeszcze na niego nie trafiłem. Pozdrawiam.

2 odpowiedzi

+2 głosów
odpowiedź 26 maja 2016 przez draghan VIP (102,850 p.)
wybrane 26 maja 2016 przez Kyoya
 
Najlepsza

Czytałeś ten artykuł? Kiedy chciałem poznać to zagadnienie, okazał się bardzo pomocny. Plus lektura uzupełniająca.

komentarz 26 maja 2016 przez Kyoya Początkujący (260 p.)
Można w jakiś sposób rozdzielić fizykę gry od renderingu? Tak żeby nadganianie następowało tylko dla fizyki?
komentarz 26 maja 2016 przez draghan VIP (102,850 p.)
Przykład z nieskończonym renderowaniem jest oczywiście przejaskrawiony.

Rozdzielić fizykę od części renderującej nie tylko można, ale należy. Nie wyobrażam sobie dobrego kodu, w którym byłyby wymieszane obliczenia fizyki z kodem odpowiedzialnym za rysowanie.
komentarz 26 maja 2016 przez Kyoya Początkujący (260 p.)
Czy takim zabezpieczeniem mogłoby być na przykład umieszczenie samej fizyki w pętli stałokrokowej, a renderowania poza nią?
komentarz 26 maja 2016 przez draghan VIP (102,850 p.)

Możesz wywoływać update fizyki w wewnętrznym while'u, a całą resztę poza nim, oczywiście w ramach pętli głównej. Ale nic nie stoi na przeszkodzie, żeby użyć kilku wariantów w jednej pętli:

const Time physics_step = 33;
const Time rendering_step = 33; // można oczywiście dać inny interwał dla obu zadań

Time physics_elapsed_time = 0;
Time rendering_elapsed_time = 0;

while(game.on())
{
    Time on_begin = Time::get_now(); 

    while(physics_elapsed_time > physics_step)
    {
        physics_elapsed_time -= physics_step;
        game.update_physics();
    }

    if(rendering_elapsed_time > rendering_step)
    {
        rendering_elapsed_time = 0;
        game.draw();
    }

   Time delta = Time::get_now() - on_begin;
   physics_elapsed_time += delta;
   rendering_elapsed_time += delta;
}

 

komentarz 26 maja 2016 przez Kyoya Początkujący (260 p.)
Genialne. Dziękuję ślicznie. :)
0 głosów
odpowiedź 26 maja 2016 przez Patrycjerz Mędrzec (182,570 p.)
Głównym zadaniem pętli gry jest aktualizacja aplikacji, czyli wszystkiego, co się w grze dzieje na okrągło (zdarzenia, przemieszczanie obiektów, ich rysowanie itp.).

Pętle gry mają różnoraką postać, zależną od konkretnych potrzeb.

Pętla stałokrokowa umożliwia kontrolowanie gry wg stałego kroku czasowego - taka cecha może być przydatna, gdy tworzymy w naszej grze np. symulacje fizyczne i potrzebna jest nam precyzja obliczeń (znając dokładny czas, wg którego zmienia się stan obiektów, możemy uniknąć niemiłych sytuacji i się na nie przygotować).
komentarz 26 maja 2016 przez Kyoya Początkujący (260 p.)
To wszystko już zdążyłem wyczytać z internetu. Mój problem bardziej polega na tym, że nie wiem jak się do tego wszystkiego zabrać. Jestem w stanie stworzyć podstawową pętlę gry, z uwzględnieniem obsługi zdarzeń itd, ale nie wiem jak w to wszystko zaimplementować pętlę stałokrokową.
komentarz 26 maja 2016 przez Patrycjerz Mędrzec (182,570 p.)
edycja 26 maja 2016 przez Patrycjerz

Należy pamiętać, że stan obiektów jest zależny od czasu - to jest słowo klucz, zapominane przez początkujących twórców gier.

Przy pętli stałokrokowej wywołujesz funkcje aktualizujące grę tylko w pewnych odstępach czasu, nigdy wcześniej:

float krok = 0.0f;
while(gra.czyOtwarta())
{
	float czas_1 = zmierzCzas();
	if(krok > 0.1f) // Czyli 10 FPS
	{
		gra.aktualizuj();
		krok = 0.0f;
		continue;
	}
	float czas_2 = zmierzCzas();
	krok += czas_2 - czas_1;
}

PS: Możesz także użyć funkcję zatrzymującą dany proces na pewien czas (na Windowsie to Sleep) - będzie to nawet lepszy pomysł, niż ciągłe mierzenie czasu.

komentarz 26 maja 2016 przez Kyoya Początkujący (260 p.)
No i właśnie w tym momencie jestem trochę zdezorientowany. :)

1. Zmienna "krok" ma w tym przypadku zajmować się pomiarem czasu trwania obiegu pętli?
2. Dlaczego blok warunkowy ma się wykonywać kiedy kro jest większy od 0.1f?
3. Dlaczego po bloku warunkowym występuje obliczanie delty czasu?

Widziałem już tak wiele implementacji pętli stałokrokowej, a nadal nie mogę tego tematu ugryźć.

@Edit: Używanie sleep'ów zbytecznie obciążyłoby aplikację, a tego chyba nie chcemy, prawda? :)
komentarz 26 maja 2016 przez Patrycjerz Mędrzec (182,570 p.)

1. Tak.

2. Ponieważ jest to główne założenie pętli stałokrokowej. Aktualizujesz grę tylko po upłynięciu pewnego czasu, nigdy wcześniej.

Rozpatrzmy dwa przypadki:

  • Komputer potrafi aktualizować stan gry szybciej, niż jest to mu ograniczone - wtedy masz pewność, że twoja symulacja nie ulegnie awarii, gdyż kontrolujesz czas zmian.
  • Komputer nie potrafi aktualizować stan gry szybciej, niż jest to mu ograniczone - gra będzie wtedy działać wolniej niż na szybszym sprzęcie.

Jeśli takie cechy ci nie odpowiadają, to pomyśl o innym typie pętli - jest ich wiele.

3. Do obliczenia kroku czasowego zastosowałem wyimaginowaną funkcję, która zwraca ilość sekund od pewnej daty... przyjmijmy, że od 1 stycznia 1970 roku (analogicznie do funkcji time).

Wtedy muszę obliczyć różnicę czasów, która będzie równa czasowi trwania jednego obiegu pętli.

Co do funkcji Sleep, to jest ona przeciwieństwem "obciążenia aplikacji", gdyż przerywa ona cały wątek - w skrócie: przyspiesza ona komputer, a nie zwalnia.

komentarz 26 maja 2016 przez draghan VIP (102,850 p.)

PS: Możesz także użyć funkcję zatrzymującą dany proces na pewien czas (na Windowsie to Sleep) - będzie to nawet lepszy pomysł, niż ciągłe mierzenie czasu.

Zatrzymanie procesu w grze (a już w ogóle poprzez Sleep) nie jest dobrym pomysłem. Głównym powodem jest konieczność zbierania wejścia na bieżąco - nie może tu występować martwa przerwa, bo gracz miałby możliwość interakcji ze światem tylko podczas wstrzelenia się z przyciśnięciem klawisza akurat kiedy aplikacja nie śpi... czyli generalnie słabo.
Ale tak pominąwszy kwestię zbierania wejścia - co w przypadku, kiedy AI chcemy aktualizować 7 razy na sekundę, a update obiektów i rysowanie klatki 30 razy na sekundę? Odmierzanie czasu i taka postać pętli, jaką podałeś, to rozwiązanie zupełnie dobre.

komentarz 26 maja 2016 przez Kyoya Początkujący (260 p.)
Jeżeli dobrze rozumiem, to przy pierwszym obiegu pętli, zmienna czas_1, będzie równa 0, prawda? Załóżmy, że obieg pętli przypisze do zmiennej czas_2 wartość 0.1f. Krok wyniesie wtedy 0.1f - 0.f, czyli 0.1f. Warunek krok > 0.1f nie zostanie spełniony. Co stanie się w takim przypadku? Według tego co przeanalizowałem krok po kroku, jeżeli założymy, że wykonanie się instrukcji zawartej w pętli trwałoby 0.1f, to czy nie miałoby to takiego skutku, że instrukcja w bloku warunkowym nie wykonałaby się nigdy?
komentarz 26 maja 2016 przez Patrycjerz Mędrzec (182,570 p.)

@draghan

Tak, masz rację, ale zauważ, że obsługą zdarzeń można obarczyć inny wątek.

Podany przez ciebie przykład aktualizacji AI i obiektów jest trochę nietrafiony, gdyż jest wg mnie bezsensowny - taką fanaberie należałoby obsłużyć poprzez zmodyfikowaną pętlę stałokrokową - gdy porzucimy ten pomysł, to Sleep wydaje się całkiem niezłym rozwiązaniem.

Popatrz na metodę setFramerateLimit z SFML, która właśnie używa przerwanie wątku, ograniczając tym samym zużycie procesora (bez tego oscyluje ono w granicach 15-20%, z tym praktycznie spada do zera):

void Window::display()
{
    // Display the backbuffer on screen
    if (setActive())
        m_context->display();

    // Limit the framerate if needed
    if (m_frameTimeLimit != Time::Zero)
    {
        sleep(m_frameTimeLimit - m_clock.getElapsedTime());
        m_clock.restart();
    }
}

Ogólnie temat pętli gier jest bardzo obszerny i trudno opisać wszystko w jednej odpowiedzi. Pętlę można stworzyć na wiele sposób i tak naprawdę od programisty zależy, w jaki sposób ją zaimplementuje, albo czy nie wymyśli w ogóle innego typu, idealnego do jego potrzeb.

@Kyoya

Czy ty w ogóle czytasz, co napisałem? Zmienna czas_1 będzie miała wartość sekund od 1 stycznia 1970 roku, czas_2 zaś troszkę więcej - odejmując te dwa czasy uzyskasz czas obiegu pętli.

komentarz 26 maja 2016 przez draghan VIP (102,850 p.)

Tak, masz rację, ale zauważ, że obsługą zdarzeń można obarczyć inny wątek.

Wątek można wstrzymać i będzie to dobre rozwiązanie. Wstrzymanie procesu już nie.

Podany przez ciebie przykład aktualizacji AI i obiektów jest trochę nietrafiony, gdyż jest wg mnie bezsensowny

Dlaczego uważasz go za bezsensowny? AI w żadnym wypadku nie powinno wykonywać obliczeń co klatkę, a przecież pętla stałokrokowa może bez problemu obsługiwać kilka różnych 'zdarzeń', wymagających obsługi z różnym interwałem.

Podobne pytania

0 głosów
2 odpowiedzi 210 wizyt
pytanie zadane 10 sierpnia 2017 w C i C++ przez WireNess Stary wyjadacz (11,130 p.)
+4 głosów
3 odpowiedzi 917 wizyt
pytanie zadane 30 maja 2016 w C i C++ przez bazinga15 Maniak (54,580 p.)
0 głosów
8 odpowiedzi 10,360 wizyt
Porady nie od parady
Nie wiesz jak poprawnie zredagować pytanie lub pragniesz poznać którąś z funkcji forum? Odwiedź podstronę Pomoc (FAQ) dostępną w menu pod ikoną apteczki.FAQ

63,266 zapytań

109,521 odpowiedzi

228,793 komentarzy

43,506 pasjonatów

Przeglądających: 157
Pasjonatów: 2 Gości: 155

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto dwie polecane książki warte uwagi. Pełną listę znajdziesz tutaj.

...