Niezależny ruch ramki

11

Przeczytałem tutaj dwa inne wątki dotyczące ruchu: Ruch oparty na czasie Vs Ruch oparty na liczbie klatek na sekundę? , i kiedy powinienem zastosować stały lub zmienny krok czasu?

ale myślę, że brakuje mi podstawowej wiedzy na temat ruchu niezależnego od ramki, ponieważ nie rozumiem, o czym mówi którykolwiek z tych wątków.

Śledzę samouczki SDL firmy Lazyfoo i przyszedłem do lekcji niezależnej od ramki. http://lazyfoo.net/SDL_tutorials/lesson32/index.php

Nie jestem pewien, co próbuje powiedzieć część dotycząca ruchu w kodzie, ale myślę, że jest to (popraw mnie, jeśli się mylę): Aby mieć ruch niezależny od ramki, musimy dowiedzieć się, jak daleko obiekt ( np. duszek) porusza się w określonym przedziale czasowym, na przykład 1 sekunda. Jeśli kropka porusza się z prędkością 200 pikseli na sekundę, muszę obliczyć, jak dużo się porusza w tej sekundzie, mnożąc 200 pps przez 1/1000 sekundy.

Czy to prawda? Lekcja mówi:

„prędkość w pikselach na sekundę * czas od ostatniej klatki w sekundach. Więc jeśli program działa z prędkością 200 klatek na sekundę: 200 pps * 1/200 sekund = 1 piksel”

Ale ... Myślałem, że pomnożymy 200 pps przez 1/1000 sekundy. Co to za biznes z liczbą klatek na sekundę?

Byłbym wdzięczny, gdyby ktoś mógł dać mi bardziej szczegółowe wyjaśnienie, jak działa ruch niezależny od ramki.

Dziękuję Ci.

DODANIE:

SDL_Rect posRect;
posRect.x = 0;
posRect.y = 0;

float y, yVel;
y = 0;
yVel = 0;

Uint32 startTicks = SDL_GetTicks();

bool quit = false;
SDL_Event gEvent;

while ( quit == false )
{
    while ( SDL_PollEvent( &gEvent ) )
    {
        if ( gEvent.type == SDL_QUIT )
            quit = true;
    }

    if ( y <= 580 )
    {
        yVel += DOT_VEL;
        y += (yVel * (SDL_GetTicks() - startTicks)/1000.f);
        posRect.y = (int)y;
    }

    startTicks = SDL_GetTicks();
    SDL_BlitSurface( bg, NULL, screen, NULL );
    SDL_BlitSurface( dot, NULL, screen, &posRect );
    SDL_Flip( screen );
}

To kod, który przesuwa kropkę w dół ekranu. Myślę, że mam wszystko do tej pory prawidłowe. Porusza się w dół ekranu, ale dzieje się coś dziwnego, czego nie potrafię wyjaśnić. Kropka ma pozostać na poziomie y = 580, gdy osiągnie wartość wyższą niż ta wartość y. Jednak za każdym razem, gdy uruchamiam program, kropka kończy się w innym miejscu, co oznacza nieco więcej niż 580, więc kropka znajduje się w połowie lub więcej niż w połowie ekranu (kropka to 20 pikseli, ekran wymiary 800 x 600). Jeśli zrobię coś takiego, jak kliknięcie i przytrzymanie paska tytułu programu, a następnie zwolnienie, kropka zniknie z ekranu. Dlaczego jest zmienna za każdym razem? Jeśli chodzi o problem z paskiem tytułowym, myślę, że to dlatego, że gdy trzymam się paska tytułowego, stoper nadal działa, a upływ czasu staje się większy, co skutkuje większą odległością kropka przesuwa się w następnej klatce. Czy to prawda?

Krewetki Krakersy
źródło
Twój dodatek to właściwie kolejne pytanie. Powinieneś uczynić z tego drugie pytanie zamiast dodawać je do już istniejącego. Można jednak łatwo odpowiedzieć: wystarczy obliczyć ruch y, np. yMovement = (yVel * (SDL_GetTicks() - startTicks)/1000.f);następnie wykonaj:if(y + yMovement <= 580){ y += yMovement; } else { y = 580; }
bummzack

Odpowiedzi:

10

UWAGA: Wszystkie ułamki mają być zmiennoprzecinkowe.

Ruch niezależny od ramki działa na podstawie ruchu poza czasem. Otrzymujesz ilość czasu, który spędzasz od ostatniej klatki (więc jeśli jest 60 klatek na sekundę, każda klatka zajmuje 1,0 / 60,0 sekund, jeśli wszystkie klatki zajęły tyle samo czasu) i dowiedz się, ile ruchu to przekłada się na.

Jeśli chcesz, aby twoja istota poruszała się o określoną ilość miejsca przez określoną jednostkę czasu (powiemy 100 pikseli na każdą sekundę), możesz dowiedzieć się, ile pikseli powinieneś poruszyć na klatkę, mnożąc ilość ruchu na sekundę (100 pikseli) o czas upływający w sekundach (1,0 / 60,0), aby dowiedzieć się, ile ruchu powinno nastąpić w bieżącej klatce.

Działa poprzez ustalenie, ile ruchu należy wykonać na klatkę, wykorzystując upływający czas i prędkość, która jest zdefiniowana z określoną jednostką czasu (preferowane są sekundy lub milisekundy). Twoje obliczenia mogą wyglądać następująco:movementPerSecond * (timeElapsedInMilliseconds / 1000.0)

Mam nadzieję, że miało to dla ciebie jakiś sens.

Chcę przesunąć mojego faceta o 200 pikseli w prawo co sekundę. Jeśli bieżąca klatka jest uruchamiana 50 milisekund po poprzedniej klatce, powinienem przesunąć mojego faceta o ułamek wcześniej określonej prędkości (która wynosiła 200 pikseli). Powinienem go przesunąć o 50/1000 dystansu, ponieważ minęła tylko 1/20 (50/1000 = 1/20) czasu. Dlatego sensowne byłoby przesunięcie go tylko o 10 pikseli (gdyby pojawiło się 19 dodatkowych klatek, 50 milisekund od siebie, wówczas łączna ilość ruchu w tej sekundzie wynosiłaby 200 pikseli, czyli tyle, ile chcieliśmy).

Sposób działania niezależnego ruchu ramki polega na tym, że ramki zwykle występują w różnych krokach czasowych (między kolejnymi ramkami zachodzi inny czas). Jeśli ciągle poruszamy byt o stałą odległość w każdej klatce, wówczas ruch oparty jest na częstotliwości klatek. Jeśli pomiędzy klatkami jest dużo czasu, gra będzie wydawać się zbyt wolna, a jeśli nie będzie dużo czasu między klatkami, gra będzie wydawać się szybka. (zbyt mało czasu między klatkami = dużo klatek = większy ruch) Aby temu zaradzić, używamy prędkości pod względem czasu i monitorujemy czas między klatkami. W ten sposób wiemy, ile czasu minęło, odkąd ostatnio zaktualizowaliśmy pozycję i jak daleko powinniśmy przesunąć byt.

Klatki na sekundę: Liczba klatek na sekundę. Zazwyczaj liczba klatek na sekundę określa, ile razy gra jest rysowana / renderowana na sekundę, lub ile razy pętla gry zostaje ukończona na sekundę.

Naprawiono krok zmiennej zmiennej wierszowej: Odnosi się do ilości czasu między ramkami. Zwykle czas między ramkami nie będzie stały. Niektóre systemy / rdzenie, takie jak fizyka, będą potrzebowały pewnej jednostki czasu, aby coś zasymulować / uruchomić. Zwykle układy fizyki są bardziej stabilne / skalowalne, jeśli krok czasowy jest ustalony. Różnica między stałymi / zmiennymi krokami czasowymi jest w nazwach. Brzmią one według ustalonych przedziałów czasowych: przedziały czasowe występujące w ustalonym tempie. Zmienne stopnie czasowe to stopnie czasowe występujące w różnym / różnym tempie.

Michael Coleman
źródło
W podanym przykładzie 50 milisekund to czas dla każdej klatki, prawda? A to zostało obliczone przez 1000 / FPS? A więc ruch, który należy wykonać, aby każda klatka wynosiła piksele na sekundę * 50/1000?
ShrimpCrackers
hm, zdałem sobie jednak sprawę, że milisekundy dla każdej ramy czasowej byłyby prawdopodobnie zmienne, prawda? Coś jak getTicks () - startTicks zawsze będzie inny i nie będzie stały.
ShrimpCrackers
@Omnion: Jeśli określisz odległość w „pikselach na sekundę”, nie możesz użyć milisekund ... powinna ona wynosić 1,0 / 60,0, a nie 1000/60, co spowodowałoby coś zupełnie innego.
bummzack
@ShrimpCrackers tak, upływający czas się zmienia. Wyobraź sobie starszy komputer, który nie jest w stanie renderować 60 fps. Nadal chcesz, aby gra działała z taką samą prędkością (ale nie taką samą liczbą klatek na sekundę) na takim komputerze.
bummzack,
więc w samouczku lazyfoo, co oznacza 1000 w deltaticks / 1000.f? FPS? 1000 milisekund? Jestem teraz trochę zdezorientowany. Wygląda na to, że FPS jest niezbędny do określenia czasu wymaganego dla każdej klatki, ale w rzeczywistości nie jest uwzględniany w ruchu.
ShrimpCrackers
7

W dynamice klatek Twój kod (na przykład) przeniesienia bytu wyglądałby następująco:

x = x + speedPerFrame

Jeśli chcesz być niezależny od ramek, może wyglądać następująco:

x = x + speedPerSecond * secondsElapsedSinceLastFrame
Wouter Lievens
źródło
dzięki, to ma sens. Mam inne pytanie powyżej.
ShrimpCrackers
1

Odnośnie dodatkowego pytania.

Twój punkt zatrzymuje się za każdym razem w różnych lokalizacjach, ponieważ nie sprawdzasz granicy (y> 580) podczas jej przesuwania. Przestajesz go aktualizować dopiero, gdy przekroczy 580.

Na ostatniej klatce przed przekroczeniem 580 możesz zacząć od 579 lub 570 lub 100. Możesz także przesuwać się o 1 piksel do przodu lub 1000, w zależności od tego, jak długo zajęła ostatnia klatka.

Po prostu zmień swój stan IF na coś takiego i powinieneś być w porządku.

if ( y <= 580 )
{
    yVel += DOT_VEL;
    y += (yVel * (SDL_GetTicks() - startTicks)/1000.f);
    if( y > 580 )
    {
        y = 580;
    }
    posRect.y = (int)y;
}
Tim O'Neil
źródło