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
Teil1
Worum geht es?
In diesem Artikel werde ich 3 Arten von Game Loops vorstellen. Game Loops sind die Schleifen, in denen man das Spiel fortlaufend rendert und aktualisiert. Dabei gibt es zich Ansätze, wovon ich 3 hier vorstellen möchte.
Was muss man können?
Um dieses Tutorial gut durcharbeiten zu können, sollte man sich bereits mit SDL beschäftigt haben. Es sollte reichen, z.B. das Einführungstutorial auf dieser Seite gelesen zu haben.
Welche Arten gibt es?
Man kann seine Game Loops mit einer festen Framerate und mit einer dynamischen Framerate realisieren. Des weitere kann man auch die Zeitdifferenz verwenden. Alle 3 Ansätze werde ich euch hier zeigen.
Was ist überhaupt eine Framerate?
Die Framerate ist die Anzahl der gerenderten Bilder pro Sekunde.
Teil2
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.
- //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 etwas auf der Konsole 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 sorgen, dass SDL_Quit beim Beenden aufgerufen wird
- atexit(SDL_Quit);
- //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
- 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-Fehlermeldung 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 Anwendungs-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 wir die Abmessungen 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;
- //Hier speichern wir die Position von unserem Image
- SDL_Rect rPosition = rImage;
- //Die Event-Struktur, um SDL_Events auszuwerten
- SDL_Event event;
- //Unsere Variable die dafür sorgt, dass unser Fenster erstmal geöffnet bleibt
- bool bRun = true;
- //Speichern, wohin es sich bewegen soll
- bool bLeft = false;
- bool bRight = false;
- bool bUp = false;
- bool bDown = false;
- /**
- * Hier kommt unser Game Loop später hin
- */
- //Hier müssen wir noch unser Bild nach der Schleife wieder freigeben
- SDL_FreeSurface(image);
- 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
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:
- //Game Loop mit dynamischer Framerate
- long m_lLastTick = SDL_GetTicks();
- int m_iFPSTickCounter = 0;
- int m_iFPSCounter = 0;
- int m_iCurrentFPS = 1;
- int m_iElapsedTicks = 0;
- 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
- while(bRun) {
- //Hier fragen wir alle anstehenden 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 geschlossen werden, soll auch der Loop beendet werden
- bRun = false;
- break;
- default:
- break;
- }
- }
- //Hier werden wir die Zeitabfrage einbauen
- }
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):
- //Hier werden wir die Zeitabfrage einbauen
- m_iElapsedTicks = SDL_GetTicks() - m_lLastTick;
- m_lLastTick = SDL_GetTicks();
- m_iFPSTickCounter += m_iElapsedTicks;
- m_iFPSCounter++;
- if(m_iFPSTickCounter >= 1000) {
- m_iCurrentFPS = m_iFPSCounter;
- m_iFPSCounter = 0;
- m_iFPSTickCounter = 0;
- }
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:
- //Hier bewegen wir den Spieler um jeweils 1px in die gedrückte Richtung
- if(bLeft) {
- rPosition.x -= 100/m_iCurrentFPS;
- }
- else if(bRight) {
- rPosition.x += 100/m_iCurrentFPS;
- }
- if(bUp) {
- rPosition.y -= 100/m_iCurrentFPS;
- }
- else if(bDown) {
- rPosition.y += 100/m_iCurrentFPS;
- }
- //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 was sehen
- 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
- /**
- * @FILE : sdl_gameloop_dyn.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 dynamischer Framerate
- long m_lLastTick = SDL_GetTicks();
- int m_iFPSTickCounter = 0;
- int m_iFPSCounter = 0;
- int m_iCurrentFPS = 0;
- int m_iElapsedTicks = 0;
- while(bRun) {
- //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 werden wir die Zeitabfrage einbauen
- m_iElapsedTicks = SDL_GetTicks() - m_lLastTick;
- m_lLastTick = SDL_GetTicks();
- m_iFPSTickCounter += m_iElapsedTicks;
- m_iFPSCounter++;
- if(m_iFPSTickCounter >= 1000) {
- m_iCurrentFPS = m_iFPSCounter;
- m_iFPSCounter = 0;
- m_iFPSTickCounter = 0;
- }
- //Hier bewegen wir den Spieler um jewiel 1 px in die gedrückte richtung
- if(bLeft) {
- rPosition.x -= 100/m_iCurrentFPS;
- }
- else if(bRight) {
- rPosition.x += 100/m_iCurrentFPS;
- }
- if(bUp) {
- rPosition.y -= 100/m_iCurrentFPS;
- }
- else if(bDown) {
- rPosition.y += 100/m_iCurrentFPS;
- }
- //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);
- }
- //Hier müssen wir noch unser Bild nach der Schleife wieder freigeben
- SDL_FreeSurface(image);
- return 0;
- }
Teil3
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;
- }
Die benötigten Variablen
Wir brauchen natürlich wieder neue Variablen. Wobei wir bei diesem Ansatz sogar nur eine brauchen.
Die differenz ausrechnen
So da wo wir im 1. beispiel “//Hier werden wir die Zeitabfrage einbauen” stehen haben packen wir nun folgende zwei zeilen darunter.
- //Hier werden wir die Zeitabfrage einbauen
- int elapseMS = SDL_GetTicks() - lastTime;
- lastTime = SDL_GetTicks();
War doch einfach oder? Wir errechnen hier ja einfach die Differenz zwischen dem letzten mal Zeitnehmen und jetzt.
Die Bewegung
So die Bewegung anhand der Zeitdifferenz sollte ja nun kein problem mehr sein. Daher werde ich keine weiteren Worte darüber verlieren.
- //Hier bewegen wir den Spieler
- if(bLeft) {
- rPosition.x -= 1*elapseMS;
- }
- else if(bRight) {
- rPosition.x += 1*elapseMS;
- }
- if(bUp) {
- rPosition.y -= 1*elapseMS;
- }
- else if(bDown) {
- rPosition.y += 1*elapseMS;
- }
So und schon haben wir eine game loop mithilfe der Zeitdifferenz.
Der komplette Code
- /**
- * @FILE : sdl_gameloop_ms.cpp
- * @AUTOR: Florian Krauthan
- * @DATE : 27.12.2009
- */
- //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 Zeitdifferenz
- long lastTime = SDL_GetTicks();
- while(bRun) {
- //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 werden wir die Zeitabfrage einbauen
- int elapseMS = SDL_GetTicks() - lastTime;
- lastTime = SDL_GetTicks();
- //Hier bewegen wir den Spieler
- if(bLeft) {
- rPosition.x -= 1*elapseMS;
- }
- else if(bRight) {
- rPosition.x += 1*elapseMS;
- }
- if(bUp) {
- rPosition.y -= 1*elapseMS;
- }
- else if(bDown) {
- rPosition.y += 1*elapseMS;
- }
- //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);
- }
- //Hier müssen wir noch unser Bild nach der Schleife wieder freigeben
- SDL_FreeSurface(image);
- return 0;
- }
Schluss
Ok, das waren nun 3 Methoden für einen Game Loop. Im grunde sind die beiden ersten gleich sinnvoll. Wobei ich die 3. empfehlen würde. Auf alle Fälle sollte man so einen Game Loop benutzen und nicht einfach nur eine Schleife. Jedenfalls dann, wenn das Spiel auch auf neueren und älteren – bzw. PCs mit anderer Hardwareausstattung – gleichschnell laufen soll.


(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.