Teil 8: Der Gleiter des Spielers

Wir wollen nun so schnell wie möglich mal was bewegtes auf den Bildschirm bringen, und überlegen uns dazu, welche Attribute ein Raumschiff für die Darstellung auf dem Bildschirm braucht.
Die Antwort ist einfach: eine Koordinate. Damit kommen wir zu einer ganz einfachen Basisklasse für Objekte in unserem Weltraum:
Datei objects/Entity.h

#ifndef ENTITY_H
#define ENTITY_H
#include "Types.h"
// ---------------------------------------------------------------------------
class CEntity
{
public:
  CEntity();
  CEntity( const float fX, const float fY );
  virtual ~CEntity();
  void SetPosition( const float fX, const float fY );
  void GetPosition( float& fX, float& fY ) const;
protected:
  float m_fX;
  float m_fY;
};
#endif

Damit hätten wir das mit der Position schonmal geklärt. Das ist aber noch nicht alles. Ein Objekt sollte sich nach Möglichkeit auch selbst Zeichnen können, und außerdem wollen wir den Objekten auf dem Bildschirm ja auch gerne ein Eigenleben einhauchen, sei es, in Form von Spielersteuerung, oder von KI. Außerdem muß ein Objekt evtl. Grafiken und andere Ressourcen laden und entladen. Wir fügen also noch ein paar Funktionen im public Bereich hinzu:

   virtual void Initialize() = 0;
  virtual void Uninitialize() = 0;
  virtual void Draw() = 0;
  virtual void Idle( DWORD dwDeltaTime ) = 0;

Die Idle Funktion sollte dir ja schon bekannt vorkommen. Hier werden wir gleich die Tastaturabfrage für das eigene Schiff sowie später die KI für die Gegner einbauen. Initialize und Uninitialize sind wie bereits besprochen für das Laden und Entladen von Ressourcen verantwortlich. Draw letztendlich wird diese Ressourcen auf den Bildschirm bringen. In unserem Fall wird es wohl keinen Unterschied machen, ob man in ein und der selben Funktion die KI abhandelt und das Objekt zeichnet, manchmal macht es aber Sinn, beides zu trennen, deshalb nehmen wir diese Trennung direkt von Anfang an vor.
Jetzt brauchen wir mal den Gleiter aus dem letzten Teil. Hier gibts das Bitmap. Speichere die Datei in einem Unterverzeichnis resource/gleiter. In das resource-Verzeichnis werden wir fortan alle Grafiken speichern. Damit das ganze schön übersichtlich bleibt, unterteilen wir das Verzeichnis auch noch. Ordnung ist das halbe Leben, oder so ähnlich 😉
Für den Gleiter leiten wir also mal eine einfache Klasse ab. Als einzige Erweiterung kommt die Gleiter-Grafik hinzu, die in einem SDL_Surface* gespeichert wird:
Datei objects/Glider.h

#ifndef GLIDER_H
#define GLIDER_H
#include <SDL.h>
#include "Types.h"
#include "Entity.h"
// ---------------------------------------------------------------------------
class CGlider : public CEntity
{
public:
  void Initialize();
  void Uninitialize();
  void Draw();
  void Idle( DWORD dwDeltaTime );
private:
  SDL_Surface* m_pSurface;
};
#endif

Wir kümmern uns erstmal um die Anzeige des Gleiters. Dazu muß die Grafik in der Initialize-Funktion geladen, und ein Colorkey festgelegt werden. In der Funktion Draw wird das Surface dann auf den Bildschirm gebracht, und in Uninitialize letztendlich wieder freigegeben.
Datei objects/Glider.cpp

#include "Glider.h"
#include <core/KeyboardManager.h>
// ---------------------------------------------------------------------------
extern SDL_Surface* g_pBackSurface;
// ---------------------------------------------------------------------------
void CGlider::Initialize()
{
  m_pSurface = SDL_LoadBMP( "resource/gleiter/gleiter.bmp" );
  SDL_SetColorKey( m_pSurface, SDL_SRCCOLORKEY, SDL_MapRGB( g_pBackSurface- >format, 0, 0, 0 ) );
}
// ---------------------------------------------------------------------------
void CGlider::Uninitialize()
{
  SDL_FreeSurface( m_pSurface );
}
// ---------------------------------------------------------------------------
void CGlider::Draw()
{
  SDL_Rect rc;
  rc.x = m_fX;
  rc.y = m_fY;
  SDL_BlitSurface( m_pSurface, 0, g_pBackSurface, &rc );
}
// ---------------------------------------------------------------------------
void CGlider::Idle( DWORD dwDeltaTime )
{
}

Hiermit wird der Gleiter zunächst an seiner Position m_fX, m_fY (die vorerst mit (0, 0) initialisiert wird) auf das Backsurface gebracht. In der Funktion Initialize wird ein Problem offensichtlich: Wird eine Grafik nicht gefunden, stürzt das Programm spätestens beim nächsten Befehl ab. Deshalb werden wir uns im nächsten Teil erst einmal mit Fehlerbehandlung befassen. Damit wir aber in diesem Teil mal endlich was auf dem Bildschirm sehen, fügen wir den Gleiter testweise in die GSGame.h ein:
Datei game/GSGame.h

#ifndef GSGAME_H
#define GSGAME_H
// ---------------------------------------------------------------------------
#include "Types.h"
#include "GameState.h"
#include <objects/Starfield.h>
#include <objects/Glider.h>
// ---------------------------------------------------------------------------
class CGameManager;
// ---------------------------------------------------------------------------
class CGSGame : public IGameState
{
public:
  CGSGame( CGameManager* pGameManager );
  ~CGSGame();
  void Enter();
  void Idle( DWORD dwDeltaTime );
  void Leave();
private:
  CStarfield m_clStarfield;
  CGlider m_clGlider;
};
#endif

Wenn wir so bei jedem Objekt vorgehen würden, hätten wir am Ende ein ziemliches Chaos an Variablen, da retten auch Arrays nicht viel. Wir werden also bald noch eine Managerklasse hinzufügen, die sich um das Leben und Ableben unserer Entities im Spiel kümmert. Vorerst aber initialisieren und zeichnen wir den Gleiter in GSGame.cpp:
Datei /GSGame.cpp

#include "GSGame.h"
#include <SDL.h>
#include <core/KeyboardManager.h>
// ---------------------------------------------------------------------------
CGSGame::CGSGame( CGameManager* pGameManager )
:   IGameState( pGameManager )
{
}
// ---------------------------------------------------------------------------
CGSGame::~CGSGame()
{
}
// ---------------------------------------------------------------------------
void CGSGame::Enter()
{
  m_clStarfield.Initialize();
  m_clGlider.Initialize();
}
// ---------------------------------------------------------------------------
void CGSGame::Idle( DWORD dwDeltaTime )
{
  m_clStarfield.Idle( dwDeltaTime );
  m_clGlider.Idle( dwDeltaTime );
  m_clGlider.Draw();
}
// ---------------------------------------------------------------------------
void CGSGame::Leave()
{
  m_clGlider.Uninitialize();
}

Damit ist unser Gleiter auf dem Bildschirm:
TODO
Letztendlich wollen wir zur Entspannung nach soviel Arbeit auch noch ein wenig mit dem Gleiter auf dem Bildschirm umherfliegen. Dazu fügen wir ein wenig Tastaturabfrage in die CGlider::Idle Funktion ein:

void CGlider::Idle( DWORD dwDeltaTime )
{
  if( KEYBOARD->KeyDown( SDLK_UP ) && m_fY > 0 )
  {
     m_fY -= ( (float)dwDeltaTime / 10.0f );
  }
  if( KEYBOARD->KeyDown( SDLK_DOWN ) && m_fY < 599 )
  {
     m_fY += ( (float)dwDeltaTime / 10.0f );
  }
  if( KEYBOARD->KeyDown( SDLK_LEFT ) && m_fX > 0 )
  {
     m_fX -= ( (float)dwDeltaTime / 10.0f );
  }
  if( KEYBOARD->KeyDown( SDLK_RIGHT ) && m_fX < 799 )
  {
     m_fX += ( (float)dwDeltaTime / 10.0f );
  }
}

Die Bewegung ist natürlich alles andere als realistisch, aber als kleines Etappenziel reicht sie uns vorerst.
Download

Leave a Reply

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