270 likes | 376 Views
V03 Laden und Initialisieren der Grafiken. Grafiken des Spiels laden und testweise auf dem Bildschirm anzeigen eine Klasse anlegen, die alle erforderlichen Grafikoperationen enthält: class display unter Rückgriff auf definierte Klassen CDisplay und CSurface in Ddutil.h und Ddutil.cpp
E N D
V03 Laden und Initialisieren der Grafiken Grafiken des Spiels laden und testweise auf dem Bildschirm anzeigen eine Klasse anlegen, die alle erforderlichen Grafikoperationen enthält: class display unter Rückgriff auf definierte Klassen CDisplay und CSurface in Ddutil.h und Ddutil.cpp CDisplay: allgemeine Schnittstelle zum Grafiksystem stellt übergreifende Dienste bereit CSurface: Instanzen nehmen einzelne darzustellende Bitmaps auf Elemente der Oberfläche werden aus Bitmap - Datei in Klasse CSurface eingelesen
CDisplay: Funktionen von Interesse class CDisplay { public: HRESULT CreateWindowedDisplay; // erzeugt ein Display in einem Fenster HRESULT CreateSurfaceFromBitmap(...); // erzeugt eine Surface aus einer Bitmapdatei HRESULT Blt(...); // kopiert eine Surface in das Display HRESULT Present(); // aktualisiert Display im Fenster LPDIRECTDRAW7 GetDirectDraw(); // gibt Zeiger auf das mit Display verbundene DirectDraw - Objekt zurück HRESULT UpdateBounds(); // aktualisiert Display bei Größenänderung des Fensters };
CSurface: Funktionen von Interesse class CSurface { HRESULT DrawBitmap(); }; - Funktion überträgt eine Bitmap aus einer Datei in eine Surface bzw. Oberfläche
1. Schritt • Klasse display wird mit dem CDisplay versehen und mit erforderlichen Oberflächen bzw. Csurfaces • hinzu kommen Konstruktor display und Destruktor ~display und eine Freigabefunktion namens free_all class display { private: CDisplay dsply; CSurface *hgrnd;// Zeiger auf ein CSurface CSurface *fldst; CSurface *fllst; CSurface *prvst; CSurface *deckel; CSurface *ziff [10]; public: display ();// Initialisierung der Zeiger mit 0 void free_all();// gibt Speicher frei ~display() { free_all(); }// Aufruf von Freigabefunktion };
Konstruktor: alle Zeiger werden mit 0 initialisiert display::display () { int i; hgrnd = 0; fldst = 0; fllst = 0; prvst = 0; deckel = 0; for ( i = 0; i < 10; i++) ziff [i] = 0; }
free_all wird vom Destruktor aufgerufen und beseitigt alle dynamisch angelegten Objekte, d.h. gibt Speicher frei void display :: free_all () { int i; if (hgrnd) delete hgrnd; if (fldst) delete fldst; if (fllst) delete fllst; if (prvst) delete prvst; if (deckel) delete deckel; for (i = 0; i < 10; i++) { if ( ziff [i] ) delete ziff [i];} }
2. Schritt • Hinzufügen einer Initialisierungsmethode • erzeugt Display und Surfaces • Initialisierung des Displays durch die Funktion CreateWindowedDisplay • diese übernimmt den Handle des umschließenden Fensters (wnd) und die gewünschten Abmessungen des Displays (ultris_nettobreite, ultris_nettohoehe) • Zur Erinnerung: HWND ultris_window • Handle; Zeiger, der Zugriff auf Hauptfenster ermöglicht; in WinMain Funktion class display { private: ... public: .. HRESULT init (HWND wnd); };
HRESULT display :: init (HWND wnd) { HRESULT hr; //Fehlercode hr,erfolgt Rücksprung in aufrufendes Programm int i; char fname[20]; hr = dsply.CreateWindowedDisplay (wnd, ultris_nettobreite, ultris_nettohoehe); if (hr < 0) return hr; hr = dsply.CreateSurfaceFromBitmap (&hgrnd, “ul_hgrnd.bmp”, ultris_nettobreite, ultris_nettohoehe); if (hr < 0) return hr; hr = dsply.CreateSurfaceFromBitmap (&fldst, “ul_feld.bmp”, 20, 20); if (hr < 0) return hr; hr = dsply.CreateSurfaceFromBitmap(&fllst, “ul_stein.bmp”, 20, 20);
if (hr < 0) return hr; hr = dsply.CreateSurfaceFromBitmap (&prvst, “ul_prev.bmp”, 15, 15); if (hr < 0) return hr; hr = dsply.CreateSurfaceFromBitmap (&deckel, “ul_adeck.bmp”, 240, 100); if (hr < 0) // überprüft ob bei Erzeugung Fehler aufgetreten return hr; // Rücksprung in aufrufendes Programm for (i=0; i < 10; i++) { sprintf (fname, “ul_z%d.bmp”, i); hr = dsply.CreateSurfaceFromBitmap ( &ziff[i], fname, 20, 40); if (hr < 0) return hr; } return S_OK; }
hr = dsply.CreateSurfaceFromBitmap (&fldst, “ul_feld.bmp”, 20, 20); if (hr < 0) return hr; • einzelne Surfaces werden durch Aufruf der Funktion CreateSurfaceFromBitmap dynamisch aus jeweiliger Bitmap - Datei erzeugt • als Parameter übergeben: • Adresse eines Zeigers, in dem Referenz auf Surface abgelegt wird • Name der Bitmap - Datei • Abmessung (Breite, Höhe) der Bitmap • tritt bei Erzeugung eines Elements ein Fehler auf • Fehlercode hr - erfolgt Rücksprung in aufrufendes Programm • ein Surface entspricht einer Bitmap
Kontinuierlich wirkende Animation für das menschliche Auge - Kinofilm besteht aus Folge von Standbildern • in der Abfolge der Standbilder eine Frequenz von mehr als 20 Bildern bzw. Frames pro Sekunde = Eindruck einer kontinuierlichen Bewegung - unser Display hat Front- und Backbuffer - im Frontbuffer: befindet sich Bild, das gerade angezeigt wird - im Backbuffer: neues Bild, das heißt den nächsten Frame aufbauen • zur Darstellung des nächsten Frames - umschalten zwischen Front- und Backbuffer - zwei benötigte Funktionen: - Funktion um Surfaces in den Backbuffer des Displays zu laden ( Blt) - Funktion um zwischen Front- und Backbuffer umzuschalten (Present)
Funktion Blt = Abkürzung für Blocktransfer • Bitmap einer Surface (Quellsurface) an bestimmte Position des Displays (Zielbereich) transferieren oder “blitten” HRESULT CDisplay::Blt (DWORD x, DWORD y, CSurface * pSurface, RECT* prc) HRESULT CDisplay::Blt (x - Koordinate des Zielbereichs, y-Koordinate des Zielbereichs, Quell - Surface, ausgewähltes Rechteck der Quell Surface (kann fehlen)) • wenn kein Teilrechteck (prc) angegeben wird,so wird die gesamte Bitmap geblittet 2. Funktion zum Umschalten zwischen Front- und Backbuffer HRESULT CDisplay::Present()
Blt - Methoden class display { private:... public: ... void hintergrund () {dsply.Blt (0, 0, hgrnd);} // hintergrund beginnt an Position (0,0), füllt gesamten // Fensterinhalt, Csurface ist hgrnd void abdeckung () {dsply.Blt (60, 0, deckel); } // abdeckung beginnt an stelle (60,0), void ziffer (int pos, int val) {dsply.Blt (120+pos*20, 50, ziff [val] );} // Zifferndarstellung beginnt bei 120, deshalb 120+ // eine Ziffernabbildung ist 20 Pixel breit // int pos ist Index der Ziffer // int val entscheidet über Art der Bitmap void feldstein (int z, int s) {dsply.Blt (80+s*20, 100+z*20, fldst);} // Segment eines gefallenen Steins liegt in einer Zeile z und Spalte s // Feld beginnt bei x = 80 und y = 100 // Zeilen- und Spaltenbreite beträgt jeweils 20 Pixel // Koordinaten berechnen sich daher x = 80+s*20 und y = 100+z*20
void fallstein (int z, int s, int offset){ dsply.Blt (80+s*20, 100+z*20+offset, fllst);} // fallendes Steinsegment in Spalte s, jedoch nicht in Zeile, // da es Pixel für Pixel fällt // zu der letzten Zeile z kommt Zugabe offset, um die das // Segment weiter gerückt ist // Offset = Anzahl von Pixeln, die sich Stein n.u. bewegt hat void prevstein (int p, int z, int s, int b, int h) { dsply.Blt (290+s*15+(4-b)*15/2, 410-p*70+z*15+(4-h)*15/2, prvst);} // Berechnung der Position der Vorschausteine // Steine sollen in Ausgabe zentriert dargestellt werden // es gibt bis zu fünf Vorschausteine p = 0,1,2,3,4 // linke obere Ecke: x = 290 und y = 410-p*70 /* bei Segmentgröße von 15 x 15 Pixeln liegt die linke obere Ecke eines Segments in Spalte s und Zeile z bei x = 290+s*15 und y = 410-p*70 + z*15 */ /* wenn Anzahl der Segmente des Steins in Höhe h und Breite b bekannt ist, kann man den Randausgleich berechnen, wenn Stein in 4 x 4 Segmente großen Vorschaubereich */ /* man erhält Koordinaten: x = 290+s * 15 + (4-b)*15/2 und y = 410 - p*70+z*15+(4-h) * 15/2 */ };
Ziffernbereich Deckel Fallfläche Hintergrund Spieloberfläche Vorschau
Funktion HRESULT present (); • Grafiken aus dem Backbuffer auf den Bildschirm bringen • Phänomen der “Lost Devices”: Zugriff auf Devices wie die Grafikkarte kann “verloren gehen” -> Device befindet sich dann im “lost state” • Grafikoperationen, die nur auf internen Speicher zurück greifen, wie etwa das Blitten, werden fehlerfrei durchgeführt • jedoch wird bei Umschalten von Front- und Backbuffer der lost state kritisch, da jetzt auf Grafikkarte zugegriffen werden muss • Returncode für Fehlermeldung: DDERR_SURFACELOST • verlorene Daten auf Grafikkarte müssen restauriert werden: dies macht Funktion restore
Funktion HRESULT present () • zunächst Ausführen der Present Funktion von CDisplay • sollte das misslingen, weil der Fehlercode übergeben wird, so werden die Surfaces restauriert HRESULT display::present() { HRESULT hr; hr = dsply.Present(); if ( hr == DDERR_SURFACELOST )//fehlercode return restore(); return hr; }
Funktion HRESULT restore(); • restaurieren aller Surfaces des Displays • neuzeichnen aller Bitmaps in Surfaces HRESULT display::restore() {HRESULT hr; int i; char fname[20]; hr = dsply.GetDirectDraw() -> RestoreAllSurfaces(); if (hr < 0) return hr; hr = hgrnd->DrawBitmap (“ul_hgrnd.bmp”, ultris_nettobreite, ultris_nettohoehe); if (hr < 0) return hr; hr = fldst -> DrawBitmap (“ul_feld.bmp”, 20,20); if...fllst, prvst, deckel ... for ( i = 0; i < 10 ; i++) {sprintf (fname, “ul_z%d.bmp”, i); hr = ziff [i] -> DrawBitmap (fname, 20, 40); if ( hr < 0) return hr;} return S_OK;}
zum Abschluss zwei weitere Member - Funktionen... class display { private: ... public:.. void update () { dsply.UpdateBounds();} HRESULT cooperative (){ return dsply.GetDirectDraw() -> TestCooperativeLevel ();} }; • Memberfunktion update:Aufruf, wenn die Lage unseres Hauptfensters auf Bildschirm verändert ist • es geschieht Aufruf der Methode UpdateBounds, die Display an die neue Lage anpasst • Memberfunktion cooperative: stellt „Kooperativität“ des Displays fest
Klasse display ist jetzt vollständig implementiert, wir legen Instanz dieser Klasse mit dem Namen ultris_display an display ultris_display; - Objekt muss zum Schluss in Windows Applikation integriert werden • innerhalb WinMain Funktion Display initialisieren bevor Fenster mit ShowWindow dargestellt wird int APIENTRY WinMain (...) {... if (ultris_display.init (ultris_window) < 0) { // wenn Initialisierung fehl schlägt, Abbruch // der Anwendung mit Fehlerdialog MessageBox (ultris_window, “Fehler beim Initialisieren der Grafik”, “Ultris - Fehlermeldung”, MB_OK | MB_ICONERROR | MB_SETFOREGROUND); return 0; } // sonst geht es mit Anzeige des Fensters // und Main Event Loop weiter ShowWindow (ultris_window, nCmdShow); while (TRUE){...} }
Callback - Handler unseres Hauptfensters ultris_windowhandler • immer wenn Fenster bewegt oder in seiner Größe verändert wurde, erhalten wir eine Benachrichtigung durch die Nachricht WM_MOVE • als Reaktion rufen wir die update Funktion unseres Display auf LRESULT CALLBACK ultris_windowhandler ( ... ) { switch (msg) { ... case WM_MOVE: ultris_display.update(); return 0; case WM_PAINT: int i; ultris_display.hintergrund(); ultris_display.abdeckung();
for (i = 0; i< 6; i++) ultris_display.ziffer (i, i+1); for (i = 0; i < 10; i++) ultris_display.feldstein (19-i, i); for (i = 0; i < 10;i++) ultris_display.fallstein ( 1, i, 2*i); for ( i = 0; i < 4; i++) ultris_display.prevstein ( 3, 0, i, 4, 1); ultris_display.present (); break; } ... } • hier wird message - orientierte Architektur des Windows - Systems deutlich • Fensterausgaben dürfen nicht spontan gemacht werden • System fordert zum Neuzeichnen des Hauptfensters mit Message WM_PAINT auf • wenn diese Message erkannt wird, werden einige Testausgaben gemacht: Hintergrund, Abdeckung, einige Ziffern und Steine
Funktion WinMain • an die Stelle, in der regelmäßig wiederkehrende, das Spiel betreffende Aktivitäten eingebaut werden können, gelangt man immer dann, wenn keine Windows - Message vorliegt • hier kann also der Spielverlauf programmiert werden • zudem: regelmäßiges Prüfen der Devices und der Kooperativität int APIENTRY WinMain (...) { while (TRUE) { if (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {...} else { HRESULT hr; hr = ultris_display.cooperative(); // Prüfen der Kooperativität // wenn der Aufruf der Funktion ein negatives Ergebnis // liefert, so liegt ein Problem vor if (hr < 0) {
switch (hr) { case DDERR_EXCLUSIVEMODEALREADYSET: // eine andere Anwendung hat sich Grafik Device exklusiv // gesichert Sleep (10);//warten bis es frei gegeben wird break; case DDERR_WRONGMODE: // jemand hat Grafikmodus (etwa die Auflösung) geändert // in dieser Situation muss alles noch einmal neu // initialisiert werden // zuvor belegte Ressourcen frei geben ultris_display.free_all(); ultris_display.init (ultris_window); PostMessage (ultris_window, WM_PAINT, 0, 0); // hier Message WM PAINT schicken zum Neuzeichnen des Fensters break; } } else{// hier kann Spiel stehen} } } }
Es gibt weitere Meldungen im Zusammenhang mit Grafikkarte, auf die man reagieren kann • Wenn das System im 8 -Bit -Grafikmodus ist (256 Farben), arbeitet die Grafikkarte mit Farbpalette • Das heißt, auch die Farbpalette muss neu initialisiert werden, wenn sie durch andere Anwendung überschrieben wurde • Entsprechende Message: WM_QUERYNEWPALETTE • Wir verwenden jedoch für Ultris Bitmaps mit mehr als 256 Farben
Kurz zurück zum Blitten - • es ist auch möglich statt rechteckiger Objekte andersformige Objekte zu blitten • Dies geschieht unter Verwendung des Color Keying • Eine in der darzustellenden Bitmap nicht vorkommende Farbe wird als Color Key für die Surface ausgewählt • Alles, was in Color Key Farbe in der Bitmap vorkommt, wird nicht transferiert • Equivalent: „Blue Box“ im Fernsehen • Beispiel: Bitmap liegt in einer Datei vor, alles, was nicht gezeichnet werden soll, ist schwarz • Anlegen einer Surface: CSurface *s • Initialisieren der Surface mit CreateSurfaceFromBitmap • Color Key wird duch Funktionsaufruf gesetzt: s -> SetColorKey (RGB (0, 0, 0)); - schwarze Bildpunkte werden nicht transferiert