Skocz do zawartości

[Art] Delta-timing


Rekomendowane odpowiedzi

WSTĘP

Nie drażniło Cię nigdy, że gdy Twoja gra spadnie poniżej 60 fps to automatycznie cała akcja dzieje się wolniej?

Przecież w inne (nie gmowe raczej ;p) gry można spokojnie grać na mniejszym fpsie.

Albo dobra, nie będzie ładnego wstępu.

 

Po przeczytaniu tego tekstu powinieneś bez problemu oprzeć swoją grę o taki właśnie zmienny framerate - deltatime (delta to różnica (wymaga zweryfikowania ;p)) :)

Więc zaczynajmy.

 

MECHANIZM DZIAŁANIA

Najpierw pomyślmy, jak to powinno działać.

Im mniej fps wyrabia gra, tym wszystko powinno się dziać szybciej.

Nie skorzystamy jednak z licznika fps (było by to za mało dokładne, na dodatek gm pokazuje taki, eee, specyficzny fps - nie taki jaki pokaże nam Fraps), a, jak już się pewnie domyśliłeś po nazwie, z różnicy czasu pomiędzy klatkami.

Brzmi strasznie :D ?

Po prostu będziemy liczyli czas, jaki upłynął pomiędzy dwoma klatkami, i na jego podstawie będziemy zmieniali prędkości wszystkich obiektów w grze.

 

KOD

Utwórz sobie obiekt, umieść go w roomie, i nazwij go jak Ci się podoba, powiedzmy obj_deltatime, wstaw do niego taki kod:

GML
//Create

global._dt = 1/60; //W tej zmiennej globalnej bedziemy przechowywali roznice pomiedzy klatkami (w sekundach - czyli dla 60 klatek

// uzyskmy watosc 0.016, i taka tez ustawiamy "na start")

global._dt1 = date_current_time(); // Ta zmienna oznacza czas (normalny czas, pobrany przez date_current_time()) poprzedniej klatki

global._dt2 = date_current_time(); // A ta aktualnej

//Begin Step

global._dt2 = date_current_time()*100000; // Ustawiamy zmienna _dt2 na aktualny czas (mnozymy przez 100000 aby uzyskac wynik w sekundach)

global._dt = global._dt2-global._dt1; // Obliczamy roznice pomiedzy tymi wartosciami - odejmujemy czas z poprzedniej klatki od aktualnego czasu

global._dt1 = global._dt2; // Ustawiamy aktualny czas jako ten z poprzedniej klatki</span></span></span></span></span>

Jak widać, kod jest stosunkowo prosty, chyba nikt nie powinien mieć problemów z jego zrozumieniem.

 

UŻYCIE

Dobrze, mamy obliczoną różnicę w czasie pomiędzy klatkami, co dalej.

Wyobraźmy sobie to.

Mamy sobie platformówkę, w której gracz przy fpsie 60 powinien poruszać się z prędkością 1px/klatkę.

Jak nietrudno się domyślić, przy 30 fpsach natomiast będzie przemieszczał się 2px/klatkę.

 

Ale my nie dysponujemy fpsem, a różnicą czasu pomiędzy klatkami.

Więc przebudujmy poprzedni przykład.

60 fps - 0.016ms - 1px/s

30 fps - 0.032ms - 2px/s

15 fps - 0.064ms - 4px/s

 

Jak nietrudno zauważyć, prędkość gracza rośnie proporcjonalnie do różnicy czasu (różnica czasu zwiększa się dwa razy - prędkość gracza zwiększa się dwa razy).

A więc uniwersalny wzór na prędkość gracza wygląda o tak:

prędkość [px/kl] = prędkość [px/s] * global._dt;

 

Ludek z naszego przykładu ma więc w Stepie kod:

GML
speed = 60 * global._dt;

 

FUNKCJA

Co by sobie wszystko maksymalnie ułatwić (aby nie dodawać do wszystkiego *global._dt) napiszemy sobie funkcję, która będzie po prostu mnożyła podaną w argumencie liczbę przez deltatime. Nazwijmy ją jakoś prosto - np. dt().

GML
return argument0 * global._dt;

Ruch gracza z poprzedniego przykładu będzie więc wyglądał tak:

GML
speed = dt(60); //60 to przypominam predkosc wyrazona w pikselach na sekunde</span></span></span></span></span>

 

Teraz pora na omówienie różnych przykładowych ruchów.

 

1. STAŁA PRĘDKOŚĆ RUCHU

Ten przykład już omówiliśmy, pokażę więc sam kod:

GML
speed = dt(prędkość w pikselach na sekundę);

2. RUCH Z OPÓŹNIENIEM (FRICTION)

To nie będzie wiele bardziej skomplikowane od poprzedniego ruchu - pamiętajcie, aby wszystko, co ma jakiś związek z czasem wpakować w funkcję dt().

Do tego typu ruchu warto także używać już własnej zmiennej odpowiedzialnej za prędkość - nazwijmy ją spd. Będzie ona robiła dokładnie to samo, co gmowe speed, tyle, że będzie przechowywała prędkość wyrażoną w pikselach na sekundę.

Wersja 1.

GML
if(keyboard_check(vk_right))

{

spd += dt(100);

}

else

{

spd -= dt(100);

if(spd<0){ spd=0; }

}

 

x += dt(spd);

Wersja 2. (obie robią to samo, użyj tej, której będzie Ci wygodniej)

GML
if(keyboard_check(vk_right))

{

speed += dt(20);

}

 

friction = dt(10);

Jak widać, nic ciężkiego ;p

 

3. ALARMY

Powiedzmy, że chcemy, aby co półtora sekundy wykonywała się jakaś akcja.

Użycie gmowych alarmów oczywiście wchodzi w grę - musimy napisać własne.

GML
//Create

timer = 1.5; //Czas do wykonania sie alarmu w sekudach

//Step

if(timer <= 0)

{

//akcja

timer += 1.5

}

else

{

timer -= dt(1);

}

Podobny (albo raczej identyczny) kod należy zastosować np. przy strzelaniu.

 

To tyle z ruchów, teraz inna możliwość, którą daje nam użycie deltatime'u.

A mianowicie...

 

BULLET-TIME

Gdy wszystkie prędkości oparliśmy o jedną funkcję, aż grzech nie dodać do tego możliwości spowolnienia akcji gry - o tak, dla zabawy.

Do create obj_deltatime dodajemy:

GML
global.gamespeed = 1; //to jest wlasnie nasza predkosc gry - 1 to dnormalna wartosc, jesli chcemy spowolnic gre 5 razy ustawiamy ja na 0.2</span></span>

Zadeklarowaliśmy jedną zmienną, która będzie odpowiadała za prędkość gry.

 

funkcja dt():

GML
return argument0 * global._dt * global.gamespeed;

Prosty bullet-time do tego to chociażby:

GML
if(keyboard_check(vk_space))

{

global.gamespeed = 0.15;

}

else

{

global.gamespeed = 1;

}

No i było by na tyle.

 

DOWNLOAD

Gotowy do zmerge'owania plik gmk

 

Ed: Naprawione, tagi gml się zrypały.

Odnośnik do komentarza
Udostępnij na innych stronach

Z tym najmniejszą rzecz zmieniać to nie przesadzaj, ja endurenca przepisałem pod to w efektywnie dwie godziny (nad pociskami się męczyłem 80% tego czasu, lol).

W sumie to dodać "dt(" przed i ")" po każdej wartości to nie jakiś super problem.

 

Chociaż to zależy o chcesz przerobić w sumie.

 

A najlepiej to od początku na tym pisać, to nie będzie trzeba nic zmieniać ;d

Odnośnik do komentarza
Udostępnij na innych stronach

No widzisz, nie bardzo jak miał o tym pomyśleć, bo prostszego rozwiązania nie ma, a gm miał być maksymalnie prosty :P

No ewentualnie mógłby dać możliwość rozdzielenie stepów i drawów na osobne wątki gdzie step miałby większy priorytet priorytet, ale to też mogło by w końcu zmulić.

Odnośnik do komentarza
Udostępnij na innych stronach

  • Administratorzy

Muszę to sprawdzić, bo jak ostatnio robiłem na podstawie FPSów, to dla np. 30/60 FPSów było ok, ale dla 2/60 FPS postać zasuwała tak szybko, że jednym kliknięciem wypadała 100 000px poza ekran :D

Odnośnik do komentarza
Udostępnij na innych stronach

gnysku, nie załamuj :C

Odnośnik do komentarza
Udostępnij na innych stronach

mozna tak zrobic.

Tylko o jakie funkcje konkretnie Ci chodzi?

Odnośnik do komentarza
Udostępnij na innych stronach

current_time to nie funkcja a zmienna wbudowana do ktorej GMAPI na chwile obecna nie ma dostepu. Co innego date_current_time(), ale zeby to mialo sens trzeba zrobic to jako extension do gm7 i w niego wpakowac DLL. moge to zrobic wieczorkiem

Odnośnik do komentarza
Udostępnij na innych stronach

Jeśli chcesz dodać odpowiedź, zaloguj się lub zarejestruj nowe konto

Jedynie zarejestrowani użytkownicy mogą komentować zawartość tej strony.

Zarejestruj nowe konto

Załóż nowe konto. To bardzo proste!

Zarejestruj się

Zaloguj się

Posiadasz już konto? Zaloguj się poniżej.

Zaloguj się
  • Ostatnio przeglądający   0 użytkowników

    • Brak zarejestrowanych użytkowników przeglądających tę stronę.
×
×
  • Dodaj nową pozycję...