Game Loops mit SDL
Posted by fkrauthan | Posted in Artikel, Linux, SDL, Windows | Posted on 10-01-2009
Tags: c++, gameloop, gameschleife, SDL, spielschleife, time, tutorial, zeit
4
Die feste Framerate
So, nun haben wir gesehen, wie man das mit der dynamischen Framerate macht. Als nächstes werden wir uns der festen Framerate widmen. Dazu werden die meisten Codefragmente von vorher wieder benötigt.
Die benötigten Variablen
Zuerst brauchen wir natürlich wieder ein paar Variablen, damit das Ganze auch funktoniert.
- //Game Loop mit statischer Framerate
- int m_iUpdateRate = 60; //Unsere Anwendung wird mit 60 FPS laufen
- float m_fSetpSize = 1.0f / iUpdateRate;
- float timeToProcess = 0.0f;
- Uint32 startTime = SDL_GetTicks();
Dieser Code kommt vor unsere Spielschleife. Wenn wir nun eine andere FPS einstellen wollen, müssen wir nur die Variable m_iUpdateRate verändern. Man sollte ihr irgendwas zwischen 30 und 60 geben, wobei 60 in den meisten Fällen das Optimum für Spiele sein dürfte.
Die Variablen aktualisieren
Am Anfang unserer Game Loop schreiben wir folglenden Code:
- while(m_bRun) {
- Uint32 now = SDL_GetTicks();
- float dt = 0.001f * (now - startTime);
- startTime = now;
- timeToProcess += dt;
- timeToProcess = std::min(timeToProcess, 4.0f * m_fStepSize);
Dabei holen wir uns die momentane Zeit. Rechnen die Differenz seit dem letzten Frame in ms aus. Dann rechnen wir die aktuelle Differenz zu unserer vergangenen Zeit hinzu.
Nun können wir unter diese Zeilen auch unseren Renderaufruf starten.
- //Zuerst übermalen wir den Bildschirm mit Schwarz
- SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 0));
- //Hier blitten wir unser Bild an die Position, die wir vorher definiert haben
- SDL_BlitSurface(image, &rImage, screen, &rPosition);
- //Dann aktualisieren wir den Screen, damit wir auch etwas sehen.
- SDL_Flip(screen);
Die Nachrichtenverarbeitung
Nun rendern wir zwar schon, aber noch fehlt uns die Nachrichtenverarbeitung und die Bewegung der Sprites. Das kommt nun.
- while(timeToProcess >= m_fStepSize) {
- //Hier fragen wir alle anstehenden SDL_Events ab
- while(SDL_PollEvent(&event)) {
einfach den kompletten Code unserer SDL_Event-Schleife einfügen. Danach bewegen wir noch unsere Spielfigur.
- }
- //Hier bewegen wir den Spieler um jeweils 1 px in die gedrückte Richtung
- if(bLeft) {
- rPosition.x -= 100*m_fSetpSize;
- }
- else if(bRight) {
- rPosition.x += 100*m_fSetpSize;
- }
- if(bUp) {
- rPosition.y -= 100*m_fSetpSize;
- }
- else if(bDown) {
- rPosition.y += 100*m_fSetpSize;
- }
- timeToProcess -= m_fStepSize;
- }
Hier ist nichts Neues zu sehen. Außer dass wir die Bewegungsgeschwindigkeit nicht durch die FPS dividieren, sondern mit der Framezeit multiplizieren.
Noch eine kleine “CPU-Schonungsoperation”
Momentan arbeitet das System mit voller CPU-Auslastung. Das ist unnötig. Für die übrige Zeit werden wir einfach ein Delay (Verzögerung) implementieren, um die CPU zu schonen. Das sieht dann wie folgt aus:
- }
- double timeLeft = m_fStepSize - timeToProcess;
- if(timeLeft >= 0.001f) {
- SDL_Delay(1);
- }
- }
Gut, das war auch schon unsere neue Main-Routine.
Der komplette Code
- /**
- * @FILE : sdl_gameloop_stat.cpp
- * @AUTOR: Florian Krauthan
- * @DATE : 13.08.2008
- */
- //Der SDL Include Block
- #ifdef WIN32
- #include <SDL.h>
- #include <SDL_image.h>
- #else
- #include <SDL/SDL.h>
- #include <SDL/SDL_image.h>
- #endif
- //Der STD iostream include damit wir mit cout was auf der Console ausgeben können
- #include <iostream>
- int main(int argc, char **argv) {
- //Starten von SDL
- if(SDL_Init(SDL_INIT_VIDEO) < 0 ) {
- return -1;
- }
- //Dafür sagen das SDL_Quit beim Beenden aufgerufen wird
- atexit(SDL_Quit);
- //Hier erzeugen wir ein Fenster mit 800x600x32 mit DoubleBuf Support. Wollen wir noch Fullscreen haben hängen wir einfach an SDL_DOUBLEBUF ein " | SDL_FULLSCREEN" an
- SDL_Surface *screen = SDL_SetVideoMode(800, 600, 32, SDL_DOUBLEBUF);
- if(screen == NULL) {
- //Sollte ein Fehler beim erstellen des SDL Fensters aufgetreten sein geben wir die SDL Fehler Meldung zurück und beenden das Programm
- std::cout << "Konnte SDL Fenster nicht erzeugen: " << std::endl << SDL_GetError() << std::endl;
- return -1;
- }
- //Nun setzen wir den Applikation Titel
- SDL_WM_SetCaption("SDL - Gameloop","SDL - Gameloop");
- //Hier laden wir unser Bild mit SDL_Image
- SDL_Surface *image = IMG_Load("sdl_example2_tux.jpg");
- if(image == NULL) {
- //Sollte er das Bild nicht laden können
- std::cout << "Konnte das Bild nicht laden: " << std::endl << SDL_GetError() << std::endl;
- return -1;
- }
- //Hier speichern wie die Abmessung für unser Image in einem SDL_Rect
- SDL_Rect rImage;
- rImage.x = 0;
- rImage.y = 0;
- rImage.w = image->w;
- rImage.h = image->h;
- //So und hier drinnen Speichern wir die Position von Unserem Image
- SDL_Rect rPosition = rImage;
- //Die Event struktur um SDL_Events auszuwerten
- SDL_Event event;
- //Unser Variable die sorgt das unser Fenster erstmal offen bleibt
- bool bRun = true;
- //Speichern wohin er sich bewgen soll
- bool bLeft = false;
- bool bRight = false;
- bool bUp = false;
- bool bDown = false;
- //Gameloop mit Statischer Framerate
- int iUpdateRate = 60; //Unsere APP wird mit 60 FPS laufen
- float fStepSize = 1.0f / iUpdateRate;
- float timeToProcess = 0.0f;
- Uint32 startTime = SDL_GetTicks();
- while(bRun) {
- Uint32 now = SDL_GetTicks();
- float dt = 0.001f * (now - startTime);
- startTime = now;
- timeToProcess += dt;
- timeToProcess = std::min(timeToProcess, 4.0f * fStepSize);
- //Zuerst übermalen wir den Bildschirm mit Schwarz
- SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 0));
- //Hier Blitten wir unser Bild an die Position die wir vorher definirt haben
- SDL_BlitSurface(image, &rImage, screen, &rPosition);
- //Dann Upadaten wir den Screen, das wir auch was sehen.
- SDL_Flip(screen);
- while(timeToProcess >= fStepSize) {
- //Hier fragen wir alle anstehendn SDL_Events ab
- while(SDL_PollEvent(&event)) {
- //Und gehen diese durch
- switch(event.type){
- case SDL_KEYDOWN:
- //Eine Taste wurde gedrückt nun müssen wir feststellen welche taste
- if(event.key.keysym.sym==SDLK_LEFT) {
- //Pfeiltaste Links wurde gedrückt
- bLeft = true;
- }
- else if(event.key.keysym.sym==SDLK_RIGHT) {
- //Pfeiltaste Rechts wurde gedrückt
- bRight = true;
- }
- else if(event.key.keysym.sym==SDLK_DOWN) {
- //Pfeiltaste Oben wurde gedrückt
- bDown = true;
- }
- else if(event.key.keysym.sym==SDLK_UP) {
- //Pfeiltaste Unten wurde gedrückt
- bUp = true;
- }
- if(event.key.keysym.sym==SDLK_ESCAPE) {
- //ESCAPE wurde gedrückt. Also Beenden wir alles
- bRun = false;
- }
- break;
- case SDL_KEYUP:
- //Eine Taste wurde losgelassen nun müssen wir feststellen welche taste
- if(event.key.keysym.sym==SDLK_LEFT) {
- //Pfeiltaste Links wurde losgelassen
- bLeft = false;
- }
- else if(event.key.keysym.sym==SDLK_RIGHT) {
- //Pfeiltaste Rechts wurde losgelassen
- bRight = false;
- }
- else if(event.key.keysym.sym==SDLK_DOWN) {
- //Pfeiltaste Oben wurde losgelassen
- bDown = false;
- }
- else if(event.key.keysym.sym==SDLK_UP) {
- //Pfeiltaste Unten wurde losgelassen
- bUp = false;
- }
- break;
- case SDL_QUIT:
- //Sollte das Fenster geschlosen werden, soll er auch die Loop beenden
- bRun = false;
- break;
- default:
- break;
- }
- }
- //Hier bewegen wir den Spieler um jewiel 1 px in die gedrückte richtung
- if(bLeft) {
- rPosition.x -= 100*fStepSize;
- }
- else if(bRight) {
- rPosition.x += 100*fStepSize;
- }
- if(bUp) {
- rPosition.y -= 100*fStepSize;
- }
- else if(bDown) {
- rPosition.y += 100*fStepSize;
- }
- timeToProcess -= fStepSize;
- }
- double timeLeft = fStepSize - timeToProcess;
- if(timeLeft >= 0.001f) {
- SDL_Delay(1);
- }
- }
- //Hier müssen wir noch unser Bild nach der Schleife wieder freigeben
- SDL_FreeSurface(image);
- return 0;
- }


(2 votes, average: 4.50 out of 5)
hi, warum steht c++ bei den tags wenn du komplett in c schreibst?
gibts nen grund dafür die fps zu begrenzen?
mfg nacho
Naja im Grunde kannst du auch C++ nehmen daher im Tag c++. SDL ist halt einfach eine C Lib. Der einzige Grund ist das du bei einer begrenzten FPS keine FPS Schwankungen hast. Das heißt wenn du sie unbegrenzt hast und der immer mal wieder länger und kürzer braucht für ein Frame dann merkt man das teilweise. Wenn man sie nun fest beschränkt dann ist die Gefahr solcher Schwankungen geringer, da man die Grenze meist relativ tief setzt.
Wo genau ist der Unterschied zwischen der Ersten und der Dritten Variante? Im Grunde arbeiten beide Verfahren mit einem Faktor, der einfach von der Zeit abhängig ist. Wenn ein Frame länger braucht, ist die Entfernung größer. Diese Abhängigkeit ist bei beiden vorhanden.
Ich hatte es bei meinen Spielen auch wie in der dritten Lösung gemacht. Es gibt einen Zeitfaktor, der bei gewünschter Framerate 1 ist, bei langsamen rechnern >1 und bei schnellen Rechnern <1. Alle Bewegung wird damit multipliziert. So sieht es dann flüssig aus.
Einer der größten unterschied zwischen Version1 und Version3 ist, dass man bei der Version1 eine Division braucht für die Bewegung während Version3 mit Multiplizieren arbeitet. Das macht einen gewaltigen Geschwindigkeitsunterschied aus.