Teil 12: Feindkontakt

Wir haben alle Vorbereitungen getroffen, deshalb gibt es jetzt wie versprochen endlich den ersten Feind.
Hier ist er, der Feind mit “stahlharten” Endgegnerqualitäten:
TODO
Weltraumschrott. Ja, sorry, der muß erstmal reichen 😉 Aber wie ihr seht, besteht der schonmal aus mehreren Frames, das heisst, wir werden ihn auch animieren.
Was soll der Schrott können? Er wird uns einfach entgegenfliegen, also von rechts nach links durch den Bildschirm, und das mit einer bestimmten Geschwindigkeit. Wir werden also eine neue Klasse von CEntity ableiten. In CEntity haben wir bereits die Position, zusätzlich brauchen wir noch einen SDL_Surface* für die Grafik, einen float für die Geschwindigkeit und einen float für das Frame. float für das Frame?! Ja, richtig, denn wir berechnen das Frame des Schrotts genau so wie die Position mithilfe der vergangenen Framezeit und dem Speed. Damit kommen wir zur folgenden Deklaration:
Datei /objects/Scrap.h

#ifndef SCRAP_H
#define SCRAP_H
#include <SDL.h>
#include <Types.h>
#include <objects/Entity.h>
class CScrap : public CEntity
{
public:
  void Initialize();
  void Uninitialize();
  void Draw();
  void Idle( DWORD dwDeltaTime );
private:
  SDL_Surface* m_pSurface;
  float m_fSpeed;
  float m_fFrame;
};
#endif

Wie sieht nun die Implementierung aus? Die Initialisierung ist einfach. Wir laden das Schrott-Bitmap, das übrigens in resource/schrott/schrottteil2-1.bmp gehört, genau wie wir es vom Gleiter kennen. Daraufhin initialisieren wir die Position sowie die Parameter Speed und Frame mit zufälligen Werten. Der Schrott soll an der rechten Bildschirmhälfte starten, deshalb legen wir m_fX auf 800 fest. Die y-Position kann zufällig zwischen 0 und 599 liegen. Der Speed sollte bei unserem großen Schrott, der im Vordergrund fliegen soll, bei etwa 80-120 Pixeln pro Sekunde liegen, was einem Faktor von 0.8 – 1.2 entspricht. Der Schrott hat 5×4 Frames, also muß das Frame zwischen 0 und 19 liegen.
Datei objects/Scrap.cpp, erster Teil

#include "Scrap.h"
#include <stdlib.h>
#include <game/GameManager.h >
// ---------------------------------------------------------------------------
extern SDL_Surface* g_pBackSurface;
// ---------------------------------------------------------------------------
void CScrap::Initialize()
{
  m_pSurface = SDL_LoadBMP( "resource/schrott/schrottteil2-1.bmp" );
  if( 0 == m_pSurface )
  {
     GAMEMANAGER->QuitWithError( "Datei resource/schrott/schrottteil2-1.bmp wurde nicht gefunden!" );
  }
  if( SDL_SetColorKey( m_pSurface, SDL_SRCCOLORKEY, SDL_MapRGB( g_pBackSurface- >format, 0, 0, 0 ) ) < 0 )
  {
     GAMEMANAGER->QuitWithError( "Colorkey für resource/schrott/schrottteil2-1.bmp konnte nicht gesetzt werden!" );
  }
  m_fX = 800.0f;
  m_fY = (float)( rand() % 600 );
  m_fSpeed = 0.8f + (float)( rand() % 40 ) / 100.0f;
  m_fFrame = rand() % 20;
}
// ---------------------------------------------------------------------------
void CScrap::Uninitialize()
{
  if( m_pSurface )
  {
     SDL_FreeSurface( m_pSurface );
  }
}

Damit kommen wir zu den beiden übrigen Funktionen, Draw und Idle. Während wir beim Gleiter im Moment einfach das gesamte Surface blitten können, müssen wir beim animierten Schrott nun ein Quellrechteck ermitteln das gezeichnet werden soll. Dazu betrachten wir die Grafik etwas genauer. Die Frames sind offenbar Zeile für Zeile angeordnet, mit 5 Grafiken pro Zeile und 4 Spalten. Aus der Bitmapgröße ergibt sich damit, dass ein Frame 50×50 Pixel groß ist. w und h unseres SDL_Rect ist also jeweils 50. Zur Ermittlung von x und y bedienen wir uns der Ganzzahldivision und dem Rest der Division. x ist hierbei das Frame modulo 5, also der Rest der Division durch 5, und damit immer ein Wert zwischen 0 und 4 für die Spalte. y ist entsprechend das Frame ganzzahl-dividiert durch 5 für die Zeile. Diese beiden Werte multipliziert mit Breite und Höhe ergeben die Startpositionen für das zu zeichnende Rechteck.
In der Idle Funktion soll der Schrott weiterbewegt und gedreht werden. Die Bewegung erfolgt wie schon vom Gleiter bekannt durch eine Verrechnung mit Framezeit und Geschwindigkeit. Und genau so funktioniert auch die Berechnung des Frames, nur dass wir hier den Wert noch etwas weiter runterskalieren, damit sich der Schrott nicht ganz so schnell dreht. Wenn der neue Framewert größer als 19 ist, fangen wir wieder beim 0. Frame an. Wenn der Schrott ausserhalb des Sichtbereits ist, also den linken Bildschirmrand um seine Breite überschritten hat, setzen wir das m_bDestroy Flag, so daß der EntityManager weiß, daß dieser Schrott gelöscht werden kann. So sieht der Rest dann aus:
Datei /objects/Scrap.cpp, zweiter Teil:

// ---------------------------------------------------------------------------
void CScrap::Draw()
{
  SDL_Rect rcSource;
  SDL_Rect rcDest;
  rcSource.x = (int)m_fFrame % 5 * 50;
  rcSource.y = (int)m_fFrame / 5 * 50;
  rcSource.w = 50;
  rcSource.h = 50;
  rcDest.x = m_fX;
  rcDest.y = m_fY;
  SDL_BlitSurface( m_pSurface, &rcSource, g_pBackSurface, &rcDest );
}
// ---------------------------------------------------------------------------
void CScrap::Idle( DWORD dwDeltaTime )
{
  m_fX -= ( (float)dwDeltaTime / 10.0f ) * m_fSpeed;
  m_fFrame += ( (float)dwDeltaTime / 100.0f ) * m_fSpeed;
  while( (int)m_fFrame > 19 )
  {
     m_fFrame -= 19.0f;
  }
  if( m_fX < -50 )
  {
     m_bDestroy = true;
  }
}

Das Herunterrechnen des Frames geschieht übrigens in einer while-Schleife, damit wir auch bei längeren Framezeiten (wenn also der PC mal wegen irgendetwas ein vielfaches von 20 Schrott-Frames gehakt hat, kommt ja unter Windows gelegentlich mal vor) wieder beim richtigen Frame ankommen. Das ist bei dem Schrott nicht sonderlich wichtig, aber es würde evtl. jemandem trotzdem auffallen, wenn es denn mal passiert.
Unser neues Spielobjekt müssen wir natürlich auch bei der EntityFactory anmelden. Dazu fügen wir im Header EntityFactory.h einen weiteren enum namens EN_Scrap hinzu und includen in EntityFactory.cpp den Scrap, um dann im switch eine Instanz davon anzulegen, genau wie beim Gleiter. Da wir in Zukunft noch einige Objekte einbauen werden, werde ich in Zukunft nicht mehr genauer darauf eingehen, sondern nur noch den entsprechenden enum nennen. Du weißt ja dann, was wir zu tun haben.
Damit wir schließlich im Spiel auch etwas Schrott herumfliegen sehen, müssen wir welchen erstellen, und zwar in der CGSGame::Idle Funktion. Hierzu muß auch in GSGame.cpp der Schrott inkludiert werden. Die Idle-Funktion könnte dann z.B. so aussehen:
Datei /game/GSGame.cpp

void CGSGame::Idle( DWORD dwDeltaTime )
{
  if( rand() % 1000 < 5 )
  {
     ENTITYMANAGER->Add( CEntityFactory::Create( CEntityFactory::EN_Scrap ) );
  }
  m_clStarfield.Idle( dwDeltaTime );
  ENTITYMANAGER->Idle( dwDeltaTime );
  ENTITYMANAGER->Draw();
  if( KEYBOARD->KeyDown( SDLK_ESCAPE ) )
  {
     GAMEMANAGER->Quit();
  }
}

Hier wird Schrott zufällig erstellt, was uns für den Anfang reichen sollte. Er fliegt also.
Jetzt gibt’s ‘ne Überraschung: noch mehr Schrott! Unten im Download-Bereich findest Du Links zu drei weiteren Schrottbildern. Dazu findest Du gleich einige Aufgaben, die es zu lösen gilt, damit Du auch mal wieder mitdenken mußt.
Es gibt natürlich ein Problem: Der Schrott fliegt doof an uns vorbei und keiner merkt was. Deshalb wird es natürlich bald den nächsten Teil mit Kollisionsabfrage geben. Aber es gibt auch noch ein anderes Problem: Pro Schrott wird die Grafik geladen und wieder freigegeben. Falls jemand unser kleines Spielchen von einer Diskette spielen sollte oder auf einem langsamen Rechner mit langsamer Festplatte, wird der keine Freude daran haben. Wir müssen uns also vielleicht auch etwas überlegen, wie wir bestimmte Sachen im Voraus laden können. Das wird also im nächsten Teil erstmal kommen. Und zu guter letzt werden sämtliche Schrottstücke ÜBER dem Gleiter gezeichnet, selbst wenn sie sich im Hintergrund befinden sollten, wie die kleinen, auf die ich gleich in den Aufgaben komme. Wir müssen also bald auch noch eine Tiefeninformation einfügen und ein wenig sortieren. Krass, was man für so ein kleines Spielchen alles braucht, oder?

Aufgaben

  1. Lade die übrigen Schrottbilder herunter und füge sie im selben Verzeichnis ein.
  2. Lade in der Initialize Funktion eines der vier Bilder, bestimmt durch einen Zufallswert. Beachte, daß die Bilder unterschiedlich große Schrottsprites haben. Da wir die Größe eines Entities sowieso für die Kollisionsabfrage brauchen, kannst Du Höhe und Breite direkt in CEntity hinzufügen.
  3. Die kleinen Schrottstücke sollen langsamer fliegen als die größeren, weil sie sich weiter im Hintergrund befinden.

Downloads

Leave a Reply

Your email address will not be published. Required fields are marked *