Game Loops mit SDL

Drucke diesen Post This page as PDF Posted by fkrauthan | Posted in Artikel, Linux, SDL, Windows | Posted on 10-01-2009

Tags: , , , , , , ,

1 Star2 Stars3 Stars4 Stars5 Stars (2 votes, average: 4.50 out of 5)
Loading ... Loading ...

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.

Code: C++ | Plain Text
  1.     //Game Loop mit statischer Framerate
  2.     int m_iUpdateRate = 60; //Unsere Anwendung wird mit 60 FPS laufen
  3.  
  4.     float m_fSetpSize = 1.0f / iUpdateRate;
  5.     float timeToProcess = 0.0f;
  6.     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:

Code: C++ | Plain Text
  1.     while(m_bRun) {
  2.         Uint32 now = SDL_GetTicks();
  3.         float dt = 0.001f * (now - startTime);
  4.         startTime = now;
  5.  
  6.         timeToProcess += dt;
  7.         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.

Code: C++ | Plain Text
  1.         //Zuerst übermalen wir den Bildschirm mit Schwarz
  2.         SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 0));
  3.         //Hier blitten wir unser Bild an die Position, die wir vorher definiert haben
  4.         SDL_BlitSurface(image, &rImage, screen, &rPosition);
  5.         //Dann aktualisieren wir den Screen, damit wir auch etwas sehen.
  6.         SDL_Flip(screen);

Die Nachrichtenverarbeitung
Nun rendern wir zwar schon, aber noch fehlt uns die Nachrichtenverarbeitung und die Bewegung der Sprites. Das kommt nun.

Code: C++ | Plain Text
  1. while(timeToProcess >= m_fStepSize) {
  2.             //Hier fragen wir alle anstehenden SDL_Events ab
  3.             while(SDL_PollEvent(&event)) {

einfach den kompletten Code unserer SDL_Event-Schleife einfügen. Danach bewegen wir noch unsere Spielfigur.

Code: C++ | Plain Text
  1.             }
  2.             //Hier bewegen wir den Spieler um jeweils 1 px in die gedrückte Richtung
  3.             if(bLeft) {
  4.                 rPosition.x -= 100*m_fSetpSize;
  5.             }
  6.             else if(bRight) {
  7.                 rPosition.x += 100*m_fSetpSize;
  8.             }
  9.             if(bUp) {
  10.                 rPosition.y -= 100*m_fSetpSize;
  11.             }
  12.             else if(bDown) {
  13.                 rPosition.y += 100*m_fSetpSize;
  14.             }
  15.  
  16.             timeToProcess -= m_fStepSize;
  17.         }

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:

Code: C++ | Plain Text
  1.         }
  2.  
  3.         double timeLeft = m_fStepSize - timeToProcess;
  4.         if(timeLeft >= 0.001f) {
  5.             SDL_Delay(1);
  6.         }
  7.     }

Gut, das war auch schon unsere neue Main-Routine.

Der komplette Code

Code: C++ | Plain Text
  1. /**
  2.  * @FILE : sdl_gameloop_stat.cpp
  3.  * @AUTOR: Florian Krauthan
  4.  * @DATE : 13.08.2008
  5. */
  6.  
  7. //Der SDL Include Block
  8. #ifdef WIN32
  9. #include <SDL.h>
  10. #include <SDL_image.h>
  11. #else
  12. #include <SDL/SDL.h>
  13. #include <SDL/SDL_image.h>
  14. #endif
  15.  
  16. //Der STD iostream include damit wir mit cout was auf der Console ausgeben können
  17. #include <iostream>
  18.  
  19. int main(int argc, char **argv) {
  20.     //Starten von SDL
  21.     if(SDL_Init(SDL_INIT_VIDEO) < 0 ) {
  22.         return -1;
  23.     }
  24.     //Dafür sagen das SDL_Quit beim Beenden aufgerufen wird
  25.     atexit(SDL_Quit);
  26.  
  27.     //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
  28.     SDL_Surface *screen = SDL_SetVideoMode(800, 600, 32, SDL_DOUBLEBUF);
  29.     if(screen == NULL) {
  30.         //Sollte ein Fehler beim erstellen des SDL Fensters aufgetreten sein geben wir die SDL Fehler Meldung zurück und beenden das Programm
  31.         std::cout << "Konnte SDL Fenster nicht erzeugen: " << std::endl << SDL_GetError() << std::endl;
  32.         return -1;
  33.     }
  34.  
  35.     //Nun setzen wir den Applikation Titel
  36.     SDL_WM_SetCaption("SDL - Gameloop","SDL - Gameloop");
  37.  
  38.     //Hier laden wir unser Bild mit SDL_Image
  39.     SDL_Surface *image = IMG_Load("sdl_example2_tux.jpg");
  40.     if(image == NULL) {
  41.         //Sollte er das Bild nicht laden können
  42.         std::cout << "Konnte das Bild nicht laden: " << std::endl << SDL_GetError() << std::endl;
  43.  
  44.         return -1;
  45.     }
  46.  
  47.     //Hier speichern wie die Abmessung für unser Image in einem SDL_Rect
  48.     SDL_Rect rImage;
  49.     rImage.x = 0;
  50.     rImage.y = 0;
  51.     rImage.w = image->w;
  52.     rImage.h = image->h;
  53.  
  54.     //So und hier drinnen Speichern wir die Position von Unserem Image
  55.     SDL_Rect rPosition = rImage;
  56.  
  57.  
  58.     //Die Event struktur um SDL_Events auszuwerten
  59.     SDL_Event event;
  60.     //Unser Variable die sorgt das unser Fenster erstmal offen bleibt
  61.     bool bRun = true;
  62.  
  63.     //Speichern wohin er sich bewgen soll
  64.     bool bLeft  = false;
  65.     bool bRight     = false;
  66.     bool bUp    = false;
  67.     bool bDown  = false;
  68.  
  69.     //Gameloop mit Statischer Framerate
  70.     int iUpdateRate = 60; //Unsere APP wird mit 60 FPS laufen
  71.  
  72.     float fStepSize = 1.0f / iUpdateRate;
  73.     float timeToProcess = 0.0f;
  74.     Uint32 startTime = SDL_GetTicks();
  75.  
  76.     while(bRun) {
  77.         Uint32 now = SDL_GetTicks();
  78.         float dt = 0.001f * (now - startTime);
  79.         startTime = now;
  80.  
  81.         timeToProcess += dt;
  82.         timeToProcess = std::min(timeToProcess, 4.0f * fStepSize);
  83.  
  84.         //Zuerst übermalen wir den Bildschirm mit Schwarz
  85.         SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 0));
  86.         //Hier Blitten wir unser Bild an die Position die wir vorher definirt haben
  87.         SDL_BlitSurface(image, &rImage, screen, &rPosition);
  88.         //Dann Upadaten wir den Screen, das wir auch was sehen.
  89.         SDL_Flip(screen);
  90.  
  91.         while(timeToProcess >= fStepSize) {
  92.             //Hier fragen wir alle anstehendn SDL_Events ab
  93.             while(SDL_PollEvent(&event)) {
  94.                 //Und gehen diese durch
  95.                 switch(event.type){
  96.                     case SDL_KEYDOWN:
  97.                         //Eine Taste wurde gedrückt nun müssen wir feststellen welche taste
  98.                         if(event.key.keysym.sym==SDLK_LEFT) {
  99.                             //Pfeiltaste Links wurde gedrückt
  100.                             bLeft = true;
  101.                         }
  102.                         else if(event.key.keysym.sym==SDLK_RIGHT) {
  103.                             //Pfeiltaste Rechts wurde gedrückt
  104.                             bRight = true;
  105.                         }
  106.                         else if(event.key.keysym.sym==SDLK_DOWN) {
  107.                             //Pfeiltaste Oben wurde gedrückt
  108.                             bDown = true;
  109.                         }
  110.                         else if(event.key.keysym.sym==SDLK_UP) {
  111.                             //Pfeiltaste Unten wurde gedrückt
  112.                             bUp = true;
  113.                         }
  114.                         if(event.key.keysym.sym==SDLK_ESCAPE) {
  115.                             //ESCAPE wurde gedrückt. Also Beenden wir alles
  116.                             bRun = false;
  117.                         }
  118.                         break;
  119.                     case SDL_KEYUP:
  120.                         //Eine Taste wurde losgelassen nun müssen wir feststellen welche taste
  121.                         if(event.key.keysym.sym==SDLK_LEFT) {
  122.                             //Pfeiltaste Links wurde losgelassen
  123.                             bLeft = false;
  124.                         }
  125.                         else if(event.key.keysym.sym==SDLK_RIGHT) {
  126.                             //Pfeiltaste Rechts wurde losgelassen
  127.                             bRight = false;
  128.                         }
  129.                         else if(event.key.keysym.sym==SDLK_DOWN) {
  130.                             //Pfeiltaste Oben wurde losgelassen
  131.                             bDown = false;
  132.                         }
  133.                         else if(event.key.keysym.sym==SDLK_UP) {
  134.                             //Pfeiltaste Unten wurde losgelassen
  135.                             bUp = false;
  136.                         }
  137.                         break;
  138.                     case SDL_QUIT:
  139.                         //Sollte das Fenster geschlosen werden, soll er auch die Loop beenden
  140.                         bRun = false;
  141.                         break;
  142.                     default:
  143.                         break;
  144.                 }
  145.  
  146.                
  147.             }
  148.             //Hier bewegen wir den Spieler um jewiel 1 px in die gedrückte richtung
  149.             if(bLeft) {
  150.                 rPosition.x -= 100*fStepSize;
  151.             }
  152.             else if(bRight) {
  153.                 rPosition.x += 100*fStepSize;
  154.             }
  155.             if(bUp) {
  156.                 rPosition.y -= 100*fStepSize;
  157.             }
  158.             else if(bDown) {
  159.                 rPosition.y += 100*fStepSize;
  160.             }
  161.  
  162.             timeToProcess -= fStepSize;
  163.         }
  164.  
  165.         double timeLeft = fStepSize - timeToProcess;
  166.         if(timeLeft >= 0.001f) {
  167.             SDL_Delay(1);
  168.         }      
  169.     }
  170.  
  171.     //Hier müssen wir noch unser Bild nach der Schleife wieder freigeben
  172.     SDL_FreeSurface(image);
  173.  
  174.     return 0;
  175. }

Seite: 1 2 3 4 Alle

Comments (4)

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.

Write a comment