440 likes | 571 Views
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
E N D
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
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
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
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. 1 Die Grundklasse Breakanoid • Breakanoid.cpp • Breakanoid.h
Variablen I // CBreakanoid-Klasse class CBreakanoid { public: // Variablen 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 [...]
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
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
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); }
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
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; }
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; }
HauptfunktionWinMain 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(); TB_SAFE_DELETE(g_pBreakanoid); MessageBox(NULL,"Fehler beim Initialisieren des Spiels!", "Fehler", MB_OK | MB_ICONEXCLAMATION); return 1; }
HauptfunktionWinMain 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(); TB_SAFE_DELETE(g_pBreakanoid); return 0; }
Das TitelbildCIntro // 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 };
Das TitelbildDie 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]
Das TitelbildInitialisieren, 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); return TB_OK; }
Das TitelbildRendern I //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; • Rendern erfolgt durch ein Rechteck, das mit der Textur des Bildes überzogen wird ‚Transformierte Vertizes‘
Das TitelbildRendern 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);
Das TitelbildRendern 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 […]
Das TitelbildRendern 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 TitelbildMove // 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; }
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
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); break; case 2:// Spiel beenden PostQuitMessage(0); break; } }
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]) { g_pBreakanoid->m_apSound[0]->PlayNextBuffer(); m_iCursor++; tbDelay(80); }