Chcąc by gra działała prawidłowo na różnych komputerach programista musi brać pod uwagę m. in. różnicę w szybkości działania nie tylko samych komputerów, ale też np. różne częstotliwości odświeżania ekranów zależne od monitora oraz wybranego trybu graficznego.
Kwestię kontroli szybkości działania programu omawiałem w ostatnim odcinku kursu programowania gier na przykładzie gry ZX-Rajd, zwłaszcza w opisie filmu. W tym artykule wyjaśnię w jaki sposób można sprawdzić szybkość działania gry i jak dostosować ją do danego komputera w oparciu o doświadczenia w pisaniu gier Holka i Ping-Pong w języku RC-Basic.
Pierwszą metoda jest wykorzystanie funkcji synchronizacji z odświeżaniem ekranu, w RC Basic jest to komenda Update. Poniżej fragment kodu, który umieszczamy po tej komendzie w głównej pętli gry:
klatki=klatki+1
If hz>60 and Timer-cz < 1200/60 and klatki >= Round(60/(hz-60)) then
klatki=0
update
end if
cz=Timer
Powoduje to ograniczenie szybkości gry do 60 klatek na sekundę poprzez wywoływanie dodatkowych komend Update, które powodują wstrzymanie działania programu do momentu otrzymania kolejnego sygnału synchronizacji pionowej ekranu. Hz to częstotliwość odświeżania ekranu, w RC Basic można ją odczytać stosując komendę GetDesktopDisplayMode. Nawet jeśli jest wyższa niż 60 (i <= 120), gra będzie działać z prędkością około 60 klatek / s (lub hz / 2, jeśli hz> 120).
W Holce zastosowałem trochę zmodyfikowana wersję:
If hz>60 and Timer-cz < 20 and klatki mod (Round(60/(hz-60))+1) = 0 then
Update
End If
cz=Timer
„klatki” oznaczają zmierzoną ilość klatek na sekundę. Można to wyznaczyć np. tak:
If Timer – czas >1000 then
czas=Timer
klatki=0
End If
klatki=klatki+1
Można też wykorzystać funkcję podającą ilość klatek, w RC Basic jest to funkcja FPS, ale w moim przypadku nie podawała dokładnej wartości. Nie udało mi się dowiedzieć w jaki sposób ta funkcja działa.
Innym sposobem kontroli szybkości jest wykorzystanie współczynnika, przez który mnożymy szybkość w naszych obliczeniach ruchu. Parametr ten jest standardowo stosowany w symulacjach ruchu ciała, na które działają siły, np. grawitacji, zwykle oznaczany przez dt. Oznacza on krok czasu o jaki „przeskakujemy” w każdym kroku programu (pętli), im mniejsza wartość tym symulacja jest dokładniejsza, ale wolniejsza. Podstawowe równania ruchu stosujące metodę Eulera wyglądają tak:
s=s+v*dtv=v+a*dt
Są one zgodne z wzorami znanymi z fizyki, gdzie: s – droga, v – prędkość, a – przyspieszenie. Równania te można rozbić na składowe odpowiadające współrzędnym wektora prędkości i przesunięcia. Metodę tę stosowałem w opisywanych wcześniej programach Orbity i Symulator Jazdy. W grze Ping-Pong równania opisujące ruch piłki wyglądają tak (dwa wymiary):
xp=xp+vpx*dt ‚ zmiana położenia w poziomie w zależności od prędkości*krok czasu
yp=yp+vpy*dt ‚ zmiana położenia w pionie
vpy=vpy+9.5*dt-vpy*dt/15+ob*vpx/15*dt ‚ +grawitacja-opór+podkręcenie
vpx=vpx-vpx*dt/15 ‚ opór powietrza
W grze parametry możemy dobrać intuicyjnie, eksperymentalnie. 9.5 to przyspieszenie grawitacji, które w tym modelu działa tylko w pionie, w rzeczywistości wynosi ok. 9.81 m/s^2, obniżyłem je, by zniwelować niedokładności wynikające z dość dużego kroku czasu. Krok czasu wynosi 10/60, gdzie 60 to częstotliwość odświeżania ekranu komputera, na którym tworzyłem grę, a zarazem ilość klatek na sekundę. Współczynnik oporu powietrza to 1/15. Ob to „siła podkręcenia”, ma ona wpływ głównie na prędkość pionową, w tym uproszczonym modelu tylko na nią (w czasie lotu) i jest proporcjonalna do prędkości poziomej lotu, przyjmuje wartość od -pi do pi i też jest dzielona przez 15.
Mając taki model musimy pamiętać o uwzględnianiu współczynnika dt wszędzie, gdzie jest to konieczne (głównie w obliczeniach związanych z prędkością obiektów). Szybkość symulacji (gry) możemy regulować zmieniając wartość tego parametru.
Aby gra działała podobnie na komputerach o różnej szybkości działania musimy odpowiednio dostosować do nich parametr dt, w tym przypadku do ilości generowanych klatek na sekundę, w naszym przykładzie jego wartość ma wynosić 10/ilość klatek. Pozostaje więc tylko ustalić faktyczną ilość tych klatek. Niby prosta sprawa – można zastosować metody omówione wcześniej lub np. dodać w głównej pętli następujący fragment:
dt=(Timer-czas)/100
czas=Timer
Spowoduje to dopasowanie parametru dt w każdym przebiegu pętli, np. 60 razy na sekundę, poprzez obliczenie czasu wykonania danego przebiegu. Tak częsta aktualizacja powoduje jednak problem związany z tym, że komputer może się „zaciąć” np. z powodu działania innego programu, co znacznie wydłuży czas wykonania danego przebiegu pętli, a to spowoduje tak znaczną zmianę parametru dt, że gra zachowa się niepoprawnie. Poprzedni przykład powoduje aktualizację co sekundę, ale to nadal może być za często.
Najprostszym sposobem jest przyjęcie, że ilość klatek jest taka sama jak częstotliwość odświeżania ekranu, ale w przypadku wolnych komputerów to nie musi być prawdą i może spowodować wielokrotne spowolnienie gry.
Na szczęście w Ping-Pongu uruchomiłem na początku demonstracyjną grę komputer-komputer, po zakończeniu której obliczany jest czas działania gry, ilość klatek i do tego dostosowywany parametr dt. Odbywa się to poprzez zapisanie do zmiennej CZAS aktualnego czasu (funkcja TIMER) przed główną pętlą gry:
czas=Timer
a zaraz po zakończeniu pętli aktualizowana jest wartość parametru dt w poniższy sposób:
dt=(Timer-czas)/100/klatki
Pojedynczy set trwa zwykle conajmniej kilka sekund, co już jest czasem na tyle długim, by w znacznym stopniu wyeliminować wspomniany problem. Być może jeszcze lepiej by było sumować czas i ilość klatek kilku lub wszystkich gier…