340 likes | 456 Views
Das erste Spiel Teil 3 ( Scherfgen 531 - 552). Softwaretechnologie II (Teil 1): Simulation und 3D Programmierung Medienwiss ./Medieninformatik AM3 Visuelle Programmierung I Referent: Janek Rudolf. Inhalt. Schritt 5: Bälle hinzufügen Schritt 6: Die Blöcke Schritt 7: Versuche
E N D
Das erste SpielTeil 3 (Scherfgen 531 - 552) Softwaretechnologie II (Teil 1): Simulation und 3D Programmierung Medienwiss./Medieninformatik AM3 Visuelle Programmierung I Referent: Janek Rudolf
Inhalt • Schritt 5: Bälle hinzufügen • Schritt 6: Die Blöcke • Schritt 7: Versuche • Schritt8: Punkte • Schritt 9: Sound für das Spiel • Schritt10: Hier spielt die Musik
Schritt 5: Bälle hinzufügen • Erstellung von Klasse CBall • CGame werden 16 CBall-Variablen in einem Array hinzugefügt • U.a. BOOL m_bExists, BOOL m_bGrabbed, tbVector3 m_vPosition, tbVector3 m_vVelocity
Fortbewegung und Kollisionen des Balls • Bewegung durch Methode CBall:Move • Fortbewegung des Balls: m_vVelocity x vergangene Zeit seit letztem Frame(CBall:Move) + m_vPosition
Ball kann mit Wand und Schläger kollidieren • Radius des Ballmodells = 0.25 • Kollision mit der Wand tritt ein wenn: x-Koordinate des Positionsvektor +/- 0.25 in oder hinter der Wand liegt. Rechts: x=9.25, Links: x=-9.25, z=4.25 • Kollision mit dem Schläger tritt ein wenn: xBall >xSchläger-1.25 ^xBall<xSchläger +1.25^ zBall>zSchläger -0.25^zBall<zSchläger+0.25
Indem die z-Komponente des Geschwindigkeitsvektors umgekehrt wird, prallt der Ball ab
Verlieren des Balls • Ist m_vPosition <= -12, geht der Ball verloren • Die Variable m_bExists wird auf FALSE gesetzt • Im günstigsten Fall „klebt“ danach ein neuer Ball auf dem Schläger • Ist das der Fall, wird m_bGrabbed == TRUE und die move-Methode verlassen
// Bewegt einen Ball tbResultCBall::Move(floatfTime) { // Wenn der Ball klebt: abbrechen! if(m_bGrabbed) return TB_OK; // Position verändern m_vPosition += m_vVelocity * fTime; // Wenn der Ball eine Wand berührt, prallt er ab. if(m_vPosition.x - 0.25f <= -9.25f) {bWall = TRUE; m_vVelocity.x *= -1.0f; m_vPosition.x = -9.0f;} // Linke Wand if(m_vPosition.x + 0.25f >= 9.25f) {bWall = TRUE; m_vVelocity.x *= -1.0f; m_vPosition.x = 9.0f;} // Rechte Wand if(m_vPosition.z + 0.25f >= 4.25f) {bWall = TRUE; m_vVelocity.z *= -1.0f; m_vPosition.z = 4.0f;} // Obere Wand // Auch am Schläger prallt er ab (Natürlich!). if(m_vPosition.x >= m_pGame->m_vPaddlePos.x - 1.25f && m_vPosition.x <= m_pGame->m_vPaddlePos.x + 1.25f && m_vPosition.z <= m_pGame->m_vPaddlePos.z + 0.25f && m_vPosition.z - m_vVelocity.z * fTime >= m_pGame->m_vPaddlePos.z + 0.25f) // Es gibt eine Kollision! Wir kehren die z-Komponente des Bewegungsvektors um. m_vVelocity.z *= -1.0f; if(m_vPosition.z < -12.0f) { m_bExists = FALSE; } }
Rendern • Implementieren der Methode Cball::Render • tbMatrixTranslation(GetAbsPosition()) verschiebt Ballmodell an richtige Stelle • Absolute Position des Balls wird gebraucht in Translationsmatrix, sonst ist sie relativ zum Schläger • CGame::m_pBallModel->Render rendert danach den Ball
Die Bälle • Erster Ball bei Eintritt ins Level, Zero Memory leert Array m_aBall • Erstellung eines Balls durch Methode CGame::CreateBall mit Parametern der Position, des Geschwindigkeitsvektors und dem Zustand, ob angeklebt oder nicht • Erster Ball: Relative Position zum Schläger(0,0, 0,25), Geschwindigkeitsvektor (0,0,0) und Zustand = Angeklebt
Erstellung einer Liste in Form des Arrays CGame::m_aBall mit 16 Elementen • Mit m_bExists prüfen ob/wie viele Bälle existieren • CGame::Move geht die 16 Elemente durch, wenn die Elemente existieren, Aufruf seiner Move-Funktion und Aufruf von CBall::Render
// Erstellt einen neuen Ball intCGame::CreateBall(tbVector3 vPosition, tbVector3 vVelocity, BOOL bGrabbed) { // Freien Ball suchen for(intiBall = 0; iBall < 16; iBall++) { if(!m_aBall[iBall].m_bExists) { // Freier Ball gefunden! Ausfüllen! m_aBall[iBall].m_bExists = TRUE; m_aBall[iBall].m_pGame = this; m_aBall[iBall].m_bGrabbed = bGrabbed; m_aBall[iBall].m_vPosition = vPosition; m_aBall[iBall].m_vVelocity = vVelocity; // Index des neuen Balls liefern returniBall; } } // Kein Platz mehr! return -1; }
Abfeuern mit der Leertaste // Wenn die Leertaste gedrückt wurde, wird der klebende Ball // abgeworfen. if(g_pbButtons[TB_KEY_SPACE] && m_aBall[0].m_bExists && m_aBall[0].m_bGrabbed) { // Sound abspielen g_pBreakanoid->m_apSound[3]->PlayNextBuffer(); // Ball abfeuern! m_aBall[0].m_bGrabbed = FALSE; // Die Position eines klebenden Balls ist immer relativ // zum Schläger. Wir wandeln sie nun in eine absolute Position um. m_aBall[0].m_vPosition += m_vPaddlePos;
// Den Bewegungsvektor des Balls berechnen wir zufällig. m_aBall[0].m_vVelocity.x = tbFloatRandom(-4.0f, 4.0f); m_aBall[0].m_vVelocity.y = 0.0f; m_aBall[0].m_vVelocity.z = tbFloatRandom(8.0f, 10.0f); // Den Bewegungsvektor des Schlägers addieren m_aBall[0].m_vVelocity += m_vPaddleVel; // Dem Ball einen kleinen "Schubs" nach vorne geben m_aBall[0].m_vPosition.z += 0.1f; }
Schritt 6: Die Blöcke • Erstellen der Klasse CBlock mit der einzigen Methode Render • Variablen : m_iEnergy: Anzahl Energiepunkte • M_itype: Typ des Blocks (1 blau, 2 orange, 3 grün, 4 gelb) type= Anzahl Energiepunkte • tbVector3 m_vPosition: Position des Blocks auf dem Spielfeld • CGame* m_pGame: Kopie des CGame-Zeigers
Blockreihen • Erstellen eines Arrays mit 64 CBlock-Elementen (wie bei den Bällen) • ZeroMemory setzt bei betreten des Levels alle Blöcke zurück • Level wird in Zeilen bzw. Strings unterteilt – Jedes Zeichen im String = ein Block • 1 Zeile = 9 Zeichen/Blöcke - Mehrere Zeilen = Level • Funktion CGame::CreateBlockRow um die Blockreihe zu erstellen
// Erstellt eine Reihe von Blöcken tbResultCGame::CreateBlockRow(char* pcBlocks, tbVector3 vStartPos) { intiType; // Alle Zeichen im String durchgehen for(DWORD dwChar = 0; dwChar < strlen(pcBlocks); dwChar++) { // Wenn das Zeichen kein Leerzeichen ist... if(pcBlocks[dwChar] != ' ') { // Freien Block suchen for(DWORD dwBlock = 0; dwBlock < 64; dwBlock++) { if(m_aBlock[dwBlock].m_iEnergy <= 0) { // Freier Block gefunden - ausfüllen! // Zeichen im String in einen Blocktyp umwandeln. iType = 0; if(pcBlocks[dwChar] == '1') iType = 1; else if(pcBlocks[dwChar] == '2') iType = 2; else if(pcBlocks[dwChar] == '3') iType = 3; else if(pcBlocks[dwChar] == '4') iType = 4;
Levels von Breakanoid // Je nach Level die Blöcke erstellen switch(iLevel) { case 1: CreateBlockRow(" 1 1 1 ", tbVector3(-8.0f, 0.0f, 2.0f)); CreateBlockRow("12 111 21", tbVector3(-8.0f, 0.0f, 1.0f)); CreateBlockRow("12 21", tbVector3(-8.0f, 0.0f, 0.0f)); CreateBlockRow(" 112 211 ", tbVector3(-8.0f, 0.0f, -1.0f)); break; case 2: CreateBlockRow(" 222 ", tbVector3(-8.0f, 0.0f, 1.0f)); CreateBlockRow(" 2 3 2 ", tbVector3(-8.0f, 0.0f, 0.0f)); CreateBlockRow(" 2 333 2 ", tbVector3(-8.0f, 0.0f, -1.0f)); CreateBlockRow("111111111", tbVector3(-8.0f, 0.0f, -2.0f)); break;
Kollision zwischen Ball und Block • Prüfung in CBall::Move, ob Ball und Block kollidieren • Ist das der Fall, wird dem Block ein EP abgezogen, der Ball prallt ab • Problem: Welcher Block und welche Seite des Blocks wird getroffen • x-oder z-Komponente des Geschwindigkeitsvektors umkehren
Zuerst Bedingung setzen, ob der Ball überhaupt mit dem Block kollidieren kann • Danach berechnen, wo der Ball den Block trifft, dazu die Minimalste Distanz von den 4 Blöcken zum Ball berechnen
Bei links oder rechts vom Block, Vorzeichenveränderung der x-Komponente, • Bei oben oder unten vom Block, Vorzeichenveränderung der z-Komponente • nach Kollision, dem Ball einen Schub in die richtige Richtung geben
// Kollision mit den Blöcken berechnen for(DWORD dwBlock = 0; dwBlock < 64; dwBlock++) { if(m_pGame->m_aBlock[dwBlock].m_iEnergy > 0) { vBlock = m_pGame->m_aBlock[dwBlock].m_vPosition; // Befindet sich der Ball im Kollisionsbereich? if(m_vPosition.x + 0.25f >= vBlock.x - 1.0f && m_vPosition.x - 0.25f <= vBlock.x + 1.0f && m_vPosition.z + 0.25f >= vBlock.z - 0.5f && m_vPosition.z - 0.25f <= vBlock.z + 0.5f) // Entfernung des Balls von allen Blockseiten berechnen fDistLeft = fabsf(m_vPosition.x + 0.25f - (vBlock.x - 1.0f)); fDistRight = fabsf(m_vPosition.x - 0.25f - (vBlock.x + 1.0f)); fDistTop = fabsf(m_vPosition.z - 0.25f - (vBlock.z + 0.5f)); fDistBottom = fabsf(m_vPosition.z + 0.25f - (vBlock.z - 0.5f));
// Minimale Distanz berechnen fMinDist = TB_MIN(fDistLeft, TB_MIN(fDistRight, TB_MIN(fDistTop, fDistBottom))); // Wenn die Distanz zur linken oder rechten Seite am kleinsten ist... if(fMinDist == fDistLeft || fMinDist == fDistRight) { // Ball an der z-Achse abprallen lassen m_vVelocity.x *= -1.0f; // Dem Ball einen kleinen "Schubs" geben if(fMinDist == fDistLeft) m_vPosition.x -= 0.1f; elsem_vPosition.x += 0.1f; } else { // Ball an der x-Achse abprallen lassen m_vVelocity.z *= -1.0f; // Dem Ball einen kleinen "Schubs" geben if(fMinDist == fDistTop) m_vPosition.z += 0.1f; elsem_vPosition.z -= 0.1f; } // Dem Block einen Energiepunkt abziehen und Punkte addieren m_pGame->m_aBlock[dwBlock].m_iEnergy--; // Kollision ist immer nur mit einem einzigen Block möglich! break;
Multiball • Wird ein Block zerstört (Energie =0), ermittelt tbIntRandom eine Zahl zwischen 1 und 14 • Ist es eine 7, wird ein neuer Ball mit zufälliger Flugrichtung nach unten erstellt
Ende des Levels/Spiels: • Das Level ist geschafft, wenn alle Blöcke zerstört sind • Dazu wird im m_aBlock-Array die Anzahl der Blöcke mit vorhandener Energie gezählt • Sind alle Level geschafft, fängt der Spieler wieder bei Level 1 an
Schritt 7: Versuche • Variable CGame::m_iNumTries wird zu Beginn des Spiels auf 5 gesetzt • In der Methode CGame:Move werden mit Hilfe einer for-Schleife die Bälle gezählt • Ist die Anzahl der Bälle 0, so wird dem Spieler mit m_iTriesLeft- - ein Versuch abgezogen und ein neuer Ball auf den Schläger „geklebt“
Game Over ! • Um das Spiel bei m_iTries == 0 zu beenden, hinzufügen der Variable BOOL m_bGameOver • Bei TRUE wird in CGame::Render mit grüner und roter Schrift „Game Over!“ ausgegeben
Schritt8: Punkte • Um eine Punktzahl zu speichern, wurde bereits die Variable CGame::m_iScoreerstellt • Die vergangene Zeit eine Levels wird in der Variable CGame::m_fLevelTimegespeichert • Umso weniger Zeit, desto mehr Punkte und pro Level 10000 Punkte extra • Zudem 100 Punkte bei Treffen eines Blocks, 1000-4000 bei Zerstörung
// Level 1 bringt 10000 Punkte, Level 2 20000 Punkte usw.. m_iScore += m_iLevel * 10000; // Je weniger Zeit man gebraucht hat, desto mehr Extrapunkte gibt's. // Bei x benötigten Sekunden gibt es den x-ten Teil von 1000000 Punkten. m_iScore += (DWORD)(1000000.0f * (1.0f / m_fLevelTime)); // Dem Block einen Energiepunkt abziehen und Punkte addieren m_pGame->m_aBlock[dwBlock].m_iEnergy--; m_pGame->m_iScore += 100; // Wenn der Block zerstört wurde, gibt es Extrapunkte if(m_pGame->m_aBlock[dwBlock].m_iEnergy <= 0) { m_pGame->m_iScore += m_pGame->m_aBlock[dwBlock].m_iType * 1000;
Schritt 9: Sound für das Spiel • Da alle Sounds schon geladen wurden, geht es nur noch um das abspielen • Die Sounds, die im Array CBreakanoid::m_apSound[11] gespeichert sind, werden gebraucht für: • CBreakanoid::m_apSound[2]: neuen Level betreten • CBreakanoid::m_apSound[3]: Ball abfeuern • CBreakanoid::m_apSound[4]: Ball geht verloren • CBreakanoid::m_apSound[5]: Ball trifft Schläger • CBreakanoid::m_apSound[6]: Extraball-Sound • CBreakanoid::m_apSound[7]: Ball prallt an Wand ab • CBreakanoid::m_apSound[8-11]: Ball trifft Block, 4 verschiedene Sounds
Das Abspielen des Sounds funktioniert, indem man PlayNextBuffer auf der tbSound_klasseaufruft • Wenn der Ball einen Block berührt, wird durch Zufallsgenerator einer von 4 Tönen abgespielt: // Zufälligen "Pling"-Sound abspielen iSound= tbIntRandom(8, 11); iBuffer= g_pBreakanoid->m_apSound[iSound]->PlayNextBuffer(); if(iBuffer != -1)
Schritt10: Hier spielt die Musik • Die Musik ist als MP3(MUSIC.MP3) gespeichert • Musik wird die komplette Zeit unverändert abgespielt • Dafür muss die Variable tbMusic* m_pMusic in der CBreakanoid –Klasse abgelegt werden
Geladen und abgespielt wird sie in CBreakanoid::Load // Musik laden und gleich abspielen m_pMusic = newtbMusic; if(m_pMusic->Init("Data\\Music.mp3")) { // Fehler! TB_ERROR("Fehler beim Laden der Musik!", TB_ERROR); } m_pMusic->Play(); return TB_OK;