Pong

Klasse erstellen

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.

1)
Class Vector