Animation und Timing

Jetzt wird es aber langsam Zeit dass wir Bewegung ins Spiel bringen, da es ja genau darum bei eigentlich allen Spielen in irgendeiner Form geht. Da sich Bilder natürlich nicht wirklich bewegen können erzeugen wir genau gleich wie zum Beispiel beim Fernseher die Illusion einer Bewegung in dem wir in sehr kurzen zeitlichen Abständen alle Objekte an ihrer aktuellen Position mit ihrer aktuellen Form anzeigen. Im vorherigen Artikel haben wir den Tux geladen, in die Oberfläche des Bildschirms kopiert und dann diese Oberläche anzeigen lassen. Das Laden werden wir auch weiterhin nur einmal machen, aber wenn wir die geladenen Oberfläche in kurzen Zeitabständen immer wieder an eine etwas verschobene Position kopieren dann können wir eine flüssige Bewegung erkennen.

Ein erster Ansatz

Bauen wir also das Programm aus dem vorigen Programm etwas um und packen den Teil mit dem Blitten des Bildes in eine Schleife. In dieser Schleife arbeiten wir zuerst wieder die anstehenden Nachrichten ab, wobei wir dieses Mal statt SDL_WaitEvent eine andere Funktion verwenden müssen, da SDL_WaitEvent solange wartet bis eine Nachricht eintrifft. Wir verwenden jetzt SDL_PollEvent, die die selbe Signatur wie SDL_WaitEvent hat aber sofort zurückkehrt auch wenn keine Nachrichten vorhanden sind. Damit wir keine ungültigen Nachrichten verarbeiten, müssen wir den Rückgabewert beachten, der bei einer extrahierten Nachricht eins ist und wenn keine Nachricht geholt wurde null. Mit dem Wissen können wir jetzt den Tux quer durchs Fenster fahren lassen:

  // ...
 
  dest_rect.x = 350;
  dest_rect.y = 150;
 
  // Bis hierher ist der Code gleich wie im vorherigen Artikel
 
  // ---------------------------------------------------------------------------
  // Jetzt mit Bewegung
 
  int move_x = 1;      // aktuelle Bewegung des Tux in x-Richtung
  bool running = true; // Abbruchbedingung für die Spielschleife
 
  // Jetzt läuft das Programm in einer Endlosschleife
  while( running )
  {
    SDL_Event event;
 
    // Events abarbeiten (Solange SDL_PollEvent 1 zurückgibt
    // ist ein Event in event gespeichert worden.
    while( SDL_PollEvent(&event) )
    {
      switch( event.type )
      {
        // Programm sollte beendet werden ('x' im Fenster wurde geklickt)
        case SDL_QUIT:
          running = false;
          return 0;
      }
    }
 
    // ... und jetzt den Tux bewegen
    dest_rect.x += move_x;
 
    // und falls er an einer Wand anstößt ihn genau an den Schnittpunkt
    // mit der Wand setzten und die Richtung umkehren
    if( dest_rect.x >= 800 - tux->w )
    {
      dest_rect.x = 800 - tux->w;
      move_x *= -1;
    }
    else if( dest_rect.x <= 0 )
    {
      dest_rect.x = 0;
      move_x *= -1;
    }
 
    // und schließlich noch blitten
    SDL_BlitSurface(tux, 0, screen, &dest_rect);
 
    // und anzeigen
    SDL_Flip(screen);
  }
 
  // Oberfläche wieder freigeben...
  SDL_FreeSurface(tux);
 
  std::cout << "Programm beendet." << std::endl;
  return 0;
}
 
// ... und hier fehlt nur mehr die loadBMP Funktion aus dem
// vorherigen Artikel.

Den Bildschirm leeren

Wer beim Ausführen des vorherigen Beispiels genau hinschaut hat, wird vielleicht festgestellt haben das ein paar Teile des Tux eine Spur nach sich ziehen. Das liegt daran, dass wir den Tux jedes Mal etwas versetzt über das alte Bild darüber blitten und somit nicht verdeckte Regionen natürlich auch ihre alten Farbwerte beibehalten. Aus diesem Grund sollten wir vor jedem Frame als erstes den Bildschirm leeren, also vollständig mit irgendeiner Farbe füllen, was wir einfach mit SDL_FillRect machen können:

SDL_FillRect(screen, NULL, 0); // Den Bildschirm schwarz anfüllen

Zeitbasierte Animation

Wenn wir unsere Programm etwas genauer beobachten und es aus irgendeinem Grund zu einer veränderten Auslastung des Prozessors kommt, dann kommt es oft vor das unser Programm mehr oder weniger Rechenzeit zugewiesen bekommt und somit die Schleife auch mehr oder weniger oft in der gleichen Zeit durchlaufen kann. Da die Position des Tux aber in jedem Schleifendurchlauf um einen festen Wert verändert wird erfolgt die Bewegung abhängig von der Prozessorauslastung ungleich schnell. Noch größer wird dieses Problem wenn wir verschiedenen Prozessoren betrachten, da eine andere Prozessortaktung auch eine andere Auführungsgeschwindigkeit bewirkt. So kann also unser Tux auf einem langsamen Prozessor gemütlich von einem Rand zum anderen wandern, während er auf einem schnellen Computer nur noch rast.

Deshalb müssen wir alle Bewegungen von der Zeit abhängig machen, wozu wir aber ein Möglichkeit zur Zeitmessung brauchen. Die SDL stellt und dafür die Funktion SDL_GetTicks zur Verfügung die uns die die vergangene Zeit seit der Initialisierung der SDL in Millisekunden zurück gibt. Wenn wir also jeweils am Anfang und am Ende eines Durchlaufes die Zeit abfragen und die Differenz bilden, dann wissen wir wie lange dieses Frame gedauert hat. Da wir aber diese Zeit bereits während dieses Frames benötigen verwenden wir zur Berechnung der Bewegung immer die Zeit des vorherigen Durchlaufes.

FIXME

// ...
float pos_x = 350,
      pos_y = 150;
float move_x = 100;
 
Uint32 old_ticks, new_ticks = SDL_GetTicks();
float frame_time = 1/50.f;
 
while( running )
{
  // ... (Nachrichten abfangen)
 
  pos_x += frame_time * move_x;
 
  // ... (Bildschirm leeren, Tux anzeigen etc.)
 
  //--------------------------------------------------------------------------
  // Zeit des aktuellen Schleifendurchlaufs berechnen
 
  old_ticks = new_ticks;
  new_ticks = SDL_GetTicks();
  frame_time = (new_ticks - old_ticks)/1000.f;
}

FIXME


Bitmap Grafiken anzeigen und laden ← | ↑ SDL Start ↑ | → TODO...

Diskussion