Das erste Spiel Universität zu Köln Historisch Kulturwissenschaftliche Informationsverarbeitung WS 12/13 Übung: Visuelle Programmierung I – Simulation und 3D Programmierung Prof. Dr. Manfred Thaller Referentin: Marietta Steinhöfel
Gliederung Das Spielprinzip Das Spielgerüst Der Code Die Grundklasse CBreakanoid Das Titelbild Das Hauptmenü Sound
1. Das Spielprinzip Name: „Breakanoid“ (Arkanoid + Breakout) „2.5D-Grafik“: Grafik: 3D Bewegung: 2D (xz-Ebene) Kamera synchron zu Schläger
2. Das Spielgerüst Die Spielzustände enum-Aufzählung verwaltet Spielzustände: EGameState (speichert Wert für Spielzustand) Intro = Titelbild, über das man zum Hauptmenü gelangt GS_INTRO Hauptmenü = Hintergrundbild & Vordergrund (Auswahl Menüeintrag) GS_MAIN_MENU Spiel GS_GAME Kein Spielzustand GS_NONE Breakanoid.h
2. Das Spielgerüst Die Breakanoid-Klasse Grundklasse, die das ganze Spiel verwaltet: CBreakanoid Instanz der Klasse wird in WinMain-Funktion erzeugt Methode wird aufrufen, um Spiel zu starten Am Ende wird Instanz wieder gelöscht Breakanoid.h
2. Das Spielgerüst Die Spielzustandklassen Zusätzlich Klassen für Spielzustände: CIntro CMainMenu CGame Zeiger auf Instanzen d. Klassen in CBreakanoid gespeichert & erstellt Breakanoid.h
2. Das Spielgerüst Methoden CBreakanoid- und Spielzustandklassen haben folgende Methoden: Load lädt Daten für ganzes Spiel (CBreakanoid::Load) oder bestimmte Zustände (CIntro::Load) Unload Herunterfahren Init Initialisierung des kompletten Spiels bzw. Spielzuständen Aufruf der Load -Methode Exit Macht Schritte rückgängig Aufruf der Unload-Methode
2. Das Spielgerüst Verwaltung der Spielzustände Spielzustand ändern mit Methode CBreakanoid::SetGameState bekommt gewünschten Wert übergeben (GameState) Aktuellen Zustand verlassen z.B.: CIntro::Exit Methode aufrufen Neuen Zustand initialisieren z.B.: CMainMenu::Init Beginn & Ende = kein Spielzustand GS_NONE
3. Der Code
3. 1 Die Grundklasse Breakanoid Breakanoid.cpp Breakanoid.h
Variablen I // CBreakanoid-Klasse class CBreakanoid { public: tbConfigm_Config; // Konfiguration TriBase-Engine PDIRECT3DSTATEBLOCK9m_pStateBlock; // Statusblock für Direct3D // Instanzen der Spielzustände – ihre Zeiger werden in Klasse gespeichert CIntro* m_pIntro; // Intro CMainMenu* m_pMainMenu; // Hauptmenü CGame* m_pGame; // Spiel EGameState m_GameState; // Speichern des Aktuellen Spielzustands floatm_fTime; // Stoppuhr = zählt wie viele Sek. Zustand schon aktiv ist [...] Hier ist die Deklaration für die Variablen der Klasse class CBreakanoid .h
Variablen II // Globale Variablen extern CBreakanoid*g_pBreakanoid; // Breakanoid-Zeiger extern float*g_pfButtons; // Array mit float-Werten zur Abfrage der Eingabegeräte speichert Zustand analoger Knöpfe extern BOOL*g_pbButtons; // Array für digitale Werte d. Knöpfe 2. Außerdem drei gloable Variablen .h
Methoden SetGameState I // Setzt einen neuen Spielzustand tbResult CBreakanoid::SetGameState(EGameState NewGameState) //erwartet EGameState-Wert { tbResult r = TB_OK; // 1. Alten Spielzustand entladen Switch (m_GameState) // GameState: speichert aktuellen/alten Zustand case GS_INTRO: m_pIntro->Exit(); break; // Alten Zustand herunterfahren case GS_MAIN_MENU :m_pMainMenu->Exit(); break; //durch Aufruf der Exit-Methode case GS_GAME :m_pGame->Exit(); break; } // Zeit zurücksetzen m_fTime = 0.0f; // Stoppuhr auf null zurück, weil Zustand nicht mehr aktiv Weil Klasse Spielzustände verwaltet, brauchen wir eine Methode, die den aktuellen Spielzustand setzt, um Spieler zu einem Menüpunkt zu schicken: Die Methode heißt: SetGameState und erwartet einen EGameState-Wert .cpp
Methoden SetGameState II // 2. Neuen Spielzustand laden m_GameState = NewGameState; // Neuen Spielzustand initialisieren switch (m_GameState) // Init-Methoden Aufruf für entsprechenden Spielzustand { case GS_INTRO: r = m_pIntro->Init(); break; case GS_MAIN_MENU: r = m_pMainMenu->Init(); break; case GS_GAME: r = m_pGame->Init(); break; } // Eventuelle Fehler abfangen if(r) TB_ERROR("Fehler beim Laden des Spielzustands!", TB_ERROR); return TB_OK;
Methoden Load I // Lädt das Spiel tbResult CBreakanoid::Load() // lädt relevante Spiel-Daten { char acFilename[256]; // Direct3D initialisieren - Aufruf DirectX-Klassen der TriBase-Engine: // Einstellungen des Konfig.Dialog liegen schon in m_Config (Header) // IDI_ICON1 =Ressource, die Icon des Spiels enthält (zB. Spielszene) if(tbDirect3D::Instance().Init(&m_Config, "Breakanoid", NULL, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1)))) // Fehler! TB_ERROR("Fehler beim Initialisieren von Direct3D!", TB_ERROR); } Load-Methode: CBreakanoid::Load() Laden des Spiels (relevante Daten!) CBreakanoid::Unload() Löschen, Speicher frei geben
Methoden Load II […] // DirectInput initialisieren if(tbDirectInput::Instance().Init()) // Speicher für die analogen Knöpfe reservieren g_pfButtons = new float [tbDirectInput::Instance().GetNumButtons()]; // So viel Speicher für Array reserviert - wie analoge Knöpfe g_pbButtons = new BOOL [tbDirectInput::Instance().GetNumButtons()]; // Und nun noch DirectSound... if(tbDirectSound::Instance().Init(&m_Config, NULL, DSSCL_PRIORITY, FALSE)) // FALSE, weil in dem Spiel kein 3D-Sound ist { // Fehler! TB_ERROR("DirectSound konnte nicht initialisiert werden!", TB_ERROR); }
Methoden Init I // Initialisiert das Spiel komplett tbResult CBreakanoid::Init() { tbResult r; // TriBase-Engine initialisieren und den Konfigurationsdialog aufrufen: if (tbInit()) return TB_ERROR; r = tbDoConfigDialog(&m_Config); //Konfigurationsdialog Aufruf & abspeichern in m_Conig //TB_CANCELED = wird von tbDoConfigDialog zurück geliefert, wenn Benutzer im Dialog auf ABRECHEN klickt if(r == TB_CANCELED) return TB_CANCELED; else if(r) TB_ERROR("Engine konnte nicht initialisiert werden!", r); // Laden... if(Load()) TB_ERROR("Fehler beim Laden des Spiels!", TB_ERROR); //Spieldaten laden durch LOAD Methoden Aufruf Initialisierung des gesamten Spiels in CBreakanoid::Init() es wird auf die bereits besprochenen Methoden SetGameState und Load zurück gegriffen Init Methode wird später in Hauptfunktion WinMain aufgerufen CBreakanoid::Exit() Macht Inittialisierungsschritte von Init-Methode rückgängig: Setzt Status auf SetGameState (GS_NONE) Ruft Unload auf löscht die Instanzen
Methoden Init II // Klassen für alle Spielzustände erstellen als Instanzen durch NEW m_pIntro = new CIntro; m_pMainMenu = new CMainMenu; m_pGame = new CGame; // Wir beginnen beim Intro! SetGameState(GS_INTRO); // SetGameState setzt Spielzustand aufs Titelbild (Intro) return TB_OK; }
Methoden Move I // Bewegt das Spiel tbResult CBreakanoid::Move(float fTime) // liefert seit letztem Frame vergangene Zeit in Sek. { tbResult r = TB_OK; // Eingabegeräte abfragen. Wertet Eingabe des Benutzers aus + speichert tbDirectInput::Instance().GetState(g_pfButtons, g_pbButtons); […] // Aktuellen Spielzustand bewegen switch(m_GameState) //ruft Move-Funktion für jeweilige Klasse auf case GS_INTRO: r = m_pIntro->Move(fTime); break; case GS_MAIN_MENU: r = m_pMainMenu->Move(fTime); break; case GS_GAME: r = m_pGame->Move(fTime); break; } Render- (zeichnen) und Move-Funktion (bewegen) werden einmal Pro Frame aufgerufen Move bewegt das Spiel Render zeichnet alles Funktionen erwartet FLOAT-Paramter = liefert seit letzten Frame vergangene Sekunden damit Programm überall gleich schnell läuft
Methoden Move II // Eventuelle Fehler abfangen if(r) TB_ERROR("Fehler beim Bewegen des Spielzustands!", TB_ERROR); // Frame-Zeit-Wert zu Zustand-Laufzeit-Stopuhr addieren m_fTime += fTime; return TB_OK; }
Methoden Render // Rendert das Spiel tbResult CBreakanoid::Render(float fTime) { […] // Aktuellen Spielzustand rendern switch(m_GameState) { case GS_INTRO: r = m_pIntro->Render(fTime); break; case GS_MAIN_MENU: r = m_pMainMenu->Render(fTime); break; case GS_GAME: r = m_pGame->Render(fTime); break; } // Eventuelle Fehler abfangen if(r) TB_ERROR("Fehler beim Rendern des Spielzustands!", TB_ERROR); return TB_OK;
Methoden Run // Move- und Render-Funktion (Kapselung) tbResult Move(float fTime) {return g_pBreakanoid->Move(fTime);} tbResult Render(float fTime) {return g_pBreakanoid->Render(fTime);} // Lässt das Spiel laufen tbResult CBreakanoid::Run() { // Nachrichtenschleife betreten. Ruft in jedem Frame Move und Render auf if(tbDoMessageLoop(::Move, ::Render)) // Fehler! TB_ERROR("Fehler in der Nachrichtenschleife!", TB_ERROR); } return TB_OK; Alle Methoden liegen vor Man kann das Spiel laufen lassen in CBreakanoid::Run() Es wird ein tbDoMessageLoop aufgerufen, die (Nachrichtenschleife) kümmert sich darum, dass Move und Render in jedem Frame aufrufen werden Es ist in der Rückriffunktion problematisch direkt auf die Breakanoid-Klasse zuzugreifen. Daher die Kapselung als Alternative ruft entsprechende Methoden auf
Hauptfunktion WinMain I // Windows-Hauptfunktion int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char* pcCommandLine, int iShowCommand) { tbResult r; // Spiel initialisieren g_pBreakanoid = new CBreakanoid; r = g_pBreakanoid->Init(); // Init-Funktionsaufruf // Wenn Benutzer "ABBRECHEN" drückt = Programm beenden if(r == TB_CANCELED) TB_SAFE_DELETE(g_pBreakanoid); return 0; } // Wenn es nicht die Benutzereingabe war, handelt es sich um ein Fehler else if(r) g_pBreakanoid->Exit(); MessageBox(NULL, "Fehler beim Initialisieren des Spiels!", "Fehler", MB_OK | MB_ICONEXCLAMATION); return 1; Komplettes Spielgerüst liegt nun vor es wird in Hauptfunktion WinMain eingesetzt Wird zu Beginn aufgerufen *WINAPI ‚Windows Application Programming Interface‘ = Programm Schnittstelle für Programme auf Windows
Hauptfunktion WinMain II // Spiel laufen lassen if(g_pBreakanoid->Run()) { g_pBreakanoid->Exit(); // Abfangen von Fehlern TB_SAFE_DELETE(g_pBreakanoid); MessageBox(NULL, "Fehler im Spiel!", "Fehler", MB_OK | MB_ICONEXCLAMATION); return 1; } // Spiel verlassen g_pBreakanoid->Exit(); return 0;
3.2 Das Titelbild
Das Titelbild CIntro // Intro.h // Klasse für das Intro class CIntro { public: // Variablen PDIRECT3DTEXTURE9 m_pTitle; // Titelbild-Textur // Konstruktor inline CIntro() : m_pTitle(NULL) {} // Methoden tbResult Init(); // Initialisierung = Betreten des Spielzustands tbResult Exit(); // Herunterfahren = Verlassen d. S. tbResult Load(); // Laden aller Daten tbResult Unload(); // Entladen tbResult Move(float fTime); // Bewegen tbResult Render(float fTime); // Rendern }; Jetzt wird Erster Spielzustand implementiert: GS_INTRO Bestandteile sind: Bild (Title.jpg im DATA Ordner), Text (Titel, Aufforderung) Name der Klasse: CIntro Hat Methoden: Spielzustände 1 Variable: Textur mit Titelbild
Das Titelbild Die Schrift // Load-Methode in Breakanoid.cpp // Schriftarten laden m_pFont1 = new tbFont; // Schriftart 1 // Schrift besteht immer aus zwei Dateien -> liegen im Data-Ordner: if(m_pFont1->Init("Data\\Font1.tga", "Data\\Font1.tbf")) { // Fehler! TB_ERROR("Fehler beim Laden der Schriftart Data\\Font1!", TB_ERROR); } [analog zu Schriftart 2] Laden der Schrift (Text) erfolgt in der Breakanoid – Load Methode so muss sie nur 1x geladen werden und nicht bei jeder Klasse 2 versch. Schriftarten werden in Variablen gespeichert: m_pFont1 für große, dekorativen Text m_pFont2 für Punktzahlen
Das Titelbild Initialisieren, Laden, Entladen //Intro.cpp tbResult CIntro::Load() // Init ruft Load auf { // Titelbild laden (als Textur) m_pTitle = tbTextureManager::Instance().GetTexture("Data\\Title.jpg"); if(m_pTitle == NULL) TB_ERROR("Fehler beim Laden von Data\\Title.jpg!", TB_ERROR); return TB_OK; } // __________________________________________________________________ tbResult CIntro::Unload() // Exit ruft Unload auf // Die Textur löschen tbTextureManager::Instance().ReleaseTexture(m_pTitle);
Das Titelbild Rendern I Rendern erfolgt durch ein Rechteck, das mit der Textur des Bildes überzogen wird ‚Transformierte Vertizes‘ //Intro.cpp // Vertizes für das Titelbild struct STitleVertex { tbVector 3vPosition; float fRHW; D3DCOLOR Color; tbVector2 vTex0; static const DWORD dwFVF; }; const DWORD STitleVertex::dwFVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1; Transformatierte Vertizes sind dazu da eine 2D-Grafik mit Direct3D zu zeichnen Weil manchmal reichen 2-Dimensionale-Grafiken aus. So wie in diesem Fall: Es wird ein Rechteck benötigt, um zB Text darzustellen, das pixelgenau auf dem Bildschirm platziert werden soll (Ergebnis: Benutzeroberfläche)
Das Titelbild Rendern II // Rendert den Spielzustand tbResult CIntro::Render(float fTime) { STitleVertex aVertex[4]; // 4 Vertizes, für jede Bildschirmecke ein Vertex // Puffer leeren und Szene beginnen tbDirect3D& D3D = tbDirect3D::Instance(); D3D->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, tbColor(0.0f, 0.0f, 0.0f), 1.0f, 0); D3D->BeginScene(); // ------------------------------------------------------------------ // Vertexformat und Titelbildtextur setzen, Z-Buffer aus D3D.SetFVF(STitleVertex::dwFVF); D3D.SetTexture(0, m_pTitle); D3D.SetRS(D3DRS_ZENABLE, D3DZB_FALSE); Rendern des Bildes: Rechteck besteht aus 4 Vertizes Für jede Bildschirmecke eins Via Dreieckfolge TRIANGLESTRIP gezeichnet
Das Titelbild Rendern III // Die vier Vertizes des Titelbilds erstellen (Rechteck) // Links unten aVertex[0].vPosition = tbVector3(0.0f, D3D.GetScreenSize().y, 0.5f); // Position der Pixelkoordinate aVertex[0].fRHW = 1.0f; // Kehrwert der w-Koordinate aVertex[0].Color = tbColor(1.0f, 0.8f, 0.8f); aVertex[0].vTex0 = tbVector2(0.0f, 1.0f); // Texturkoordinate // Links oben aVertex[1].vPosition = tbVector3(0.0f, 0.0f, 0.0f); aVertex[1].fRHW = 1.0f; aVertex[1].Color = tbColor(0.8f, 1.0f, 0.8f); aVertex[1].vTex0 = tbVector2(0.0f, 0.0f); // ...andere genauso […] Besonderheiten transfortmierter Vertizes: x/y Koordinate (0,0->links oben) = entspricht echte Pixel-Bildschirmkoordinaten ...die anderen Ecken abhängig von ausgewähler Auflösung z-KO muss zwischen 0 und 1 liegen w-KO (Kehrwert davon) zur Transformation
Das Titelbild Rendern IIII // Texturkoordinaten sinusförmig verschieben ("wabbeln") // Texturkoordinaten werden für jedes Vertex, in jedem frame geändert: for(DWORD dwVertex = 0; dwVertex < 4; dwVertex++) { aVertex[dwVertex].vTex0.x += sinf(g_pBreakanoid->m_fTime + (float)(dwVertex)) * 0.01f; aVertex[dwVertex].vTex0.y += cosf(g_pBreakanoid->m_fTime + (float)(dwVertex)) * 0.01f; } // Als Dreiecksfolge zeichnen D3D->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, aVertex, sizeof(STitleVertex)); // Bild mit Trainlges-Trip zeichnen […]
Das Titelbild Move // Bewegt den Spielzustand tbResult CIntro::Move(float fTime) { // Wenn eine der typischen Tasten gedrückt wurde: zum Hauptmenü! // Prüft, ob Eingabe getätigt wurde. Akzeptierte Eingaben: if( g_pbButtons[TB_KEY_NUMPADENTER] || // ENTER g_pbButtons[TB_KEY_RETURN] || g_pbButtons[TB_KEY_SPACE] || // LEERTASTE g_pbButtons[TB_MOUSE_BUTTON(0)] || // MAUSTASTEN g_pbButtons[TB_MOUSE_BUTTON(1)]) // gehe mit Verzögerung von 100 Millisek. zum Hauptmenü tbDelay(100); G_pBreakanoid->SetGameState(GS_MAIN_MENU); } return TB_OK; Bewegen des Spielzustandes in der Move-Methode Übergang im Spiel zeigen
3.3 Das Hauptmenü 1)Klicken aufs Titelbild Ausführen und zeigen! GS_MAIN_MENU mit Klasse CMainMenu Verbindet einzelne Spielteile Wird erreicht durch: 1)Klicken aufs Titelbild 2) Abbrechen des laufenden Spiels 3 Menüpunkte: 1) Starten 2) Hilfe (Klappt nicht…) 3) Abbrechen Wir haben: 3 Texte ausgewählter: Bewegung + Farbwechsel Bewegendes Hintergrundbild
Das Hauptmenü Variablen // MainMenu.h class CMainMenu { public: // Variablen LPDIRECT3DTEXTURE9 m_pBackground; // speichert Hintergrundbild Int m_iCursor; // Menücursor = merkt sich Ausgewählten der 3 Einträge. [0]=erster. BOOL m_bShowingHelp; // Wird Hilfetext angezeigt? // Konstruktor inline CMainMenu() : m_pBackground(NULL) , m_iCursor(0) , m_bShowingHelp(FALSE) // bei TRUE (Cursor auf ‚Hilfe anzeigen‘): // Menueinträge verschwindet + Hilfekasten wird angezeigt {}
Das Hauptmenü Methoden I CMainMenu::Init() Betreten des Hauptmenüs ruft Load-Methode auf lädt nur die Textur des Hintergrundbildes Cursor auf null setzen damit zu Beginn erster Menüeintrag ausgewählt ist CMainMenu::Exit() Verlassen des Hauptmenüs ruft Unload auf löscht Textur aus Speicher Analog zu Titelbild
Das Hauptmenü Render I Rendern des Bildes wie bei Intro (‚wabbeln‘) Texte für 3 Menüeinträge zeichnen. Dafür gibt es ein Array mit drei Einträgen: tbResult CMainMenu::Render(float fTime) { SBackgroundVertex aVertex[4]; char* apcMenuEntry[3] = {"Spiel starten", "Hilfe anzeigen", "Spiel beenden"}; tbVector2 vPosition; tbColor Color; […]
Das Hauptmenü Render II If-Abfrage prüft, ob Hilfetext oder Menüeinträge gezeichnet werden sollen Position: if(!m_bShowingHelp) { //jeden Text mit Schrifart 'g_pBreakanoid->m_pFont1' rendern g_pBreakanoid->m_pFont1->Begin(); // Die Menüeinträge zeichnen. Jeder der 3 Einträg durchläuft Schleife for(int iEntry = 0; iEntry < 3; iEntry++) // Die Position für den Text dieses Eintrags berechnen vPosition.x = 0.5f; // erster Menüeintrag liegt bei (0.5, 0.4) //jeden weiteren um 0.125 Einheiten nach unten verschieben: vPosition.y = 0.4f + (float)(iEntry) * 0.125f; …
Das Hauptmenü Render III Bewegung: // Wenn der Cursor auf diesem Eintrag liegt, dann schwingt der Text // Wenn render-Eintrag = ausgewählter Eintrag -> dann schwingen if(m_iCursor == iEntry) vPosition.x += 0.05f * sinf(g_pBreakanoid->m_fTime); Farbe berechnen: // Normalerweise ist Eintrag COLOR dunkelblau. // Wenn der Cursor aber darauf liegt, dann ist er heller. if(m_iCursor != iEntry) Color = tbColor(0.3f, 0.3f, 0.9f, 0.75f); // Standardfarbe Blau... else Color = tbColor(0.5f, 0.5f, 1.0f, 1.0f); //... heller & transparenter Text zeichnen: g_pBreakanoid->m_pFont1->DrawText(vPosition, apcMenuEntry[iEntry], // es werden relative und zentrierte Koordinaten/Größen verwendet: TB_FF_ALIGN_HCENTER | TB_FF_ALIGN_HCENTER | TB_FF_RELATIVE | TB_FF_RELATIVESCALING, // Text wird mit 1,5 skaliert -1, Color, Color + tbColor(-0.3f, 0.4f, 0.0f), tbVector2(1.5f, 1.5f));
Das Hauptmenü Move I // Bewegt den Spielzustand tbResult CMainMenu::Move(float fTime) { if(!m_bShowingHelp) // Wenn showing Help = FALSE Pfeiltasten werden bewegt // Wird Taste nach unten/oben gedrückt, wird Cursor durchs Hauptmenüs bewegt if(g_pbButtons[TB_KEY_UP]) // Cursor nach unten bewegen m_iCursor--; tbDelay(80); // … mit Verzögerung } if(g_pbButtons[TB_KEY_DOWN]) // Cursor nach oben bewegen m_iCursor++; tbDelay(80); // Cursor in die Grenzen weisen es gibt ja nur drei Menüpunkt zum Wählen [0,1,2] if(m_iCursor < 0) m_iCursor = 2; if(m_iCursor > 2) m_iCursor = 0; […]
Das Hauptmenü Move II // Wenn die Enter-, Leer- oder Return-Taste gedrückt wurde, // dann möchte der Benutzer einen Eintrag auswählen oder den Hilfetext wieder ausblenden. if(g_pbButtons[TB_KEY_RETURN] || g_pbButtons[TB_KEY_NUMPADENTER] || g_pbButtons[TB_KEY_SPACE]) { if(!m_bShowingHelp) // Nun kommt es darauf an, was gerade ausgewählt ist! // -> Wenn Hilfetext = FALSE, Cursor navigieren! switch(m_iCursor) case 0: // Spiel starten g_pBreakanoid->SetGameState(GS_GAME); // Spielzustand auf GS_GAME setzen break; case 1: // Hilfe anzeigen m_bShowingHelp = TRUE; tbDelay(100); case 2: // Spiel beenden PostQuitMessage(0); }
Das Hauptmenü Move III else // Ist Hilfetext = TRUE, dann abschalten { // Die Hilfe wieder deaktivieren m_bShowingHelp = FALSE; tbDelay(100); } […]
Das Hauptmenü Sound I Sorgt für Töne beim Bewegen u. Betätigen des Cursors tbDirectSound-Klasse in Breakanoid initialisert! Breakanoid.h: Sound Array [12] es gibt 12 unterschiedliche Sounds Breakanoid.cpp: Sounds werden in CBreakanoid::Load() geladen: // Sounds laden for(DWORD s = 0; s < 12; s++) { sprintf(acFilename, "Data\\Sound%d.wav", s + 1); m_apSound[s] = new tbSound; if(m_apSound[s]->Init(acFilename, DSBCAPS_STATIC | DSBCAPS_LOCDEFER | DSBCAPS_CTRLFREQUENCY)) // Fehler! TB_ERROR("Fehler beim Laden eines Sounds!", TB_ERROR); }
Das Hauptmenü Sound II MainMenu.cpp: CMainMenu::Move spielt den Sound ab If-Abfrage: Verschieden Töne für versch. Eingaben: // Sound Nr.1 beim Drücken UP/DOWN if(g_pbButtons[TB_KEY_UP]) { g_pBreakanoid->m_apSound[0]->PlayNextBuffer(); m_iCursor--; tbDelay(80); } if(g_pbButtons[TB_KEY_DOWN]) m_iCursor++;