überarbeitet
von: Denny R. Walter auf Basis eines älteren Kapitels (vom 29.10.2000) von Dr.
Erhard Henkes
Kapitel 9 -
"Bewegungsabläufe" Stand: 29.06.2006
Hallo, mein Name ist Denny
R. Walter.
Herr Dr. Henkes hat mich darum gebeten, dieses Kapitel zu
überarbeiten, weshalb ich es diesmal sein werde, der Ihnen die
Vorgehensweisen erklärt.
Sie werden in
diesem Kapitel hauptsächlich lernen bzw. wiederholen:
- wie man Bilder
auf dem Bildschirm ausgibt, wobei eine Farbe als Hintergrund gilt und
nicht mitkopiert wird
- wie man
Bewegungsabläufe ohne Flackern realisiert.
Bevor Sie dieses
Kapitel durchgehen, sollten Sie sich Kapitel 4, Unterpunkte 1 und 2
durchgelesen haben.
Erstellen Sie
nun bitte eine dialogfeldbasierte MFC-Anwendung namens "Game" und
deaktivieren Sie die Einbindung von ActiveX-Steuerelementen sowie die
Infobox. Entfernen Sie dann aus Ihrem Dialog die Buttons und den Text,
so dass Sie ein leeres Dialogfeld haben.
In unserem
Programm geht es darum, eine Figur auf einem Hintergrund darzustellen
und zu bewegen.
Bitte laden Sie sich dafür folgende Grafiken
herunter und speichern Sie diese als
Bitmap-Dateien im Format xxx.bmp (z.Z. liegen sie im GIF-Format vor.)
http://henkessoft.de/C++/MFC/MFC_Kap09/mann1.gif
http://henkessoft.de/C++/MFC/MFC_Kap09/mann2.gif
http://henkessoft.de/C++/MFC/MFC_Kap09/getroffen.gif
http://henkessoft.de/C++/MFC/MFC_Kap09/hintergrund.gif
Nachdem Sie die
Dateien heruntergeladen und mittels eines Grafikprogramms als Bitmap
gespeichert haben, fügen Sie die Bitmap-Dateien dem Projekt bitte
als Ressource hinzu. Dazu gehen Sie in der linken Registerkarte auf die
Ressourcenansicht, rechtsklicken auf "Game Ressourcen", wählen
"Importieren" und suchen die vier Dateien. Markieren Sie sie und
klicken Sie auf "Importieren". Wenn Sie sie nicht finden können,
prüfen Sie bitte, ob unter "Dateityp" "Alle Dateien (*.*)"
angegeben ist. Nun gehen Sie wieder in die Ressourcenansicht und machen
einen Rechtsklick auf jedes einzelne Bild, um dann auf "Eigenschaften"
zu klicken. Die Bilder müssen nun von IDB_BITMAP1 etc. umbenannt
werden in
IDB_MANN1
IDB_MANN2
IDB_GETROFFEN
IDB_HINTERGRUND
Wir erstellen in
der Klassendefinition von CGameDlg nun einige Membervariablen:
class
CGameDlg
{
...
protected:
CBitmap m_BackBuffer;
CBitmap m_Hintergrund;
CBitmap m_Mann [3];
UINT m_Index;
int m_X;
int m_Y;
...
}
Wir haben ein
Feld von CBitmap-Variablen mit dem Namen m_Mann. Dieses speichert die
Bewegungsphasen der Figur.
m_Hintergrund speichert das
Hintergrundbild.
m_Index ist der Feldindex, der
das aktuell anzuzeigende Bild der Figur festlegt.
m_X und m_Y sind die Position der Figur auf
dem Bildschirm.
m_BackBuffer ist ein Bild, in das
das fertige Aussehen des Spielfeldes zwischengespeichert wird, bevor
wir es ausgeben. Damit werden Flackereffekte verhindert. Man stelle
sich vor, folgendes würde bei einer Animation direkt auf das
Fenster kopiert werden: Hintergrund, Figur, Hintergrund, Figur,
Hintergrund, Figur. Dann gibt es immer einen winzig kleinen Moment, in
dem der Hintergrund zu sehen ist, die Figur aber noch nicht. Dadurch
entstehen Flackereffekte bei
der Figur. Deshalb wird es so gemacht, dass erst der Hintergrund und
dann die Figur in den Back-Buffer kopiert werden. Und erst dieser wird
direkt auf den Bildschirm kopiert. Somit wird pro Animationsphase nur
ein Bild auf den Bildschirm kopiert (der Back-Buffer) und dadurch gibt es kein Flackern. Außerdem hat
der Back-Buffer einen weiteren Vorteil: Man will ein Spiel
programmieren, bei dem man die Größe, sprich, den
Zoom-Faktor des Spielfelds, ändern können soll. Würde
man den Back-Buffer nicht benutzen, müßte man die Bewegung
und die Position von jedem einzelnen Bildobjekt von der aktuellen
Größe abhängig machen. Ein Beispiel: Bei normaler
Größe steht die Spielfigur an Position 10,34. Bei halber
Spielfeldgröße müßte sie aber an Position 5,17
stehen. Und wenn die Schrittweite normalerweise vier Pixel ist,
müßte sie bei halber Größe zwei und bei doppelter
Größe acht Pixel sein. Mit dem Back-Buffer muss man sich um
sowas nicht mehr kümmern: Sämtliche Bilder sind intern in
ihrer originalen Größe gespeichert. Auch der Back-Buffer
selbst hat die Größe des originalen Spielfeldes. Auf ihn
werden nun alle Spielelemente so kopiert, wie es bei einer
Größe von 100% gedacht ist. Und erst ganz am Ende wird der
Back-Buffer, der ja dann ein komplettes Bild der aktuellen Situation
gespeichert hat, entweder gestreckt oder gestaucht auf dem Fenster
ausgegeben. Dieses Phänomen können Sie in unserem Beispiel
nachher selbst begutachten, wenn Sie das Dialogfeld in der
Größe veränderbar machen: Obwohl wir uns in unserem
Spiel bezüglich Animationen etc. nicht um die aktuell angezeigte
Größe kümmern, wird das Spiel immer genauso groß
wie das Dialogfeld und die Objekte werden an der richtigen Position
sein. Das ist einer der Vorteile von Back-Buffering.
Doch nun genug geredet, machen wir weiter mit dem Programm.
Wir müssen
die Variablen initialisieren. Das erledigen wir in der Funktion
OnInitDialog:
BOOL
CGameDlg::OnInitDialog ()
{
...
CClientDC windowDC (this);
/*Dies ist ein Device
Context des Fensters.
Im
Moment wird er lediglich als Hilfsvariable gebraucht,
um die Bilder zum Fenster kompatibel zu machen.*/
BITMAP bm;
/*Diese Bitmapstruktur
speichert Daten eines Bildes.
Wir brauchen sie, um die Größe des
Hintergrundes zu ermitteln.*/
m_Hintergrund.LoadBitmap (IDB_HINTERGRUND);
//Wir laden das
Hintergrundbild in die Variable...
m_Hintergrund.GetBitmap (&bm);
//...und ermitteln unter
anderem die Größe.
m_BackBuffer.CreateCompatibleBitmap (&windowDC, bm.bmWidth,
bm.bmHeight);
/*Wir erstellen ein leeres
Bild für den Back-Buffer.
Da der Hintergrund das Spielfeld ausmacht, müssen
Hintergrund und
Back-Buffer gleich groß sein.*/
m_Mann [0].LoadBitmap (IDB_MANN1);
m_Mann [1].LoadBitmap (IDB_MANN2);
m_Mann [2].LoadBitmap (IDB_GETROFFEN);
//Laden der Bilder für die Figur.
m_Index=0;
/*Das erste Bild soll m_Mann
[0] sein,
darum setzen wir den Index auf 0.*/
m_X=110;
m_Y=185;
//Die Anfangsposition
für die Figur ist 110,185.
...
}
Für die
Ausgabe erstellen wir eine eigene Memberfunktion in der Klasse.
Wählen Sie also in der linken Registerkarte die Klassenansicht,
klicken Sie mit rechts auf CGameDlg und
wählen Sie "Member-Funktion hinzufügen". Geben Sie als
Funktionstyp void und als Name
der Funktion Ausgabe () an.
Alle weiteren Optionen sind für unser Projekt zunächst
unerheblich. Klicken Sie auf OK. Gehen Sie nun in die Funktion OnPaint und bereiten Sie diese so
vor, dass sie wie folgt aussieht:
void
CGameDlg::OnPaint ()
{
Ausgabe ();
CDialog::OnPaint ();
}
Damit gehen wir
sicher, dass die Ausgabe jedesmal wiederholt wird, sobald ein Teil des
Fensters neu gezeichnet werden muss.
Nun kümmern
wir uns um die Ausgabe. In der Ausgabe-Funktion muss folgender Code
stehen, den ich jeweils erkläre:
CClientDC
windowDC (this);
CDC
backBufferDC,
hintergrundDC,
mannDC,
maskDC,
orDC,
tempDC;
/*Ein Device Context (DC, die
entsprechende Klasse ist also CDC) ist eine Art Container für eine
Bitmap-Variable. Die Klasse CBitmap liefert keine eigenen Funktionen,
um das in ihr gespeicherte Bild zu bearbeiten und zu verändern.
Deshalb muss man einen Device Context anlegen, in den man die
Bitmap-Variable hineinwählt. Der DC stellt nun Funktionen zur
Verfügung, um das Bild in verschiedener Weise zu manipulieren.
Im Gegensatz zu CBitmap-Variablen
sollte man DCs niemals dauerhaft speichern, sondern immer nur lokal
innerhalb von Funktionen anlegen. Würde man es anders machen und
man hätte eine mittelgroße Anzahl an Sprites, könnte
das Programm geschwindigkeitstechnisch ganz schön in die Knie
gehen. Deshalb würde ich auch empfehlen, die Schritte, die
nötig sind, um ein Bild in den Back-Buffer zu kopieren, welche wir
hier in einer Funktion abarbeiten, in einem richtigen Programm in
mehrere Funktionsaufrufe zu unterteilen, denn sonst hat man bei 10
Bildern, die alle in der Funktion Ausgabe einen DC brauchen, nachher
das gleiche Problem. Für ein richtiges Programm sollte man also
eine Funktion im Stil von void KopiereBildNachBackBuffer (CBitmap
*bild, CBitmap *backBuffer) haben.
CClientDC ist ein besonderer DC.
Er ist nämlich automatisch mit dem im Parameter angegebenen
Fenster (this, stellt einen Zeiger auf das Objekt der Klasse CGameDlg
dar) verbunden. Sämtliche Zeichenoperationen wirken sich dabei
also direkt auf das Fenster aus. Durch ihn wird am Ende die eigentliche
Ausgabe realisiert. Außerdem benötigen wir ihn, um die
anderen DCs zum Fenster kompatibel zu machen.
Der Mask-, Or- und Temp-DC wird
gebraucht, um die Figur so auf den Back-Buffer zu kopieren, dass die
Hintergrundfarbe transparent wird. (Das wurde bereits in Kapitel 4
besprochen.)*/
CBitmap
maskBitmap,
orBitmap,
tempBitmap;
//Hier haben wir analog zu den
drei zusätzlichen DCs die Bitmap-Variablen.
CRect
rect;
BITMAP
bm;
/*rect und bm werden gebraucht, um
die Größe von Bitmaps (bm)
bzw. des Fensters (rect) zu
ermitteln.*/
/*Im folgenden werden die
Back-Buffer kompatibel gemacht
und mit ihren jeweiligen
Bitmaps verknüpft.*/
backBufferDC.CreateCompatibleDC
(&windowDC);
backBufferDC.SelectObject
(&m_BackBuffer);
hintergrundDC.CreateCompatibleDC
(&windowDC);
hintergrundDC.SelectObject
(&m_Hintergrund);
mannDC.CreateCompatibleDC
(&windowDC);
mannDC.SelectObject
(m_Mann [m_Index]);
m_Mann
[m_Index].GetBitmap (&bm);
/*Mask-, Or- und Temp-Bitmap
werden erst noch erstellt
und benötigen die
gleiche Größe wie die der Figur,
weshalb dessen Werte in die
bm-Variable kopiert wurden.*/
maskBitmap.CreateBitmap
(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
//maskBitmap ist monochrom,
deshalb hier kein kompatibles Bitmap.
maskDC.CreateCompatibleDC
(&windowDC);
maskDC.SelectObject
(&maskBitmap);
orBitmap.CreateCompatibleBitmap
(&windowDC, bm.bmWidth, bm.bmHeight);
orDC.CreateCompatibleDC
(&windowDC);
orDC.SelectObject
(&orBitmap);
tempBitmap.CreateCompatibleBitmap
(&windowDC, bm.bmWidth, bm.bmHeight);
tempDC.CreateCompatibleDC
(&windowDC);
tempDC.SelectObject
(&tempBitmap);
m_Hintergrund.GetBitmap
(&bm);
backBufferDC.BitBlt
(0, 0, bm.bmWidth, bm.bmHeight, &hintergrundDC, 0, 0, SRCCOPY);
/*Die obige Stelle ist die erste
wirklich Wesentliche in dieser Funktion. Es wird zuerst die
Größe des Hintergrunds ermittelt und dieser dann auf den
Back-Buffer kopiert. Das erfolgt mit der Funktion BitBlt. Die ersten
beiden Parameter sind hierbei die Position. Der dritte und vierte
Parameter stellt die Größe dar, in der der Hintergrund auf
dem Back-Buffer erscheinen soll. Wir belassen es bei der
Originalgröße. Der fünft Parameter ist der DC des zu
kopierenden Bildes. Danach kann man auswählen, ob man vom
Hintergrund nur einen bestimmten Teil haben will. Man gibt dazu hier
die Anfangsposition desselben an. Wir wollen das ganze Bild kopieren,
also setzen wir beide Werte auf 0. Der letzte Parameter gibt die Art
an, wie kopiert wird. SRCCOPY ist der Standard. Für weitere
Informationen sehen Sie bitte in Kapitel 4 nach.*/
/*Der folgende Teil mit dem Mask-,
Or- und Temp-DC ist ziemlich technisch und man muss ihn auch nicht im
einzelnen verstehen. (Ich verstehe auch nicht jeden Aspekt.) Man muss
nur wissen, dass man mit einem Bild immer so vorgehen muss, um es mit
transparenter Hintergrundfarbe auf ein anderes Bild (in unserem Fall
den Back-Buffer) zu kopieren.*/
m_Mann
[m_Index].GetBitmap (&bm);
mannDC.SetBkColor
(RGB (0x0, 0x0, 0x0));
/*Die Hintergrundfarbe wird auf
schwarz gesetzt.
Diese wird nachher nicht
mit kopiert, da sie dann als transparent gilt.*/
maskDC.BitBlt
(0, 0, bm.bmWidth, bm.bmHeight, &mannDC, 0, 0, SRCCOPY);
orDC.BitBlt
(0, 0, bm.bmWidth, bm.bmHeight, &mannDC, 0, 0, SRCCOPY);
orDC.BitBlt
(0, 0, bm.bmWidth, bm.bmHeight, &maskDC, 0, 0, 0x220326);
tempDC.BitBlt
(0, 0, bm.bmWidth, bm.bmHeight, &backBufferDC, m_X, m_Y, SRCCOPY);
tempDC.BitBlt
(0, 0, bm.bmWidth, bm.bmHeight, &maskDC, 0, 0, SRCAND);
tempDC.BitBlt
(0, 0, bm.bmWidth, bm.bmHeight, &orDC, 0, 0, SRCPAINT);
backBufferDC.BitBlt
(m_X, m_Y, bm.bmWidth, bm.bmHeight, &tempDC, 0, 0, SRCCOPY);
/*Hier wird nun der letzte DC, der
nach einem komplizierten Prozeß bearbeitet wurde,
auf den Back-Buffer
kopiert. Es handelt sich um das Bild der Figur ohne Hintergrundfarbe.*/
/*Und nun kommt der finale Teil:
Das Bild des Back-Buffers wird auf das Fenster gezeichnet. Und zwar
relativ zur Fenstergröße.*/
m_BackBuffer.GetBitmap
(&bm);
//Wir ermitteln die
Größe des Back-Buffers.
GetClientRect
(&rect);
//Wir ermitteln die
Größe des Fensters.
windowDC.SetStretchBltMode
(COLORONCOLOR);
/*Wir geben den Modus für
Streckungen und Stauchungen an. Die beiden Wesentlichen sind hier
COLORONCOLOR und HALFTONE. (BLACKONWHITE und WHITEONBLACK werden
für Monochrombitmaps verwendet.) COLORONCOLOR ist die übliche
Methode: Bei einer Vergrößerung werden Pixelreihen
dubliziert, bei einer Verkleinerung werden einige herausgenommen.
HALFTONE verursacht dagegen einen Weichzeichnungseffekt, der meiner
Meinung nach bei Spielen nicht sonderlich gut aussieht. Ich würde
daher immer zu COLORONCOLOR tendieren. Unter Windows 98 gibt es
übrigens keinen Unterschied zwischen beiden Versionen. Wenn Sie
also unter Windows 98 programmieren, sollten Sie auf HALFTONE ganz
verzichten, da das Programm sonst auf Windows 2000 oder XP völlig
anders aussehen könnte.*/
windowDC.StretchBlt
(0, 0,
rect.Width (), rect.Height (), &backBufferDC, 0, 0, bm.bmWidth,
bm.bmHeight, SRCCOPY);
/*Und hier ist sie nun: Die
Funktion, die unser Bild ausgibt. Sie ist fast so, wie BitBlt, mit dem
Unterschied, dass man das Bild strecken oder stauchen kann. Die letzten
vier Parameter vor SRCCOPY stellen den Teil des Originalbildes dar, der
kopiert werden soll (in unserem Fall das ganze Bild) und der dritte und
vierte Parameter gibt an, wie groß dieses ausgewählte Bild
wird (in unserem Fall so groß, wie das Fenster.)*/
Sollte die Figur
nun noch immer einen schwarzen Hintergrund haben, dann liegt das daran,
dass das Bitmap-Bild, dadurch, dass es aus einer Gif-Datei erstellt
wurde, als Hintergrundfarbe nicht das echte Schwarz, sondern eine Farbe
hat, die ein ganz kleines Bisschen abweicht. Bearbeiten Sie in diesem
Fall die
Bilddateien, indem Sie den Hintergrund mit dem Farbfüller erneut
mit schwarz (RGB-Wert: 0,0,0)
ausfüllen.
Nun werden wir
etwas Bewegung ins Spiel bringen. Dazu brauchen wir einen Timer und
eine Funktion, die eine Aktion ausführt, wenn die linke Maustaste
gedrückt wurde. Gehen Sie also im Menü auf "Ansicht",
"Klassen-Assistent". Wählen Sie dort unter "Klassenname" die
Klasse CGameDlg. Suchen Sie unter "Nachrichten" die Zeile "WM_TIMER". Markieren Sie sie und
klicken Sie auf "Funktion hinzufügen". Erledigen Sie das gleiche
mit WM_LBUTTONDOWN. Klicken Sie auf OK. Sie haben jetzt in
Ihrer Dialogfeldklasse zwei Funktionen mit Namen OnTimer und OnLButtonDown.
Fügen Sie der Klasse noch eine Member-Variable UINT m_Timer hinzu und
initialisieren Sie diese in OnInitDialog
mit 0. Die
Timer-Funktion soll jetzt so aussehen:
void
CGameDlg::OnTimer (UINT nIDEvent)
{
m_X+=10;
/*Jedesmal,
wenn der Timer aufgerufen wird, wird
die Figur um 10 Pixel nach rechts bewegt.*/
switch (m_Index)
{
case 0:
case 2:
m_Index=1;
break;
case 1:
m_Index=0;
break;
}
//Die
Animationsphase ändert sich je nach der aktuellen Animationsphase.
Ausgabe ();
//Nach
jedem Timeraufruf erfolgt die Ausgabe.
if (m_X>=600)
{
KillTimer (m_Timer);
m_Timer=0;
}
/*Wenn
die Figur an einer bestimmten Stelle angelangt ist, wird
der Timer beendet und die Variable, die den Timerstatus anzeigt,
auf 0 zurückgesetzt.*/
CDialog::OnTimer (nIDEvent);
}
Nun muss der
Timer nur noch aufgerufen werden. Eine Bewegung soll beginnen und
frühzeitig gestoppt werden, wenn die linke Maustaste geklickt
wird. Schreiben Sie in die Maustastenfunktion also folgendes:
void
CGameDlg::OnLButtonDown (UINT nFlags, CPoint point)
{
//Wenn
der Timer 0 ist...
if (m_Timer==0)
{
//...und
die Figur an der festgelegten Anfangsposition steht,...
if (m_X==110)
{
m_Timer=SetTimer (1, 100,
NULL);
/*...wird
der oben beschriebene Timer mit einem Intervall von
100 Millisekunden gesetzt.
Die OnTimer-Funktion wird von
nun an also alle 100ms aufgerufen.
Da wir nur einen
Timer haben, spielt die Timer-ID (erster
Parameter) keine große Rolle.*/
}
else
{
m_X=110;
m_Index=0;
Ausgabe ();
/*Steht
die Figur nicht am Anfang und bewegt sich auch nicht, wird
sie wieder an den Anfang gesetzt und das Bild ausgegeben.*/
}
}
else
{
KillTimer (m_Timer);
m_Timer=0;
/*Bei laufendem Timer bewirkt ein Klick,dass der Timer gestoppt wird
und die Figur stehenbleibt.*/
}
CDialog::OnLButtonDown (nFlags, point);
}
Klicke einmal
und die Figur läuft. Erreicht sie ihr Haus, bleibt sie stehen.
Klicke, während sie läuft, und sie bleibt stehen. Klicke,
während die Figur irgendwo anders als am Anfang steht, und sie
wird an den Anfang zurück teleportiert. So sollte das Programm
funktionieren.
Nun zum Finale:
Wir werden Funktionen einbauen, mit denen wir auf die Figur
schießen. Wir werden dazu den Cursor verändern und bei jedem
Schuss einen kurzen Sound ausgeben.
Beginnen wir mit
dem Cursor. Bitte laden Sie sich folgende Datei herunter und binden Sie
sie in Ihr Projekt als Ressource ein:
Cursor (als zip-Datei)
Nennen Sie sie IDC_ZIELCURSOR.
In die
OnInitDialog-Funktion müssen nun folgende Zeilen:
HCURSOR
cursor=AfxGetApp()->LoadCursor (IDC_ZIELCURSOR);
SetCursor
(cursor);
SetClassLong
(GetSafeHwnd (), GCL_HCURSOR, reinterpret_cast<LONG> (cursor));
Damit laden wir
den Cursor dauerhaft. (Ich weiß nicht, ob SetClassLong die beste
Möglichkeit ist, aber was anderes ist mir nicht eingefallen.
Verbesserungsvorschläge sind daher willkommen.) Nun sehen Sie,
wenn Sie das Programm starten, ein Fadenkreuz als Mauscursor.
Um zu
schießen, benutzen wir die rechte Maustaste. Fügen Sie die
Funktion OnRButtonDown genauso hinzu, wie Sie es vorhin mit
OnLButtonDown getan haben. Zur Funktion:
void
CGameDlg::OnRButtonDown (UINT nFlags, CPoint point)
{
/*Da
wir direkt auf den Bildschirm schießen, unser
Hintergrund aber intern nicht die gleiche Größe hat und
sich die tatsächliche Position des Schusses
demnach von der Position
auf dem Hintergrund unterscheidet, müssen wir die Position, auf
der der Schuß im Hintergrundbild landen soll, ausnahmsweise
berechnen.*/
CRect rect;
BITMAP bm;
//Wir
ermitteln hierzu erst die Größe von Hintergrund und Fenster:
GetClientRect (&rect);
m_Hintergrund.GetBitmap (&bm);
/*Die
Position auf dem Hintergrund ergibt sich nun aus einer einfachen Verhältnisrechnung
anhand von Position auf dem Fenster, Größe des
Fensters und Größe des
Hintergrundbildes. Der
Parameter point stellt übrigens die Mausposition auf dem Fenster dar
und wird für uns gleich als
Positionsvariable für das Bild genommen:*/
point.x=point.x*bm.bmWidth/rect.Width ();
point.y=point.y*bm.bmHeight/rect.Height ();
m_Mann [m_Index].GetBitmap (&bm);
//Wir
ermitteln die Größe der Figur.
/*Wenn die Schussposition im
Bereich der Figur liegt, wir die Figur also getroffen
haben, ...*/
if (point.x>=m_X && point.x<=m_X+bm.bmWidth-1
&& point.y>=m_Y && point.y<=m_Y+bm.bmHeight-1 )
{
KillTimer (m_Timer);
m_Timer=0;
m_Index=2;
//...
hört sie auf zu laufen und ist blutüberströmt (Bildindex
2).
}
else
{
//Ansonsten
wird ein Loch dauerhaft in den Hintergrund gezeichnet.
CClientDC windowDC (this);
CDC dcHintergrund;
dcHintergrund.CreateCompatibleDC
(&windowDC);
dcHintergrund.SelectObject
(&m_Hintergrund);
//Dazu
erstellen wir einen Hintergrund-DC und zeichnen Linien hinein.
dcHintergrund.MoveTo (point);
dcHintergrund.LineTo (point.x+1, point.y-1);
dcHintergrund.LineTo (point.x+1, point.y+1);
dcHintergrund.LineTo (point.x-1, point.y+1);
dcHintergrund.LineTo (point.x-1, point.y-1);
dcHintergrund.LineTo (point.x+1, point.y-1);
dcHintergrund.MoveTo (point);
dcHintergrund.LineTo (point.x+4, point.y-4);
dcHintergrund.MoveTo (point);
dcHintergrund.LineTo (point.x+5, point.y+3);
dcHintergrund.MoveTo (point);
dcHintergrund.LineTo (point.x-2, point.y+5);
dcHintergrund.MoveTo (point);
dcHintergrund.LineTo (point.x-4, point.y+3);
dcHintergrund.MoveTo (point);
dcHintergrund.LineTo (point.x-5, point.y-4);
dcHintergrund.MoveTo (point);
dcHintergrund.LineTo (point.x-3, point.y-5);
dcHintergrund.MoveTo (point);
dcHintergrund.LineTo (point.x+2, point.y-3);
}
Ausgabe ();
//Da
sich das Bild geändert hat, erfolgt hier noch die Ausgabe.
CDialog::OnRButtonDown (nFlags, point);
}
Ganz zum
Schluß unterlegen wir das Spiel noch mit Sounds. Laden Sie sich
bitte folgende Soundeffekte herunter und binden Sie sie als Ressourcen
ins Projekt ein:
Sounddateien (als zip-Datei)
Benennen Sie die Ressourcen in Ihrem Projekt bitte in IDR_SCHREI und IDR_TREFFER um.
Um die
Soundfunktion benutzen zu können, inkludieren wir in der Datei
"GameDlg.cpp" unter unseren anderen Header-Dateien die Datei mmsystem.h. Außerdem
benötigen wir dafür noch die Lib-Datei winmm.lib. Der Code sieht also
folgendermaßen aus:
#include
<mmsystem.h>
#pragma
comment (lib, "winmm.lib")
Nun
schreiben Sie als letzte Anweisungen in der if-Bedingung:
sndPlaySound
(MAKEINTRESOURCE (IDR_SCHREI), SND_RESOURCE|SND_ASYNC);
bzw.
sndPlaySound
(MAKEINTRESOURCE (IDR_TREFFER), SND_RESOURCE|SND_ASYNC);
Da der erste
Parameter einen String erwartet, müssen wir das Makro MAKEINTRESOURCE verwenden. Die
Angaben im zweiten Parameter zeigen an, dass es sich um eine Ressource
handelt und das der Sound parallel zum Programmablauf (asynchron)
abgespielt wird. Das Programm wird für diesen Moment also nicht
komplett gestoppt.
Das war's. Sie
haben nun ein kleines Demo-Spiel und mit dem, was Sie hier gelernt
haben, dürften Sie eigentlich in der Lage sein, weitere Spiele zu
entwickeln.