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

«»

Das Grundgerüst für unsere Game Loops
Zuerst werde ich euch hier mal ein wenig Code hinklatschen, mit dem wir dann unsere Game Loops testen können.

Code: C++ | Plain Text
  1. //Der SDL Include-Block
  2. #ifdef WIN32
  3. #include <SDL.h>
  4. #include <SDL_image.h>
  5. #else
  6. #include <SDL/SDL.h>
  7. #include <SDL/SDL_image.h>
  8. #endif
  9.  
  10. //Der STD iostream-Include, damit wir mit cout etwas auf der Konsole ausgeben können
  11. #include <iostream>
  12.  
  13. int main(int argc, char **argv) {
  14.     //Starten von SDL
  15.     if(SDL_Init(SDL_INIT_VIDEO) < 0 ) {
  16.         return -1;
  17.     }
  18.     //Dafür sorgen, dass SDL_Quit beim Beenden aufgerufen wird
  19.     atexit(SDL_Quit);
  20.  
  21.     //Hier erzeugen wir ein Fenster mit 800x600x32 und DoubleBuf-Support. Wollen wir noch Fullscreen haben, hängen wir einfach an SDL_DOUBLEBUF ein " | SDL_FULLSCREEN" an
  22.     SDL_Surface *screen = SDL_SetVideoMode(800, 600, 32, SDL_DOUBLEBUF);
  23.     if(screen == NULL) {
  24.         //Sollte ein Fehler beim Erstellen des SDL-Fensters aufgetreten sein, geben wir die SDL-Fehlermeldung zurück und beenden das Programm
  25.         std::cout << "Konnte-SDL Fenster nicht erzeugen: " << std::endl << SDL_GetError() << std::endl;
  26.         return -1;
  27.     }
  28.  
  29.     //Nun setzen wir den Anwendungs-Titel
  30.     SDL_WM_SetCaption("SDL - Gameloop","SDL - Gameloop");
  31.  
  32.     //Hier laden wir unser Bild mit SDL_Image
  33.     SDL_Surface *image = IMG_Load("sdl_example2_tux.jpg");
  34.     if(image == NULL) {
  35.         //Sollte er das Bild nicht laden können
  36.         std::cout << "Konnte das Bild nicht laden: " << std::endl << SDL_GetError() << std::endl;
  37.  
  38.         return -1;
  39.     }
  40.  
  41.     //Hier speichern wir die Abmessungen für unser Image in einem SDL_Rect
  42.     SDL_Rect rImage;
  43.     rImage.x = 0;
  44.     rImage.y = 0;
  45.     rImage.w = image->w;
  46.     rImage.h = image->h;
  47.  
  48.     //Hier speichern wir die Position von unserem Image
  49.     SDL_Rect rPosition = rImage;
  50.  
  51.     //Die Event-Struktur, um SDL_Events auszuwerten
  52.     SDL_Event event;
  53.     //Unsere Variable die dafür sorgt, dass unser Fenster erstmal geöffnet bleibt
  54.     bool bRun = true;
  55.  
  56.     //Speichern, wohin es sich bewegen soll
  57.     bool bLeft  = false;
  58.     bool bRight     = false;
  59.     bool bUp    = false;
  60.     bool bDown  = false;
  61.  
  62.     /**
  63.      * Hier kommt unser Game Loop später hin
  64.      */
  65.  
  66.     //Hier müssen wir noch unser Bild nach der Schleife wieder freigeben
  67.     SDL_FreeSurface(image);
  68.  
  69.     return 0;

Wie ihr sehen könnt, werden wir wieder ein Bild laden und dieses dann bewegen. Als Bild wird uns wieder ein Pinguin dienen.

Unser Tux

Unser Tux

Was ist an einer einfachen while-Schleife schlecht?
Zunächst müssen wir klären, was an unserem ersten Ansatz falsch ist: Eine einfache while-Schleife, in der wir die Spiellogik Frame für Frame erneut ausführen. Das Problem ist, dass wir die Zeit außer achtlassen. Ohne Berücksichtigung der verstrichenen Zeit wird unsere while-Schleife auf jedem PC mit anderer Geschwindigkeit ablaufen, je nach Leistung der Hardware sowie Auslastung durch andere Prozesse, die auf einem Anwendungsrechner in der Regel nebenher laufen (Virenscanner, Network Guards, …).

Game Loop mit dynamischer Framerate
Wir werden als erstes mal einen Game Loop mit dynamischer Framerate entwickeln. Dazu brauchen wir die Funktion SDL_GetTicks(); Diese gibt uns die Zeit seitdem unsere Anwendung SDL gestartet hat in Millisekunden (ms) zurück. Also perfekt, um die Zeit zu messen.

Also definieren wir zuerst ein paar benötigte Variablen:

Code: C++ | Plain Text
  1.     //Game Loop mit dynamischer Framerate
  2.     long m_lLastTick = SDL_GetTicks();
  3.     int m_iFPSTickCounter = 0;
  4.     int m_iFPSCounter = 0;
  5.     int m_iCurrentFPS = 1;
  6.     int m_iElapsedTicks = 0;
  7.  
  8.     while(m_bRun) {

Später wird dann m_iCurrentFPS unsere aktuelle Framerate enthalten. Dieser Code wird einfach nach dem Kommentar, dass hier der Game Loop kommt, eingefügt. Wozu wir die einzelnen Parameter benötigen gehe ich später noch genauer ein.

Unsere while-Schleife mit der Verarbeitung der SDL-Events

Code: C++ | Plain Text
  1. while(bRun) {
  2.         //Hier fragen wir alle anstehenden SDL_Events ab
  3.         while(SDL_PollEvent(&event)) {
  4.             //Und gehen diese durch
  5.             switch(event.type){
  6.                 case SDL_KEYDOWN:
  7.                     //Eine Taste wurde gedrückt. Nun müssen wir feststellen, welche Taste
  8.                     if(event.key.keysym.sym==SDLK_LEFT) {
  9.                         //Pfeiltaste Links wurde gedrückt
  10.                         bLeft = true;
  11.                     }
  12.                     else if(event.key.keysym.sym==SDLK_RIGHT) {
  13.                         //Pfeiltaste Rechts wurde gedrückt
  14.                         bRight = true;
  15.                     }
  16.                     else if(event.key.keysym.sym==SDLK_DOWN) {
  17.                         //Pfeiltaste Oben wurde gedrückt
  18.                         bDown = true;
  19.                     }
  20.                     else if(event.key.keysym.sym==SDLK_UP) {
  21.                         //Pfeiltaste Unten wurde gedrückt
  22.                         bUp = true;
  23.                     }
  24.                     if(event.key.keysym.sym==SDLK_ESCAPE) {
  25.                         //ESCAPE wurde gedrückt. Also beenden wir alles
  26.                         bRun = false;
  27.                     }
  28.                     break;
  29.                 case SDL_KEYUP:
  30.                     //Eine Taste wurde losgelassen. Nun müssen wir feststellen, welche Taste
  31.                     if(event.key.keysym.sym==SDLK_LEFT) {
  32.                         //Pfeiltaste Links wurde losgelassen
  33.                         bLeft = false;
  34.                     }
  35.                     else if(event.key.keysym.sym==SDLK_RIGHT) {
  36.                         //Pfeiltaste Rechts wurde losgelassen
  37.                         bRight = false;
  38.                     }
  39.                     else if(event.key.keysym.sym==SDLK_DOWN) {
  40.                         //Pfeiltaste Oben wurde losgelassen
  41.                         bDown = false;
  42.                     }
  43.                     else if(event.key.keysym.sym==SDLK_UP) {
  44.                         //Pfeiltaste Unten wurde losgelassen
  45.                         bUp = false;
  46.                     }
  47.                     break;
  48.                 case SDL_QUIT:
  49.                     //Sollte das Fenster geschlossen werden, soll auch der Loop beendet werden
  50.                     bRun = false;
  51.                     break;
  52.                 default:
  53.                     break;
  54.             }
  55.         }
  56.  
  57.         //Hier werden wir die Zeitabfrage einbauen
  58.     }

Gut, das war wieder jede Menge Code. Aber was solls. Nun müssen wir uns um den wichtigen Teil in unserer Game Loop kümmern. Wir müssen die FPS ausrechnen, um anhand der FPS dann unseren Pinguin zu bewegen.

Die FPS ausrechnen
So, erstmal der Code (die Erklärung folgt später):

Code: C++ | Plain Text
  1.         //Hier werden wir die Zeitabfrage einbauen
  2.         m_iElapsedTicks = SDL_GetTicks() - m_lLastTick;
  3.         m_lLastTick = SDL_GetTicks();
  4.         m_iFPSTickCounter += m_iElapsedTicks;
  5.         m_iFPSCounter++;
  6.  
  7.         if(m_iFPSTickCounter >= 1000) {
  8.             m_iCurrentFPS = m_iFPSCounter;
  9.             m_iFPSCounter = 0;
  10.             m_iFPSTickCounter = 0;
  11.         }

Was passiert da nun genau? Zuerst ziehen wir die neue Zeit von der alten Zeit ab, um eine Differenz zu erhalten. Danach speichern wir die neue Zeit in der Variablen m_lLastTick. Nun zählen wir das Ganze zu unserem FPS-Tickcounter hinzu, und erhöhen unseren FPS-Rechner. Sollte nun dieser FPS-Tickcounter mehr als 1000 ms haben, also eine Sekeunde verstrichen sein, speichern wir den FPS-Counter als unsere aktuelle Framerate und setzen den FPS-Counter und den FPS-Tickcounter wieder auf 0. Das wars schon. Nun haben wir immer eine Framerate in iCurrentFPS.

Wie bewegen wir nun unseren Tux?
Dazu werden wir einfach zur aktuellen Position entsprechend der Bewegungsrichtung die Geschwindigkeit dividiert durch die FPS hinzuaddieren. Das Ganze sieht dann so aus:

Code: C++ | Plain Text
  1.         //Hier bewegen wir den Spieler um jeweils 1px in die gedrückte Richtung
  2.         if(bLeft) {
  3.             rPosition.x -= 100/m_iCurrentFPS;
  4.         }
  5.         else if(bRight) {
  6.             rPosition.x += 100/m_iCurrentFPS;
  7.         }
  8.         if(bUp) {
  9.             rPosition.y -= 100/m_iCurrentFPS;
  10.         }
  11.         else if(bDown) {
  12.             rPosition.y += 100/m_iCurrentFPS;
  13.         }
  14.  
  15.         //Zuerst übermalen wir den Bildschirm mit Schwarz
  16.         SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 0));
  17.  
  18.         //Hier blitten wir unser Bild an die Position, die wir vorher definiert haben
  19.         SDL_BlitSurface(image, &rImage, screen, &rPosition);
  20.  
  21.         //Dann aktualisieren wir den Screen, damit wir auch was sehen
  22.         SDL_Flip(screen);

So, wenn wir das nun alles in unserer while-Sschleife haben, sollte das Ganze auf jedem PC gleich schnell laufen.

Der gesamte Code

Code: C++ | Plain Text
  1. /**
  2.  * @FILE : sdl_gameloop_dyn.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 dynamischer Framerate
  70.     long m_lLastTick = SDL_GetTicks();
  71.     int m_iFPSTickCounter = 0;
  72.     int m_iFPSCounter = 0;
  73.     int m_iCurrentFPS = 0;
  74.     int m_iElapsedTicks = 0;
  75.  
  76.     while(bRun) {
  77.         //Hier fragen wir alle anstehendn SDL_Events ab
  78.         while(SDL_PollEvent(&event)) {
  79.             //Und gehen diese durch
  80.             switch(event.type){
  81.                 case SDL_KEYDOWN:
  82.                     //Eine Taste wurde gedrückt nun müssen wir feststellen welche taste
  83.                     if(event.key.keysym.sym==SDLK_LEFT) {
  84.                         //Pfeiltaste Links wurde gedrückt
  85.                         bLeft = true;
  86.                     }
  87.                     else if(event.key.keysym.sym==SDLK_RIGHT) {
  88.                         //Pfeiltaste Rechts wurde gedrückt
  89.                         bRight = true;
  90.                     }
  91.                     else if(event.key.keysym.sym==SDLK_DOWN) {
  92.                         //Pfeiltaste Oben wurde gedrückt
  93.                         bDown = true;
  94.                     }
  95.                     else if(event.key.keysym.sym==SDLK_UP) {
  96.                         //Pfeiltaste Unten wurde gedrückt
  97.                         bUp = true;
  98.                     }
  99.                     if(event.key.keysym.sym==SDLK_ESCAPE) {
  100.                         //ESCAPE wurde gedrückt. Also Beenden wir alles
  101.                         bRun = false;
  102.                     }
  103.                     break;
  104.                 case SDL_KEYUP:
  105.                     //Eine Taste wurde losgelassen nun müssen wir feststellen welche taste
  106.                     if(event.key.keysym.sym==SDLK_LEFT) {
  107.                         //Pfeiltaste Links wurde losgelassen
  108.                         bLeft = false;
  109.                     }
  110.                     else if(event.key.keysym.sym==SDLK_RIGHT) {
  111.                         //Pfeiltaste Rechts wurde losgelassen
  112.                         bRight = false;
  113.                     }
  114.                     else if(event.key.keysym.sym==SDLK_DOWN) {
  115.                         //Pfeiltaste Oben wurde losgelassen
  116.                         bDown = false;
  117.                     }
  118.                     else if(event.key.keysym.sym==SDLK_UP) {
  119.                         //Pfeiltaste Unten wurde losgelassen
  120.                         bUp = false;
  121.                     }
  122.                     break;
  123.                 case SDL_QUIT:
  124.                     //Sollte das Fenster geschlosen werden, soll er auch die Loop beenden
  125.                     bRun = false;
  126.                     break;
  127.                 default:
  128.                     break;
  129.             }
  130.         }
  131.  
  132.         //Hier werden wir die Zeitabfrage einbauen
  133.         m_iElapsedTicks = SDL_GetTicks() - m_lLastTick;
  134.  
  135.             m_lLastTick = SDL_GetTicks();
  136.  
  137.             m_iFPSTickCounter += m_iElapsedTicks;
  138.  
  139.         m_iFPSCounter++;
  140.  
  141.         if(m_iFPSTickCounter >= 1000) {
  142.  
  143.             m_iCurrentFPS = m_iFPSCounter;
  144.  
  145.                 m_iFPSCounter = 0;
  146.  
  147.                 m_iFPSTickCounter = 0;
  148.  
  149.             }
  150.  
  151.         //Hier bewegen wir den Spieler um jewiel 1 px in die gedrückte richtung
  152.         if(bLeft) {
  153.             rPosition.x -= 100/m_iCurrentFPS;
  154.         }
  155.         else if(bRight) {
  156.             rPosition.x += 100/m_iCurrentFPS;
  157.         }
  158.         if(bUp) {
  159.             rPosition.y -= 100/m_iCurrentFPS;
  160.         }
  161.         else if(bDown) {
  162.             rPosition.y += 100/m_iCurrentFPS;
  163.         }
  164.  
  165.         //Zuerst übermalen wir den Bildschirm mit Schwarz
  166.         SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 0));
  167.  
  168.         //Hier Blitten wir unser Bild an die Position die wir vorher definirt haben
  169.         SDL_BlitSurface(image, &rImage, screen, &rPosition);
  170.  
  171.         //Dann Upadaten wir den Screen, das wir auch was sehen.
  172.         SDL_Flip(screen);
  173.     }
  174.  
  175.     //Hier müssen wir noch unser Bild nach der Schleife wieder freigeben
  176.     SDL_FreeSurface(image);
  177.  
  178.     return 0;
  179. }

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