AnMaBaGiMa's Home

DirectX-Anwendungen mit C++ erstellen - Vertiefung der Möglichkeiten

Wir wollen uns nun dem Vollbildmodus von DirectDraw widmen. So wie er eigentlich bei allen Spielen eingesetzt wird.
Der Vollbild - oder auch Fullscreen - Modus von DirectDraw bedeutet, dass der komplette Bildschirm zu unserer Zeichenfläche wird. Es sind keine Fenster mehr zu sehen. Der Desktop verschwindet im Hintergrund.
Die Initialisierung von DirectDraw im Vollbildmodus ist der normalen recht ähnlich, jedoch gibt es einige wichtige Einschränkungen:
  • DirectDraw sperrt den Bildschirm komplett. Es können keine MessageBox'en oder ähnliche von der GDI erzeugten Fenster dargestellt werden.
  • die Benutzung eines Debuggers entfällt. Der Einsatz führt meist zum Anhalten oder sogar zum Absturz des Systems.
Für die Entwicklung von Fullscreen-Anwendungen kann also folgender Ratschlag gelten:

In der Entwicklungsphase sollte die Anwendung oder das Spiel im Fenstermodus entwickelt werden. Dabei kann der Bildschirm auf die gewünschte Auflösung (per Hand) eingestellt werden. Das Fenster sollte von der Grösse dann der des Vollbildmodus entsprechen.
Anders: Ist es das Ziel eine Fullscreenanwendung für 800x600 Pixel bei 16 Bit-Farbtiefe zu entwickeln, sollte die Testphase im Fenstermodus mit einem 800x600 grossem Fenster bei einer Bildschirmauflösung von 1024x786 und 16 Bit Farbtiefe erfolgen. Dadurch können Fehler einfach in einer MessageBox ausgegeben werden und die Anwendung kann für die Fehlersuche Debuggt werden.


Wir erzeugen am besten ein neues Projekt. Die Initialisierung von DirectDraw soll von nun an in der Datei InitDDraw7.c platz finden und kann dort immer wieder, auch von anderen Programmen genutzt werden.
Der erste Aufbau ist bekannt:

/* * InitDDraw7.c * Initialisierung von DirectDraw7 * * * */ #include <windows.h> //#define INITGUID #include <ddraw.h> LPDIRECTDRAW7 g_lpDDraw7; //Globales DirectDraw Object LPDIRECTDRAWSURFACE7 g_lpDView; //Primäre Zeichenfläche (Anzeige) DDSURFACEDESC2 g_ViewDesc; //Beschreibung LPDIRECTDRAWSURFACE7 g_lpDPaint; //Arbeitszeichenfläche (zum hineinzeichnen) DDSURFACEDESC2 g_PaintDesc;//Beschreibung
Wir fügen nun eine Funktion InitDirectDraw7 hinzu. Sie wird den Vollbildmodus aktivieren. Wie aber auch schon im Fenstermodus müssen wir der Funktion das Handle des Anwendungsfensters mitgeben. Die ersten Schritte der Initialisierung sollten bekannt sein. Nachdem das zentrale DirectDraw-Objekt erzeugt wurde setzen wir das Kooperationslevel. Diesmal aber nicht "NORMAL" sondern "EXCLUSIV". Dies versetzt DirectDraw in die Lage die volle Herrschaft über den Bildschirm an sich zu reissen. Zusätzlich teilen wir DirectDraw mit, dass wir im Follbildmodus arbeiten möchten. Standard mässig verbietet DirectDraw ab hier die Benutzung von STRG+ALT+ENTF. Falls es aber einmal zu Problemen kommt, ist dies immer noch eine gute Notbremse. Desshalb aktivieren wir diese Möglichkeit zusätzlich.

BOOL InitDirectDraw(HWND hwnd, int iWidth, int iHeight, int iBits) //Diese Funkion initialisiert DirectDraw im Vollbildmodus // die Werte von iWidth, iHeight und iBits bestimmen die AUflösung die gesetzt wird. { LPDIRECTDRAW lpDDraw; if (DirectDrawCreate(NULL, &lpDDraw,NULL)!= DD_OK) { MessageBox(hwnd, "Konnte DirectDraw-Interface nicht erzeugen.\nKein DX installiert??", NULL,MB_OK); return FALSE; } if (lpDDraw->lpVtbl->QueryInterface(lpDDraw, &IID_IDirectDraw7, (void **)&g_lpDDraw7) != DD_OK) { MessageBox(hwnd, "Sie haben kein DirectX - 7 installiert", NULL, MB_OK); return FALSE; } lpDDraw->lpVtbl->Release(lpDDraw); if (g_lpDDraw7->lpVtbl->SetCooperativeLevel(g_lpDDraw7, hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWREBOOT)!=DD_OK) { MessageBox(hwnd, "Vollbildmodus nicht möglich", NULL, MB_OK); return FALSE; } //War das Setzen des Kooperationslevel erfolgreich ist nun der Bildschirm von DDraw //reserviert. Keine MessageBox funktioniert ab hier und //kein Debugger sollte ab hier genutzt werden, wenn doch - kann dies zum Absturz führen //Bildschirmauflösung setzen if (g_lpDDraw7->lpVtbl->SetDisplayMode(g_lpDDraw7,iWidth,iHeight,iBits) != DD_OK) return FALSE; //Beschreibung der Primären Zeichenfläche füllen memset(&g_ViewDesc, 0, sizeof(DDSURFACEDESC2)); g_ViewDesc.dwSize = sizeof(DDSURFACEDESC2); g_ViewDesc.dwFlags = DDSD_CAPS; g_ViewDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |//es soll eine primäre Zeichenfläche werden DDSCAPS_VIDEOMEMORY; //sie wird im Grafikkartenspeicher angelegt g_ViewDesc.ddsCaps.dwCaps2 = 0; if (g_lpDDraw7->lpVtbl->CreateSurface(g_lpDDRaw7,&g_ViewDesc, &g_lpDView, NULL)!= DD_OK) return FALSE; //Restliche Informationen der primären Zeichenfläche lesen if (g_lpDView->lpVtbl->GetSurfaceDesc(g_lpDView,&g_ViewDesc) != DD_OK)return FALSE; //Arbeitszeichenfläche erzeugen memset(&g_PaintDesc, 0, sizeof(DDSURFACEDESC2)); g_PaintDesc.dwSize = sizeof(DDSURFACEDESC2); g_PaintDesc.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; g_PaintDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN |//es soll eine normale Zeichenfläche werden DDSCAPS_SYSTEMMEMORY; //sie wird im Hauptspeicher erzeugt g_PaintDesc.ddsCaps.dwCaps2 = 0; g_PaintDesc.dwHeight = iHeight; //hoch g_PaintDesc.dwWidth = iWidth; //breit if (g_lpDDraw7->lpVtbl->CreateSurface(g_lpDDraw7,&g_PaintDesc, &g_lpDPaint, NULL)!=DD_OK) return FALSE; //restliche Beschreibung hohlen if (g_lpDPaint->lpVtbl->GetSurfaceDesc(g_lpDPaint,&g_PaintDesc) != DD_OK) return FALSE; //wenn bis hier alles geklappt hat, war die Initilisierung erfolgreich return TRUE; }
Anschliessend brauchen wir noch eine Funktion die DirectDraw wieder abbaut und den Bildschirm wieder freigibt:

void ExitDirectDraw(void) //DirectDraw in der richtigen Reihenfolge wieder abbauen { if (g_lpDPaint != NULL) g_lpDPaint->lpVtbl->Release(g_lpDPaint); if (g_lpDView != NULL) g_lpDView->lpVtbl->Release(g_lpDView); if (g_lpDDraw7 != NULL) g_lpDDraw7->lpVtbl->Release(g_lpDDraw7); }
Damit die hier gezeigten Funktionen und Variablen auch von anderen Programm-Codes genutzt werden können, schreiben wir noch eine Header-Datei.
InitDDraw.h wird dem neuen Projekt hinzugefügt.

/* * Header-Datei macht die Funktionen und Objekte für DirectDraw * im Hauptprogramm bekannt * */ #include <ddraw.h> extern LPDIRECTDRAW7 g_lpDDraw7; //Globales DirectDraw Object extern LPDIRECTDRAWSURFACE7 g_lpDView; //Primäre Zeichenfläche (Anzeige) extern DDSURFACEDESC2 g_ViewDesc; extern LPDIRECTDRAWSURFACE7 g_lpDPaint; //Arbeitszeichenfläche (zum hineinzeichnen) extern DDSURFACEDESC2 g_PaintDesc; extern BOOL InitDirectDraw(HWND hwnd, int iWidth, int iHeight, int iBits); extern void ExitDirectDraw(void);
Ein kleines Hauptprogramm zum testen des Vollbildmodus soll nix weiter machen als diesen Aufrufen und wieder Abbauen. Codiert wird das ganze in DDrawFS.c.

/* * DDrawFS.c * * Hauptprogramm zur Demonstration des DirectDraw - FullScreenMode * * */ #include <windows.h> #include "InitDDraw7.h" //Messagebehandlungsroutine LRESULT WINAPI WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASS wc; HWND hwnd; MSG msg; wc.lpszClassName = "DDrawFS"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; // siehe unten wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND + 1); wc.lpszMenuName = NULL; RegisterClass(&wc); hwnd = CreateWindow ("DDrawFS", "Vollbildmodus", WS_OVERLAPPED, 0, 0, 100, 100, //Grösse ist eigentlich egal NULL, NULL, hInstance, NULL); ShowWindow (hwnd, SW_SHOW); UpdateWindow(hwnd); if (InitDirectDraw(hwnd,800,600,16) == TRUE) { while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } } ExitDirectDraw(); return msg.wParam; } LRESULT WINAPI WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_DESTROY: PostQuitMessage(0); return TRUE; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return FALSE; }
Nach dem das Projekt erfolgreich compiliert und gelinkt wurde - Achtung: ddraw.lib und lccwin32-Nutzer die dxguid.lib nicht vergessen - sollte das Ergebnis so aussehen:

DirectDraw Fullscreen Fenster

Hmm - irgendwie ja eigenartig, oder ??? War da nicht die Rede von Vollbildmodus und Fensterlos ???
Sie werden sogar feststellen, dass Sie das Fenster, welches zu sehen ist auch ganz normal bewegen können und im hintergrund die anderen Fenster auftauchen die auf dem Desktop geöffnet sind. Lassen Sie sich nicht irritieren - das liegt in der Philosophie von Windows. Um zu verhindern, dass ein solches Verschieben möglich ist, müssen wir nur die Styles unseres Anwendungsfensters anpassen.
Z.Bsp als Popup:

BOOL APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { [..] hwnd = CreateWindow ("DDrawFS", "Vollbildmodus", WS_POPUP, 0, 0, 100, 100, //Grösse ist eigentlich egal NULL, NULL, hInstance, NULL); [..]
Anschliessend ist nur noch ein komplett weisser Bildschirm zu sehen der mit keinem Mausklick zu entfernen ist. Lediglich ein Druck auf ALT+F4 beendet wie gewohnt die Anwendung.

Vorab der aktuelle Download.

Im nächsten Schritt können wir unseren Tetris-Clone in diesem Vollbildmodus darstellen. Dazu sind nur kleinere Änderungen notwendig. Doch Achtung !! Wir müssen alle Aufrufe die die GDI benutzen, wie z.Bsp. MessageBox(...), auskommentieren. Wenn es sich lediglich um Informationen handelte ist dies nicht ganz so tragisch - doch Fehlermeldungen sind für den Anwender wichtig. Wie wir diese im Vollbildmodus darstellen zeigen wir ihnen später.
Nun ersteinmal zu den Änderungen am Tetris-Projekt:
Der erste Schritt besteht darin, dass wir dem Projekt die oben erstellten Dateien (InitDDraw7.c und InitDDraw7.h) hinzufügen. Als Nächstes tauchen wir die im Projekt enthaltenen Initialisierungsroutinen gegen die neuen aus:

/* ddterisFS.c als Kopie von ddtetris3.c * */ #include <windows.h> #include <stdio.h> //#define INITGUID // <- nur für lcc-win32 Compiler //#include "ddraw.h" Entfällt nun dafür: #include "InitDDraw7.h #define ID_TETRISTIMER 1001 //Geschwindigkeit mit der die Steine runterkommen //je kleiner die Zahl desto Scheller fallen sie!! #define STARTSPEED 250 //diese Message wird ans Anwendungsfenster gesendet, wenn das Spiel verloren ist #define WM_GAMEOVER WM_USER+101 //die Globalen Interfaces zur Arbeit mit DirectDraw kommen nun über //InitDDraw7.h /*LPDIRECTDRAW7 lpDDraw7; // wir greifen auf die Schnittstellen immer über Pointer zu LPDIRECTDRAWSURFACE7 lpDSurf7; DDSURFACEDESC2 DDSurfDesc2; // die Beschreibung unserer Zeichenfläche LPDIRECTDRAWSURFACE7 lpDPaint7; DDSURFACEDESC2 DDPaintDesc2;*/ //die Surfaces für Steine und Spielfeld benötigen wir weiterhin LPDIRECTDRAWSURFACE7 lpDStein7; DDSURFACEDESC2 DDSteinDesc2; LPDIRECTDRAWSURFACE7 lpDGame7; DDSURFACEDESC2 DDGameDesc2; // Der Clipper wird nicht mehr gebraucht. //LPDIRECTDRAWCLIPPER lpDClipper; [..] //Diese Funktion ist in InitDDraw7.c codiert //Sie wird umgenannt in InitApllication //BOOL InitDirectDraw(HWND hwnd); BOOL InitApplication(HWND hwnd); [..] //Die Funktion ruft InitDirectDraw auf BOOL InitApplication(HWND hwnd) { if }