softwaretechnologie ii (teil 1): simulation und 3d programmierung medienwiss./medieninformatik am3...
TRANSCRIPT
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
tbResult CBall::Move(float fTime)
{
// 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
int CGame::CreateBall(tbVector3 vPosition,
tbVector3 vVelocity,
BOOL bGrabbed)
{
// Freien Ball suchen
for(int iBall = 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
return iBall;
}
}
// 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
tbResult CGame::CreateBlockRow(char* pcBlocks,
tbVector3 vStartPos)
{
int iType;
// 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 berechnenfor(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 berechnenfDistLeft = 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;
else m_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;
else m_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_iScore erstellt
Die vergangene Zeit eine Levels wird in der Variable CGame::m_fLevelTime gespeichert
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_klasse aufruft
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 = new tbMusic;
if(m_pMusic->Init("Data\\Music.mp3"))
{
// Fehler!
TB_ERROR("Fehler beim Laden der Musik!", TB_ERROR);
}
m_pMusic->Play();
return TB_OK;
Danke für eure Aufmerksamkeit!!!