Nun brauchen wir Klassen (oder Strukturen; ich werde Klassen benutzen), in denen wir das Bild und die Position der Bilder halten. Wir könnten natürlich alles direkt als Variablen in der main-function speichern, aber es wird sich zeigen, dass es der Einfachheit und Übersicht hilft, wenn man das ganze in Klassen zusammenfasst. Da wir bereits eine pong.h besitzen, können wir in dieser direkt weiter schreiben:
class Sprite { privat: SDL_Rect position; SDL_Surface* bmp; Sprite(); };
So sollte es zuerst einmal aussehen. Denn an diesem Punkt sollte uns schon ein Problem auffallen. Wem es nicht auffällt, der soll bitte hier stoppen und sich einmal ein paar Sekunden den Kopf darüber zerbrechen.
Das Problem ist, dass wir zwar SDL_Rect und SDL_Surface deklarieren, aber die Headerdatei kein SDL kennt. Also müssen wir dieses erst include, bevor es weiter gehen kann. Also kommt das oben in die pong.h
#ifdef __APPLE__ #include <SDL/SDL.h> #else #include <SDL.h> #endif
Und da wir gerade einen schönen Breakpoint haben, kann ich direkt ein weiteres Problem ansprechen, worauf wir später stoßen werden: Die Pongs bewegen sich nur auf der Y-Achse, der Ball jedoch auf X und Y. Deshalb brauch der Ball einen Vektor! Und ein weiteres Merkmal fällt uns auf: Der Ball braucht mehr Eigenschaften als ein Pong, hat aber im Grunde die gleichen. Deshalb sollte man für den Ball eine neue Klasse erstellen und die Sprite Klasse hineinerben. Bevor ich weiter auf Vektoren eingehe, schreiben wir erstmal beide Klasse in einen annehmbaren Zustand.
class Sprite { protected: // nicht mehr privat // Variablen SDL_Rect position; public: // eigentlich könnte alles public sein, da das Projekt relativ klein ist // Variablen SDL_Surface *bmp; // Constructor Sprite() { bmp = SDL_LoadBMP( "pong.bmp" ); // ein Konstruktor ist nicht vererbbar } // Functions inline SDL_Rect* get_position( void ) { return &position; } virtual void set_position( int x, int y ) { position.x = x; position.y = y; } virtual void update_position( int y ) // Nur y, da Spieler nur auf der Y Achse sich bewegen { position.y += y; } }; // Ball braucht Vektoren, deshalb eine neue Klasse class Ball : public Sprite // Ball erbt von Sprite { public: // Variables bool moving; // ob der Ball sich bereits bewegt oder nicht // -- hier kommen später unsere Vektoren -- // void set_position( int x, int y ) // oben nur virtual, weil wir hier die Änderung auch in unseren Vektor eintragen muss { position.x = x; position.y = y; // Vektor wird zu x & y } void update_position( int x, int y ) // oben nur virtual, weil wir hier die Änderung auch in unseren Vektoren eintragen mus { position.x += x; position.y += y; // Auf Vektor wird x & y addiert } // Constructor Ball() { bmp = SDL_LoadBMP( "ball.bmp" ); moving = false; // am Anfang steht der Ball position.x = SCREEN_WIDTH / 2 - bmp->w / 2; // Ball wird zentriert auf der X Achse position.y = SCREEN_HEIGHT / 2 - bmp->h / 2; // Ball wird zentriert auf der Y Achse // Vektor ebenfalls auf diesen Wert setzen } };
Die Kommentare mit den Vektoren werden später durch die richtigen Anweisungen ersetzt. Aber nun haben wir schonmal 2 Klassen. Das ist ein guter Ansatz. Diese können wir nun in unserem Hauptprogramm vor der Hauptschleife deklarieren.
main.cpp
Sprite ping, pong; // Ping links, Pong rechts; Auch wenn man im Spiel eigentlich Pong ist Ball ball; // Beim Ball wird folgendes vom Konstruktor übernommen: // Dies ist hier nicht möglich, da Ping und Pong andere Startpositionen haben ping.set_position( 0, SCREEN_HEIGHT / 2 - ping.bmp->h / 2 ); // linker Bildschirmrand in der Mitte pong.set_position( SCREEN_WIDTH - pong.bmp->w, SCREEN_HEIGHT / 2 - pong.bmp->h / 2 ); // rechter Bildschirmrand in der Mitte
Es wird auffallen, dass Ping und Pong direkt am Rand vom Bildschirm sind. Dies sind sehr unschön aus. Deshalb definieren wir in der pong.h folgendes:
#define DEFAULT_GAP 24 // gap between players and screen end in px
und editieren die set_position Parameter wie folgt:
ping.set_position( DEFAULT_GAP, SCREEN_HEIGHT / 2 - ping.bmp->h / 2 ); // linker Bildschirmrand in der Mitte pong.set_position( SCREEN_WIDTH - pong.bmp->w - DEFAULT_GAP, SCREEN_HEIGHT / 2 - pong.bmp->h / 2 ); // rechter Bildschirmrand in der Mitte
Nun zurück zu den Vektoren. Um für unseren Ball Vektoren zu nutzen, sollte wir eine Klasse für Vektoren schreiben.
Zuerst einmal wird eine vector.h erstellt. Die sieht so aus:
#ifndef VECTOR_H_INCLUDED #define VECTOR_H_INCLUDED #endif VECTOR_H_INCLUDED
In dieser vector.h erstellen wir unsere Klasse. Ich nenne sie mal CVector1). Nun müssen wir uns überlegen, was unser Vektor können muss. Er muss einen x sowie einen y Wert halten. Außerdem muss er mit diesen rechnen können. Dazu fällt uns spontan Operator Overload ein! Versucht zuerst einmal selbst die Klasse zu schreiben. Wenn ihr fertig seid könnt ihr mit meiner vergleichen:
class CVector { public: // Variables int x, y; // Koordinaten // Constructor CVector() { x = y = 0; }; CVector( int, int ); // Operator Overloads CVector operator + ( CVector ); CVector operator - ( CVector ); CVector operator = ( CVector ); }; CVector::CVector( int x, int y ) { this->x = x; this->y = y; } CVector CVector::operator+ ( CVector param ) { CVector temp; temp.x = this->x + param.x; temp.y = this->y + param.y; return( temp ); } CVector CVector::operator- ( CVector param ) { CVector temp; temp.x = this->x - param.x; temp.y = this->y - param.y; return( temp ); } CVector CVector::operator= ( CVector param ) { this->x = param.x; this->y = param.y; return( param ); }
So in der Art sollte eure Klasse aussehen. Nun müssen wir noch die pong.h editieren, weil wir dort die Vektoren in die Ball Klasse hinzufügen müssen. Zuerst sollten wir aber #include „vector.h“ in unsere pong.h hinzufügen!
// Ball braucht Vektoren, deshalb eine neue Klasse class Ball : public Sprite // Ball erbt von Sprite { privat: // da wir current_coord mit "update_position" zusammen mit position verändern // Variables CVector current_coord; // derzeitige Position vom Ball public: // Variables bool moving; // ob der Ball sich bereits bewegt oder nicht CVector final_coord; // Ziel des Balls void set_position( int x, int y ) // oben nur virtual, weil wir hier die Änderung auch in unseren Vektor eintragen muss { position.x = current_cord.x = x; position.y = current_cord.y = y; } void set_position( CVector new ) { current_cord = new; position.x = new.x; position.y = new.y; } void update_position( int x, int y ) // oben nur virtual, weil wir hier die Änderung auch in unseren Vektoren eintragen mus { position.x += x; position.y += y; current_cord.x += x; current_cord.y += y; } void update_position( CVector temp ) { current_cord = current_cord + temp; position.x += temp.x; position.y += temp.y; } // Constructor Ball() { bmp = SDL_LoadBMP( "ball.bmp" ); moving = false; // am Anfang steht der Ball position.x = current_cord.x = SCREEN_WIDTH / 2 - bmp->w / 2; // Ball wird zentriert auf der X Achse position.y = current_cord.y = SCREEN_HEIGHT / 2 - bmp->h / 2; // Ball wird zentriert auf der Y Achse } };
So in der Richtung sollte nun unsere Klasse aussehen. Ist dies der Fall können wir weiter zum nächsten Kapitel.