NeHe - Lektion 32 - Picking, Alpha Blending, Sortierung (Teil 2)

Zurück zu Teil 1

Die letzte Richtung, in die unser Objekt sich bewegen kann, ist nach unten. Wenn dir gleich 3 ist, wollen wir, dass sich unser Objekt den Screen hinunter bewegt. Wir machen das, indem wir den Y-Wert des Objektes dekrementieren, basierend auf der Zeit, die verstrichen ist. Beachten Sie, dass wir uns langasamer nach unten bewegen, als nach oben. Wenn ein Objekt fällt, ist unsere fixe Fall-Rate gleich 0.0025f. Wenn wir uns nach oben bewegen ist diese gleich 0.012f.

        if (object[loop].dir==3)                    // wenn Richtung nach unten ist
            object[loop].y-=0.0025f*float(milliseconds);        // bewege nach unten

Nachdem wir unsere Objekte bewegt haben, müssen wir überprüfen, ob diese immer noch sichtbar sind. Der folgende Code überprüft, wo unser Objekt auf dem Screen ist. Wir können ungefähr berechnen wir weit nach links ein Objekt wandern kann, indem wir die Objekt-Distanz in den Screen hinein nehmen minus 15.0f (um sicher zu gehen, dass es etwas außerhalb des Screens ist) und dividieren durch 2. Für die unter Ihnen, die es noch nicht wissen... Wenn Sie sich 20 Einheiten in den Screen hinein befinden, abhängig von der Art und Weise wie Sie die Perspektive aufgesetzt haben, haben Sie ungefähr 10 Einheiten von der linken Seite des Screens bis zum Zentrum und 10 vom Zentrum bis nach rechts. So das -20.0f(Distanz)-15.0f(extra Platz)=-35.0f... dividiert durch 2 und Sie erhalten -17.5f. Das sind ungefähr 7.5 Einheiten außerhalb der linken Seite des Screens. Das bedeutet wiederum, dass das Objekt komplett außerhalb der Sicht ist.

Wie dem auch sei... nachdem wir sicher gestellt haben, dass das Objekt links außerhalb des Screens ist, überprüfen wir, ob es sich nach links bewegt (dir=0). Wenn es sich nicht nach links bewegt, kümmern wir uns nicht darum, dass es links außerhalb des Screen ist!

Zu letzt überprüfen wir, ob das Objekt getroffen wurde. Wenn das Objekt links außerhalb des Screens ist, es sich nach links bewegt und nicht getroffen wurde, ist es für den Spieler zu spät, es noch zu treffen. Deshalb inkrementieren wir den Wert von miss. Damit verringern wir die Moral und inkrementieren die Anzahl der verfehlten Ziele. Wir setzen die hit-Variable des Objekts auf TRUE, so dass der Computer denkt, dass es getroffen wurde. Das erzwingt, dass sich das Objekt selbst zerstört (und was uns erlaubt, dem Objekt eine neue Textur, Richtung, Rotation, etc. zuzuweisen).

        // wenn wir zuweit nach links gekommen sind, die Richtung links ist und das Objekt nicht getroffen wurde
        if ((object[loop].x// inkrementiere miss (verfehlte Objekte)
            object[loop].hit=TRUE;                    // Setze hit auf True um unser Objekt manuell zu zerstören
        }

Der folgende Code macht genau das Selbe wie der obige Code, aber diesmal überprüfen wir nicht, ob wir nach links hin den Screen verlassen haben, sondern ob wir nach rechts hin den Screen verlassen haben. Wir überprüfen ebenfalls, ob sich das Objekt nach rechts bewegt und nicht in irgend eine andere Richtung. Wenn das Objekt ausserhalb des Screens ist, inkrementieren wir den Wert von miss und zerstören das Objekt, indem wir unserem Programm mitteilen, dass das Objekt getroffen wurde.


        // wenn wir zuweit nach rechts gekommen sind, die Richtung rechts ist und das Objekt nicht getroffen wurde
        if ((object[loop].x>-(object[loop].distance-15.0f)/2.0f) && (object[loop].dir==1) && !object[loop].hit)
        {
            miss+=1;                        // inkrementiere miss (verfehlte Objekte)
            object[loop].hit=TRUE;                    // Setze hit auf True um unser Objekt manuell zu zerstören
        }

Der fallende Code ist ziemlich einfach. Wir überprüfen, ob das Objekt gerade den Boden getroffen hat. Wir wollen nicht, dass es durch den Boden durch fällt, was -3.0f wäre. Statt dessen überprüfen wir, ob das Objekt unter -2.0f ist. Dann überprüfen wir, ob das Objekt wirklich fällt (dir=3) und dass das Objekt noch nicht getroffen wurde. Wenn das Objekt unterhalb von -2.0f auf der Y-Achse ist, inkrementieren wir miss und setzen die hit Variable des Objektes auf TRUE (damit es sich selbst zerstört, wenn es den Boden trifft... netter Effekt).


        // wenn wir zu weit unten sind, die Richtung fallend ist und das Objekt noch nicht getroffen wurde
        if ((object[loop].y// inkrementiere miss (verfehlte Objekte)
            object[loop].hit=TRUE;                    // Setze hit auf True um das Objekt manuell zu zerstören
        }

Anders als der vorherige Code, ist der Code für das nach-oben-steigen etwas anders. Wir wollen nicht, dass das Objekt durch die Wolken geht! Wir überprüfen, ob die Y-Variable des Objetks größer als 4.5f ist (den Wolken nahe). Wir stellen ebenso sicher, dass das Objekt nach oben wandert (dir=2). Wenn der Y-Wert des Objektes größer als 4.5f ist, zerstören wir nicht das Objekt, sondern ändern einfach seine Richtung. Auf diese Weise kommt das Objekt recht schnell aus dem Boden (erinnern Sie sich daran, dass es schneller nach oben steigt als fällt) und wenn es zu hoch steigt, ändern wir die Richtung, so dass es anfängt wieder auf den Boden zu fallen.

Es gibt keinen Grund das Objekt zu zerstören oder die Miss-Variable zu inkrementieren. Wenn Sie das Objekt verfehlen, wenn es Richtung Himmel fliegt, gibt es noch die Chance es zu treffen, wenn es herunterfällt. Der Code für's fallen wird die Zerstörung des Objektes dann übernehmen.

        if ((object[loop].y>4.5f) && (object[loop].dir==2))        // wenn wir zuweit nach oben kommen und die Richtung nach oben ist
            object[loop].dir=3;                    // ändere die Richtung nach unten
    }
}

Als nächstes haben wir den Objekt-Zeichnen-Code. Ich wollte eine einfache und schnelle Art, die Spiel-Objekte zu zeichnen. zusammen mit dem Fadenkreuz, so wenig Code wie möglich. Object erwartet 3 Parameter. Als erstes haben wir die Breite. Die Breite kontrolliert wie breit das Objekt ist, wenn es gezeichnet wird. Dann haben wir die Höhe. Die Höhe kontrolliert wie hoch das Objekt ist, wenn es gezeichnet wird. Zu letzt haben wir texid. Die texid wählt die Textur aus, die wir verwenden wollen. Wenn wir einen Eimer zeichnen wollen, was Textur 1 ist, würden wir den Wert 1 für texid übergeben. Ziemlich einfach!

Kurze Zusammenfassung. Wir wählen eine Textur und zeichnen einen Quad. Wir verwenden Standard Texturkoordinaten, so dass die komplette Textur auf die Seite des Quads gemapped wird. Der Quad wird entgegen des Uhrzeigersinns gezeichnet (das ist notwendig, damit CUlling nachher funktioniert).

void Object(float width,float height,GLuint texid)                // zeichne ein Objekt mittels der übergebenen Breite, Höhe und Textur
{
    glBindTexture(GL_TEXTURE_2D, textures[texid].texID);            // wähle die korrekte Textur aus
    glBegin(GL_QUADS);                            // fange an einen Quad zu zeichnen
        glTexCoord2f(0.0f,0.0f); glVertex3f(-width,-height,0.0f);    // unten links
        glTexCoord2f(1.0f,0.0f); glVertex3f( width,-height,0.0f);    // unten rechts
        glTexCoord2f(1.0f,1.0f); glVertex3f( width, height,0.0f);    // oben rechts
        glTexCoord2f(0.0f,1.0f); glVertex3f(-width, height,0.0f);    // oben links
    glEnd();                                // fertig mit dem Zeichnen des Quad
}

Der Explosions-Code erwartet einen Parameter. num ist der Objekt-Identifizierer. Um eine Explosion zu erzeugen, müssen wir einen Teil der Explosions-Textur holen, ähnlich wie beim holen eines jeden Zeichens aus der Font-Textur. Die zwei folgenden Zeilen berechnen die Spalte (ex) und Zeile (ey) aus einer einzelnen Zahl (frame).

Die erste folgende Zeile ermittelt den aktuellen Frame und dividiert ihn durch 4. Die Division durch 4 dient zur Verlangsamung der Animation. %4 dient dazu, dass der Wert in der Spanne von 0-3 bleibt. Wenn der Wert größer als 3 ist, würde ein Wrap-Around statt finden und er wäre gleich 0, Wenn der Wert 5 ist, würde daraus 1 werden. Eni Wert von 9 wäre 0,1,2,3,0,1,2,3,0. Wir dividieren das Endergebniss durch 4.0f, da die Textur-Koordinaten in der Spanne zwischen 0.0f und 1.0f liegen. Unsere Explosions-Textur enthält 4 Explosions-Bilder von links nach rechts und 4 hoch und runter.

Hoffentlich sind Sie nicht vollständig verwirrt. Wenn unsere Zahl vor der Division nur 0,1,2 oder 3 sein kann, kann unsere Zahl nach der Division durch 4 nur 0.0f, 0.25f (1/4), 0.50f (2/4) oder 0.75f (3/4) sein. Damit erhalten wir unsere links-nach-rechts Texturkoordinate (ex).

Als nächstes berechnen wir die Zeile (ey). Wir ermitteln den aktuellen Objekt-Frame und dividieren ihn durch 4, um die Animation etwas zu verlangsamen. Wir dividieren dann erneut durch 4, um die komplette Zeile zu eliminieren. Zu letzt dividieren wir ein letztes Mal durch 4, um unsere vertikale Texturkoordinate zu erhalten.

Ein kurzes Beispiel. Wenn unser aktueller Frame 16 wäre. ey=((16/4)/4)/4 oder 4/4/4 oder 0.25f. Eine Zeile runter. Wenn unser aktueller Frame 60 wäre. ey=((60/4)/4)/4 oder 15/4/4 oder 3/4 oder 0.75f. Der Grund warum 15/4 nicht gleich 3.75 ist, ist der, dass wir mit Integers arbeiten, bis wir die finale Division durchführen. Deshalb kann der Wert von ey nur ein Wert von 4 Werten sein... 0.0f, 0.25f, 0.50f oder 0.75f. Angenommen wir bleiben innerhalb unserer Textur (was verhindert, dass frame über den Wert von 63 geht).

Ich hoffe das macht Sinn... es ist einfache, aber einschüchternde Mathematik.

void Explosion(int num)                                // zeichnet eine animierte Animation für das Objekt "num"
{
    float ex = (float)((object[num].frame/4)%4)/4.0f;            // berechne Explosion X Frame (0.0f - 0.75f)
    float ey = (float)((object[num].frame/4)/4)/4.0f;            // berechne Explosion Y Frame (0.0f - 0.75f)

Nun, da wir die Textur-Koordinaten haben, müssen wir nur noch unseren texturierten Quad zeichnen. Die Vertexkoordinaten sind fixiert bei -1.0f und 1.0f. Sie werden bemerken, dass wir ey von 1.0f subtrahieren. Wenn wir das nicht machen würden, würde die Animation in umgekehrter Reihenfolge gezeichnet werden... Die Explosion würde größer werden, anstatt auszufaden. Der Effekt würde nicht richtig aussehen!

Wir binden die Explosions-Textur bevor wir den texturierten Quad zeichnen. Erneut wird der Quad gegen den Uhrzeigersinn gezeichnet.

    glBindTexture(GL_TEXTURE_2D, textures[5].texID);            // wähle die Explosions Textur aus
    glBegin(GL_QUADS);                            // fange an einen Quad zu zeichnen
        glTexCoord2f(ex      ,1.0f-(ey      )); glVertex3f(-1.0f,-1.0f,0.0f);    // unten links
        glTexCoord2f(ex+0.25f,1.0f-(ey      )); glVertex3f( 1.0f,-1.0f,0.0f);    // unten rechts
        glTexCoord2f(ex+0.25f,1.0f-(ey+0.25f)); glVertex3f( 1.0f, 1.0f,0.0f);    // oben rechts
        glTexCoord2f(ex      ,1.0f-(ey+0.25f)); glVertex3f(-1.0f, 1.0f,0.0f);    // oben links
    glEnd();                                // fertig mit dem Zeichnen des Quad

Wie ich oben erwähnt habe, sollte der Wert von Frame nicht größer als 63 sein, ansonsten würde die Animation von vorne anfangen. Deshalb inkrementieren wir den Wert von frame und überprüfen ob er größer als 63 ist. Wenn er es ist, rufen wir InitObject(num) auf, was das Objekt zerstört und ihm neue Werte zuweise, um ein komplett neues Objekt zu erzeugen.


    object[num].frame+=1;                            // inkrementiere aktuellen Explosions Frame
    if (object[num].frame>63)                        // sind wir durch alle 16 Frames durch?
    {
        InitObject(num);                        // Initialisiere das Objekt (weise neue Werte zu)
    }
}

Dieser Codeabschnitt zeichnet alle Ziele (Objekte) auf den Screen. Wir fangen damit an, die Modelview-Matrix zu resetten. Wir translatieren dann 10 Einheiten in den Screen hinein und iterieren dann mit einer Schleife von 0 bis zum aktuellen Level.

void DrawTargets(void)                                // Zeichne die Ziele (muss seperat geschehen)
{
    glLoadIdentity();                            // Resette die Modelview Matrix
    glTranslatef(0.0f,0.0f,-10.0f);                        // bewege 20 Einheiten in den Screen hinein
    for (int loop=0; loop// iteriere durch 9 Objekte
    {

Die erste Codezeile ist das Geheimnis des Pickings individueller Objekte. Sie weist jedem Objekt einen Namen (Zahl) zu. Das erste zu zeichnende Objekt wird 0 sein. Das zweite Objekte 1, etc... Wenn die Schleife 29 erreicht, wird das zu letzt zu zeichnende Objekte den Namen 29 bekommen. Nachdem den Objekten Namen zugewiesen wurden, pushen wir die Modelview Matrix auf den Stack. Es ist wichtig anzumerken, dass der Aufruf von glLoadName() vom Programm ignoriert wird, wenn man nicht im Selektions-Modus ist.

Wir begeben uns dann zu der Lokation auf dem Screen, wo wir unser Objekt zeichnen wollen. Wir benutzen object[loop].x um uns auf der X-Achse zu positionieren und object[loop].y um uns auf der Y-Achse zu positionieren und object[loop].distance um das Objekt auf der Z-Achse zu positionieren (Tiefe in den Screen hinein). Wir sind schon 10 Einheiten in den Screen hinein translatiert, so dass die aktuelle Distanz, wo das Objekt gezeichnet wird, gleich object[loop].distance-10.0f sein wird.

        glLoadName(loop);                        // weise dem Objekt einen Namen (ID) zu
        glPushMatrix();                            // Pushe die Modelview Matrix
        glTranslatef(object[loop].x,object[loop].y,object[loop].distance);    // Positioniere das Objekt (x,y)

Bevor wir das Objekt zeichnen, müssen wir überprüfen, ob es getroffen wurde oder nicht. Wir machen das, indem wir überprüfen ob object[loop].hit gleich TRUE ist. Wenn dem so ist, springen wir zu Explosion(loop), was die Explosions-Animation zeichnet, anstatt des eigentlichen Objektes. Wenn das Objekt nicht getroffen wurde, rotieren wir das Objekt auf seine Z-Achse um object[loop].spin Grad bevor wir Object() aufrufen.

Object erwartet 3 Parameter. Der erste ist die Breite, der zweite die Höhe und der dritte ist die Nummer der zu verwendenten Textur. Um die Breite und Höhe zu erhalten, benutzen wir das Array size[object[loop].texid].w und size[object[loop].texid].h. Damit schauen wir die Breite und Höhe in unserem vordefinierten Objekt-Größe-Array nach, welches sich am Anfang des Programms befindet. Der Grund warum wir object[loop].texid verwenden, ist der, weil es die Art des zu zeichnenden Objekts reoräsentiert. Eine texid von 0 ist immer das Blueface... eine texid von 3 ist immer die Cola-Dose, etc.

Nachdem ein Objekt gezeichnet wurde, poppen wir die Matrix, resetten die View, so dass unser nächstes Objekt an der richtigen Stelle auf dem Screen gezeichnet wird.

        if (object[loop].hit)                        // wenn das Objekt getroffen wurde
        {
            Explosion(loop);                    // zeichne eine Explosion
        }
        else                                // ansonsten
        {
            glRotatef(object[loop].spin,0.0f,0.0f,1.0f);        // Rotiere das Objekt
            Object(size[object[loop].texid].w,size[object[loop].texid].h,object[loop].texid);    // zeichne das Objekt
        }
        glPopMatrix();                            // Poppe die Modelview Matrix
    }
}

Hier geschieht nun das Zeichnen. Wir fangen damit an, den Screen zu löschen und unsere Modelview-Matrix zu resetten.

void Draw(void)                                    // Zeichne unsere Szene
{
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // lösche Screen und Depth Buffer
    glLoadIdentity();                            // Resette die Modelview Matrix

Als nächstes pushen wir die Modelview-Matrix auf den Stack und wähle die Sky-Textur aus (Textur 7). Der Himmel besteht aus 4 texturierten Quads. Die ersten 4 Vertices zeichnen den Himmel in der Distanz vom Boden aus nach oben hin. Die Textur auf diesem Quad wird sich ziemlich langsam bewegen. Die nächsten 4 Vertices zeichnen den Himmel erneut am genau den selben Platz, aber die Himmels-Textur wir sich schneller bewegen. Die beiden Texturen blenden im Alpha-Blending-Modus ineinander über, was einen netten mehrschichtigen Effekt erzeugt.

    glPushMatrix();                                // Pushe die Modelview Matrix
    glBindTexture(GL_TEXTURE_2D, textures[7].texID);            // wähle die Himmels-Textur aus
    glBegin(GL_QUADS);                            // fange an Quads zu zeichnen
        glTexCoord2f(1.0f,roll/1.5f+1.0f); glVertex3f( 28.0f,+7.0f,-50.0f);    // oben rechts
        glTexCoord2f(0.0f,roll/1.5f+1.0f); glVertex3f(-28.0f,+7.0f,-50.0f);    // oben links
        glTexCoord2f(0.0f,roll/1.5f+0.0f); glVertex3f(-28.0f,-3.0f,-50.0f);    // unten links
        glTexCoord2f(1.0f,roll/1.5f+0.0f); glVertex3f( 28.0f,-3.0f,-50.0f);    // unten rechts

        glTexCoord2f(1.5f,roll+1.0f); glVertex3f( 28.0f,+7.0f,-50.0f);        // oben rechts
        glTexCoord2f(0.5f,roll+1.0f); glVertex3f(-28.0f,+7.0f,-50.0f);        // oben links
        glTexCoord2f(0.5f,roll+0.0f); glVertex3f(-28.0f,-3.0f,-50.0f);        // unten links
        glTexCoord2f(1.5f,roll+0.0f); glVertex3f( 28.0f,-3.0f,-50.0f);        // unten rechts

Damit die Illusion vermittelt wird, dass der Himmel sich auf den Betrachter bewegt, zeichnen wir zwei weitere Quads, aber diesmal aus der Distanz heraus hin zum Betrachter. Die ersten 4 Vertices zeichnen langsam vorbeiziehende Wolken und die übrigen 4 zeichnen schnell bewegende Wolken. Die beiden Schichten blenden im Alpha-Blending-Modus ineinander über, um einen mehrschichtigen Effekt zu erzeugen. Die zweite Schicht der Wolken hat einen Offset bei 0.5f, so dass die zwei Texturen nicht in einer Reihe sind. Das selbe passiert mit den zwei Schichten der Wolken darüber. Der Offset der zweiten Schicht beginnt bei 0.5f.

Der finale Effekt aller 4 Quads ist ein Himmel, der scheinbar von aus der Distanz sich zum Betrachter hin nach oben erstreckt. Ich hätte auch eine texturierte halbe Sphere verwenden können, aber ich war zu faul und der jetzige Effekt ist immer noch gut.

        glTexCoord2f(1.0f,roll/1.5f+1.0f); glVertex3f( 28.0f,+7.0f,0.0f);    // oben rechts
        glTexCoord2f(0.0f,roll/1.5f+1.0f); glVertex3f(-28.0f,+7.0f,0.0f);    // oben links
        glTexCoord2f(0.0f,roll/1.5f+0.0f); glVertex3f(-28.0f,+7.0f,-50.0f);    // unten links
        glTexCoord2f(1.0f,roll/1.5f+0.0f); glVertex3f( 28.0f,+7.0f,-50.0f);    // unten rechts

        glTexCoord2f(1.5f,roll+1.0f); glVertex3f( 28.0f,+7.0f,0.0f);        // oben rechts
        glTexCoord2f(0.5f,roll+1.0f); glVertex3f(-28.0f,+7.0f,0.0f);        // pben links
        glTexCoord2f(0.5f,roll+0.0f); glVertex3f(-28.0f,+7.0f,-50.0f);        // unten links
        glTexCoord2f(1.5f,roll+0.0f); glVertex3f( 28.0f,+7.0f,-50.0f);        // unten rechts
    glEnd();                                // fertig mit dem Zeichnen von Quads

Wo wir den Himmel fertig haben, wird es Zeit, den Boden zu zeichnen. Wir fangen an den Boden dort zu zeichnen, wo die Himmels-Textur am tiefsten ist und zum Betrachter kommt. Die Boden-Textur bewegt sich mit der selben Geschwindigkeit wie die schnellen Wolken.

Die Textur wird 7 Mal von links nach rechts und 4 Mal von hinten nach vorne gezeichnet, um etwas mehr Detail hinzuzufügen und um zu verhindern, dass die Textur klobig aussieht. Das wird erreicht, indem die Texturkoordinaten von 0.0f - 1.0f bis 0.0f - 7.0f (links nach rechts) inkrementiert wird und 0.0f - 4.0f (hoch und runter).

    glBindTexture(GL_TEXTURE_2D, textures[6].texID);            // Wähle die Grund Textur
    glBegin(GL_QUADS);                            // Zeichne einen Quad
        glTexCoord2f(7.0f,4.0f-roll); glVertex3f( 27.0f,-3.0f,-50.0f);    // oben rechts
        glTexCoord2f(0.0f,4.0f-roll); glVertex3f(-27.0f,-3.0f,-50.0f);    // oben links
        glTexCoord2f(0.0f,0.0f-roll); glVertex3f(-27.0f,-3.0f,0.0f);    // unten links
        glTexCoord2f(7.0f,0.0f-roll); glVertex3f( 27.0f,-3.0f,0.0f);    // unten rechts
    glEnd();                                // fertig mit dem Zeichnen des Quad

Nachdem wir den Himmel und den Grund gezeichnet haben, springen wir zu dem Code, der all unsere Ziele (Objekte) zeichnet und der da heißt: DrawTargets().

Nachdem unsere Ziele gezeichnet wurden, poppen wir die Modelview Matrix vom Stack (und bringen sie somit in den vorherigen Status).


    DrawTargets();                                // zeichne unsere Ziele
    glPopMatrix();                                // Poppe die Modelview-Matrix

Der folgende Code zeichnet das Fadenkreuz. Wir fangen damit an, dass wir die aktuelle Fenster-Dimensionen ermitteln. Wir machen das, nur für den Fall, dass das Fenster im Fenster-Modus in seiner Größe geändert wurde. GetClientRect ermittelt die Dimensionen und speichert sie in window. Dann wählen wir unsere Projektions-Matrix aus und pushen sie auf den Stack. Wir resetten die Sicht mit glLoadIdentity() und wechseln dann in den Ortho-Modus. Das Fenster geht dann von 0 bis window.right von links nach rechts und von 0 bis window.bottom von unten nach oben auf dem Screen.

Der dritte Parameter von glOrtho() ist eigentlich der untere Wert, ich habe aber den oberen und unteren Wert vertauscht. Das habe ich gemacht, damit das Fadenkreuz gegen den Uhrzeigersinn gerendert wird. Mit 0 für oben und window.bottom für unten würde die Windung in die entgegengesetzte Richtung verlaufen und das Fadenkreuz und der Text würde nicht erscheinen.

Nachdem die Ortho-Ansicht gesetzt wurde, wählen wir die Modelview-Matrix aus und die Position des Fadenkreuz. Da der Screen gespiegelt ist, müssen wir die Maus ebenfalls invertieren. Ansonsten würde sich unser Fadenkreuz nach unten bewegen, wenn wir die Maus nach oben bewegen würden und genauso andersherum. Deshalb subtrahieren wir den aktuellen mouse_y Wert vom unteren Rand des Fensters (window.bottom).

Nachdem wir zur aktuellen Maus-Position translatiert haben, zeichnen wir das Fadenkreuz. Wir machen das, indem wir Objects() aufrufen. Anstatt von Einheiten geben wir die Breite und Höhe in Pixel an. Das Fadenkreuz wird 16x16 Pixel breit und hoch sein und die Textur, die dazu verwendet wird, das Objekt zu zeichnen, ist Textur 8 (die Fadenkreuz Textur).

Ich habe mich aus zwei Gründen dazu entschieden einen eigenen Cursor zu verwenden... als erstes und wichtigstes, sieht es cool aus und kann mit jedem Grafikprogramm einfach geändert werden, welches den Alpha-Kanal unterstützt. Zum zweiten wird der Cursor bei einigen Grafikkarten im Fullscreen-Modus nicht angezeigt. Und das Spiel ohne Cursor im Fullscreen-Modus zu spielen, ist nicht wirklich einfach :)


    // Fadenkreuz (in der Ortho Ansicht)
    RECT window;                                // Speicherplatz für die Fenster-Dimensionen
    GetClientRect (g_window->hWnd,&window);                    // ermittle die Fenster-Dimensionen
    glMatrixMode(GL_PROJECTION);                        // wähle die Projektions-Matrix aus
    glPushMatrix();                                // speicher die Projektions-Matrix
    glLoadIdentity();                            // Resette die Projektions-Matrix
    glOrtho(0,window.right,0,window.bottom,-1,1);                // Setze einen Ortho Screen auf
    glMatrixMode(GL_MODELVIEW);                        // wähle die Modelview-Matrix
    glTranslated(mouse_x,window.bottom-mouse_y,0.0f);            // gehe zur aktuellen Maus-Position
    Object(16,16,8);                            // zeichne das Fadenkreuz

Dieser Codeabschnitt gibt einen Titel am oberen Rand des Screens aus und zeigt Level sowie Punkte in der unteren linken und rechten Ecke des Screens an. Der Grund, warum ich den Code hier platziert habe, ist, dass der Text einfacher korrekt zu positionieren ist, wenn man im Ortho-Modus ist.


    // Spiel Statistik / Titel
    glPrint(240,450,"NeHe Productions");                    // gebe Titel aus
    glPrint(10,10,"Level: %i",level);                    // gebe Level aus
    glPrint(250,10,"Score: %i",score);                    // gebe Score aus

Dieser Abschnitt überprüft, ob der Spieler mehr als 10 Objekte verfehlt hat. Wenn dem so ist, setzen wir die Anzahl der Verfehlungen (miss) auf 9 und setzen game auf TRUE. Indem wir game auf TRUE setzen, signalisieren wir, dass das Spiel vorbei aus.

    if (miss>9)                                // Haben wir mehr als 10 Objekte verfehlt?
    {
        miss=9;                                // Beschränke Verfehlungen auf 10
        game=TRUE;                            // Game Over TRUE
    }

Im folgenden Code überprüfen wir, ob game gleich TRUE ist. Wenn game gleich TRUE ist, geben wir die GAME OVER Nachricht aus. Wenn game gleich false ist, geben wir die Moral des Spielers (aus 10) aus. Die Moral berechnet sich durch die Subtraktion der Verfehlungen des Spielers von 10. Je mehr der Spieler verfehlt, umso niedriger ist seine Moral.

    if (game)                                // Ist das Spiel vorbei?
        glPrint(490,10,"GAME OVER");                    // Game Over Nachricht 
    else
        glPrint(490,10,"Morale: %i/10",10-miss);            // gebe aus: Moral #/10

Als letztes müssen wir die Projektions-Matrix auswählen, unsere Matrix wieder herstellen (poppen) - zurück in den vorherigen Status - den Matrix-Modus auf Modelview setzen und den Buffer am Ende flushen, um sicher zu gehen, dass alles Objekte gerendert wurden.

    glMatrixMode(GL_PROJECTION);                        // wähle die Projektionsmatrix aus
    glPopMatrix();                                // stelle die alte Projektions Matrix wieder her
    glMatrixMode(GL_MODELVIEW);                        // wähle die Modelview Matrix aus

    glFlush();                                // Flushe die GL Rendering Pipeline
}

Dieses Tutorial ist das Ergebniss vieler langer Nächte und vieler vieler Stunden Coding und HTML schreiben. Am Ende dieses Tutorials sollten Sie ein gutes Verständnis darüber haben, wie Picking, Sortierung, Alpha Blending und Alpha Testing funktioniert. Picking erlaubt es Ihnen, interaktive Point und Click Software zu erzeugen. Alles, von Spielen bis zu coolen GUI's. Das beste Feature von Picking ist, dass Sie nicht verfolgen müssen, wo Ihre Objekte sind. Sie verbinden es mit einem Namen und überprüfen einfach, ob es getroffen wurde. So einfach ist das! Mit Alpha Blending und Alpha Testing können Sie Ihre Objekte komplett solide machen oder absolut löchrig. Die Ergebnisse sind großartig und Sie müssen sich nicht um die Objekte kümmern, die durch die Texturen sichtbar sind, es sei denn Sie wollen es! Wie immer hoffe ich, dass Sie dieses Tutorial genossen habe und hoffe, dass ich einige coole Spiele oder Projekte, die auf dem Code dieses Tutorials basieren, sehen werde. Wenn Sie irgendwelche Fragen haben oder Fehler in diesem Tutorial finden, lassen Sie es mich bitte wissen... Ich bin auch nur ein Mensch :)

Ich hätte noch mehr Dinge wie Physik, mehr Grafiken, mehr Sound, etc. hinzufügen können. Aber dies ist nur ein Tutorial! Ich habe dieses Tutorial nicht geschrieben um Sie übermäßig zu beeindrucken. Ich habe es geschrieben, um Ihnen OpenGL beizubringen, mit so wenig Verwirrungen wie möglich. Ich hoffe ein paar coole Änderungen am Code zu sehen. Wenn Sie etwas cooles dem Tutorial hinzufügen, schicken Sie mir eine Demo. Wenn es eine coole Änderung ist, werde ich sie auf der Download Seite posten. Wenn ich genügend Modifikationen bekomme, werde ich vielleicht eine neue Seite nur für die modifizierten Versionen dieses Tutorials aufsetzen! Ich bin hier, um Ihnen einen Anfangspunkt zu bieten. Der Rest liegt bei Ihnen :)

Jeff Molofee (NeHe)

* DOWNLOAD Visual C++ Code für diese Lektion.

* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Christian Kindahl )
* DOWNLOAD Code Warrior 5.3 Code für diese Lektion. ( Conversion by Scott Lupton )
* DOWNLOAD Delphi Code für diese Lektion. ( Conversion by Michal Tucek )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by Dan )
* DOWNLOAD Euphoria Code für diese Lektion. ( Conversion by Evan Marshall )
* DOWNLOAD Linux/SDL Code für diese Lektion. ( Conversion by Edgar Costanzo )
* DOWNLOAD LWJGL Code für diese Lektion. ( Conversion by Mark Bernard )
* DOWNLOAD Mac OS X/Cocoa Code für diese Lektion. ( Conversion by Bryan Blackburn )
* DOWNLOAD Visual Studio .NET Code für diese Lektion. ( Conversion by Grant James )



Deutsche Übersetzung: Joachim Rohde
Der original Text ist hier zu finden.
Die original OpenGL Tutorials stammen von NeHe's Seite.