IT-Academy Logo
Sign Up Login Help
Home - Programmieren - Visual Basic - DirectDraw Tutorial



DirectDraw Tutorial

Allgemeine Funktionsweise von DirectDraw , Direct Draw initialisieren, Grafiken laden, Grafiken Zeichnen und Textausgabe in VB.


Autor: Tobias Surmann (incsoft)
Datum: 27-03-2003, 21:52:00
Referenzen: http://www.vbDirectX.de
Schwierigkeit: Fortgeschrittene
Ansichten: 11069x
Rating: 10 (1x bewertet)

Hinweis:

Für den hier dargestellte Inhalt ist nicht der Betreiber der Plattform, sondern der jeweilige Autor verantwortlich.
Falls Sie Missbrauch vermuten, bitten wir Sie, uns unter missbrauch@it-academy.cc zu kontaktieren.

[Druckansicht] [Als E-Mail senden] [Kommentar verfassen]



Abschnitt 1: Allgemeine Funktionsweise von DirectDraw

Wie genau funktioniert die Darstellung von Grafik, Animation und Schrift mit DirectDraw? Dies soll mit diesem Abschnitt erklärt werden. Hier wird noch kein Quelltext entwickelt, da dies nur ein Überblick über die generelle Funktionsweise von DirectDraw sein soll. Ein Grundprogramm werden wir im Abschnitt DirectDraw Grundgerüst entwickeln.

Zuerst müssen wir wissen, was eine Surface ist, da sie elementarer Bestandteil von DirectDraw ist:

Eine Surface ist ein Objekt in dem Grafikdaten gespeichert werden. Gleichzeitig ist sie auch ein Speicherbereich im RAM der Grafikkarte. So werden in einem Computerspiel z.B. einzelne Objekte in je einer Surface gespeichert. Durch Aneinanderreihung kann man dann z.B. den Effekt einer Landschaft erreichen:



Abb: In diesen Surfaces werden die Grafikdaten gespeichert und entsprechend auf dem Bildschirm angezeigt. Solche kleine Bildchen werden auch Sprites genannt.

Surfaces können auch miteinander verbunden werden. So nennt man zwei miteinander verbundene Surfaces, die beide abwechselnd auf den Bildschirm gezeichnet werden "umschaltbare Kette" (flipping chain). Die eine Surface ist dabei die primäre Surface, in der sich der aktuelle Bildschirminhalt befindet. Die andere Surface ist die sogenannte Backbuffer Surface. Sie ist für den User unsichtbar. Auf diese werden dann die Sprites (kleine Grafiken) gezeichnet und das nächste Bild zusammengestellt. Die Sprites befinden sich in sogenannten Offscreensurfaces und sind ebenfalls für den Spieler nicht sichtbar. Nach dem Zeichnen auf den Backbuffer findet ein Austausch (swap) der primären und der Backbuffer Surface statt. Dabei wird die Backbuffer Surface zur primären Surface und das eben erstellte Bild wird auf dem Bildschirm angezeigt. Die primäre Surface ist jetzt der Backbuffer. Nun kann der Backbuffer gelöscht werden und ein neues Bild zusammengestellt werden. Diese Technik nennt sich DoubleBuffer. Die Vorteile sind ein schneller und vor allem flimmerfreier Bildaufbau.

Eine seltener zum Einsatz kommende Technik nennt sich TripleBuffer. Dabei werden zwei Backbuffer und eine primäre Surface eingesetzt. Während der Inhalt des ersten Backbuffers auf die primäre Surface kopiert wird, kann man auf dem zweiten Backbuffer ein neues Bild erstellen. Da diese Technik meistens nicht zu einer Geschwindigkeitsteigerung führt, ist ihre Verwendung im Regelfall nicht notwendig.

Die folgende Skizze stellt die Funktionsweise des DoubleBuffer-Verfahrens anschaulich dar:



Abschnitt 2: DirectDraw Grundgerüst

Um Spiele zu programmieren brauchen wir ein Grundgerüst. DirectDraw muss initialisiert, Surfaces geladen und Grafiken gezeichnet werden. Hinzu kommt die Logik des Spiels und das Abfangen von Tastatureingaben.

Falls noch nicht geschehen, führen wir jetzt die Anweisungen aus dem Abschnitt Grundlagen: DirectX in die Entwicklungsumgebung einfügen (Link) aus. Das neue Projekt speichern wir unter vbdxGame.

Zuerst fügen wir ein neues Modul zu unserem Projekt hinzu. Wir nennen es MainMod. Nun erstellen wir die Einsprungsprozedur Main:

Public Sub Main()

End Sub

Wir müssen Visual Basic mitteilen, dass dies die Prozedur ist, an der das Programm beginnt. Dazu wählen wir im Menü Projekt den Menüpunkt Eigenschaften von vbdxGame... aus. Im Dialog Projekteigenschaften stellen wir nun das Startobjekt auf Sub Main.

Nun müssen wir die Prozedur Main mit unserer Grundstruktur des Spiels füllen.

Als erstes muss DirectDraw intialisiert werden. Dazu fügen wir den Aufruf der Funktion InitDirectDraw ein. Diese wird im nächsten Abschnitt entwickelt. Zunächst kennen wir nur die Parameter und den Rückgabetypen der Funktion. Die Funktion gibt bei erfolgreicher Ausführung True zurück, ansonsten False. Der Rückgabewert wird in der Prozedur Main überprüft und, falls die Ausführung nicht erfolgreich war, das Spiel mit einer Fehlermeldung beendet. Bei der Initialisierung werden außerdem die zu setzende Bildschirmauflösung, Bits per Pixel und der hWnd des Formulars übergeben:

Public Sub Main()

If InitDirectDraw(800, 600, 16, GameForm.hWnd) = False Then

	MsgBox "DirectDraw Fehler.", , "FEHLER"
	End

End If
	
End Sub
Mit diesem Programmcode wird GameForm im Vollbildmodus bei einer Auflösung von 800x600 dpi und 16 Bit Farbtiefe angezeigt.

Als nächstes müssen die Surfaces geladen werden. Da diese für jedes Programm anders sind, müssen sie in einer eigenen Funktion gekapselt werden. In der Main-Prozedur setzen wir trotzdem einen Call darauf um die Funktion für jedes Spiel anders ausfüllen zu können. Das Grundgerüst kann somit für jedes Spiel verwendet werden.

Public Sub Main()

If InitDirectDraw(800, 600, 16, GameForm.hWnd) = False Then

	MsgBox "DirectDraw Fehler.", , "FEHLER"
	End

End If
	
Call InitSurfaces

End Sub
Nun folgt der GameLoop. Der GameLoop ist eine Schleife, die solange nicht aufhört bis das Spiel beendet wird. Die Anweisungen zur Grafikausgabe und die Spiellogik befinden sich im Schleifenrumpf. Bei jedem Durchlauf wird überprüft ob eine vorher auf False gesetzte Prüfvariable (Flag), die angibt ob die Schleife verlassen werden soll, True wird und beendet die Schleife dann.

Für jedes Menü (wenn Menüs im Vollbildmodus angezeigt werden) muss eine solche Schleife gesetzt werden. Für das Spiel selbst muss natürlich auch eine solche Schleife eingefügt werden.

Public Sub Main()

If InitDirectDraw(800, 600, 16, GameForm.hWnd) = False Then

	MsgBox "DirectDraw Fehler.", , "FEHLER"
	End

End If
	
Call InitSurfaces

Dim mnuFlag As Boolean
mnuFlag = False

Do While mnuFlag = False

	Call DrawMenu 'Menü zeichnen
	Call DoMenu   'Menü Logik
	DoEvents

Loop

Dim gameFlag As Boolean
gameFlag = False

Do While gameFlag = False

	Call HandleKeys 'Tastaturabfragen
	Call DrawSurfaces 'Spielgrafik zeichnen
	Call FlipSurfaces  'Austausch der 
	primären und Backbuffer Surface
	Call ClearBackBuffer 'Backbuffer löschen
	DoEvents

Loop


Call ReleaseSurfaces

End

End Sub
Die nicht vorgestellten Funktionen DrawMenu, DoMenu, HandleKeys, DrawSurfaces, FlipSurfaces und ClearBackBuffer werden im nächsten Abschnitt entwickelt. Der jeweilige Verwendungszweck steht als Kommentar hinter dem Aufruf.

Wenn das Programm vollständig durchlaufen ist, z.B. wenn die Funktion HandleKeys feststellt dass die Escape-Taste gedrückt wurde, setzt sie das Flag gameFlag auf True damit die Schleife beendet wird. Zum Schluss werden alle Surfaces freigegeben. Dies geschieht mit der ReleaseSurfaces Prozedur.

Das DoEvents am Ende jeder Schleife ermöglicht es dem Fenster (GameForm) sich selbst zu aktualisieren.

Ihr könnt das Grundgerüst für eure eigenen Spiele verwenden, müsst es aber vielleicht an einigen Stellen anpassen.

Abschnitt 3: DirectDraw initialisieren

Im letzten Abschnitt haben wir ein Grundgerüst für ein Computerspiel erstellt. In diesem haben wir einige Funktionen benutzt, die wir noch nicht entwickelt haben. Das werden wir jetzt in diesem Abschnitt nachholen.

Zuerst fügen wir ein neues Modul in unser Projekt ein, welches die DirectDraw Funktionalität kapselt. Das Modul nennen wir DDrawMod.

Nun erstellen wir den Funktionskopf der Funktion InitDirectDraw. Diese Funktion sorgt dafür, dass in den Vollbildmodus geschaltet wird. Die Funktion hat die Parameter ScreenWidth As Integer, ScreenHeight As Integer und Bpp As Integer. Mit diesen kann später festgelegt werden, in welche Auflösung geschaltet werden soll. Jetzt muss die Funktion nur noch wissen, welche Form in den gewünschten Modus versetzt werden soll. Dazu muss der hWnd der Form angegeben werden. Der Rückgabewert vom Typ Boolean gibt an ob die Funktion erfolgreich ausgeführt wurde, oder ob Fehler aufgetreten sind.

Public Function InitDirectDraw(ScreenWidth As Integer, ScreenHeight As Integer, Bpp As Integer, hWnd As Long) As Boolean

End Function

Um DirectX initialisieren zu können benötigen wir ein Objekt, das in allen Prozeduren und Funktionen des Projekts sichtbar ist. Deshalb schreiben wir in den Deklarationsbereich des Moduls:

Public DX As New DirectX7

Private DD As DirectDraw7

Private Primary As DirectDrawSurface7
Private BackBuffer As DirectDrawSurface7
Private ddsdPrimary As DDSURFACEDESC2
Private ddsdBackBuffer As DDSURFACEDESC2
Das DirectDraw Objekt muss nur im DirectDraw-Modul sichtbar sein, deshalb deklarieren wir es als Private. Außerdem deklarieren wir die Surfaces und die Surfacebeschreibungen für die primäre Surface und den BackBuffer.

Wenden wir uns wieder der Funktion InitDirectDraw zu. Mit On Error Goto ErrOut fügen wir die Fehlerbehandlung ein. Wenn ein Fehler auftritt springt der Computer zur Markierung ErrOut: die den Wert False zurückgibt. Der Fehlercode muss für eine flexiblere Programmierung auf einer höheren Ebene ausgewertet werden.

Public Function InitDirectDraw(ScreenWidth As Integer, ScreenHeight As Integer, Bpp As Integer, hWnd As Long) As Boolean

On Error Goto ErrOut

InitDirectDraw = True
Exit Function

ErrOut:

InitDirectDraw = False

End Function

Zwischen On Error Goto ErrOut und InitDirectDraw = True können wir nun den eigentlichen Sourcecode positionieren.

Public Function InitDirectDraw(ScreenWidth As Integer, ScreenHeight As Integer, Bpp As Integer, hWnd As Long) As Boolean

On Error Goto ErrOut

Set DD = DX.DirectDrawCreate("")

DD.SetCooperativeLevel hWnd, DDSCL_FULLSCREEN Or DDSCL_EXCLUSIVE Or DDSCL_ALLOWREBOOT

DD.SetDisplayMode ScreenWidth, ScreenHeight, Bpp,0, DDSDM_DEFAULT

ddsdPrimary.lFlags = DDSD_CAPS Or DDSD_BACKBUFFERCOUNT

ddsdPrimary.ddsCaps.lCaps=DDSCAPS_PRIMARYSURFACE Or _ DDSCAPS_FLIP Or DDSCAPS_COMPLEX

ddsdPrimary.lBackBufferCount = 1

Set Primary = DD.CreateSurface(ddsdPrimary)

Dim caps As DDSCAPS2
caps.lCaps = DDSCAPS_BACKBUFFER

Set BackBuffer = Primary.GetAttachedSurface(caps)

BackBuffer.GetSurfaceDesc ddsdBackBuffer

InitDirectDraw = True
Exit Function

ErrOut:

InitDirectDraw = False

End Function

Zuerst initialisieren wir das DirectDraw-Objekt mit der DirectDrawCreate-Funktion des DirectX-Objekts. Wir übergeben in der Funktion einen leeren String, da wir die primäre Grafikkarte ansprechen möchten. Falls mehrere Grafikkarten installiert sind kann man der Funktion die GUID der gewünschten Grafikkarte als String übergeben.

Danach teilen wir DirectDraw mit, dass es unsere Form GameForm in den Vollbildmodus versetzen soll. Dies geschieht mit der SetCooperativeLevel-Funktion des DirectDraw-Objekts. DDSCL_EXCLUSIVE gibt dabei an, dass nur unser Programm Zugriffsrechte auf die Grafikkarte und dessen Speicher hat. Somit erreichen wir eine größere Ausführungsgeschwindigkeit. Mit DDSCL_ALLOWREBOOT erlauben wir dem Spieler STRG+ALT+ENTF zu drücken um den Computer neuzustarten, wenn unser Spiel einmal abstürzen sollte (was wir natürlich nicht hoffen).

Die Methode SetDisplayMode setzt die Auflösung und die Farbtiefe. Wir übernehmen die Parameter einfach aus dem Aufruf unserer eigenen Funktion: ScreenWidth, ScreenHeight und Bpp. Die letzten beiden Parameter brauchen eigentlich nicht berücksichtigt werden. Der erste gibt lediglich die Wiederherstellungsgeschwindigkeit des Monitors (Refresh Rate) an. Wenn der Parameter 0 ist wird ein Standardwert verwendet. Der letzte Parameter hat bisher nur einen gültigen Wert, nämlich DDSDM_DEFAULT.

Mit ddsdPrimary.lFlags = DDSD_CAPS Or DDSD_BACKBUFFERCOUNT teilen wir DirectDraw mit, dass wir diese Surface-Eigenschaften der primären Surface bearbeiten möchten, was wir ja in den nächsten beiden Zeilen tun. Da DirectDraw noch nicht weiß, dass die zu erstellende Surface unsere primäre Surface sein soll, die für den Spieler sichtbar ist, setzen wir das Flag DDSCAPS_PRIMARYSURFACE. Die Surface soll außerdem umschaltbar (DDSCAPS_FLIP) und komplex (DDSCAPS_COMPLEX) sein, d.h. mit einem oder mehreren Backbuffern verbunden werden.

Wir wollen nur einen Backbuffer erzeugen, daher setzen wir ddsdPrimary.lBackBufferCount = 1.

Mit der CreateSurface-Methode wird die primäre Surface nach den vorher beschriebenen Angaben erstellt.

Jetzt muss nur noch der Backbuffer mit der primären Surface verbunden werden. Deshalb setzen wir die Eigenschaft der Backbuffer-Surface einfach auf DDSCAPS_BACKBUFFER und verbinden diese über die GetAttachedSurface-Methode mit der primären Surface.

Zuletzt schreiben wir die Eigenschaften des Backbuffers in die private Beschreibungsvariable ddsdBackBuffer. Dies erledigt die GetSurfaceDesc-Funktion, die die Eigenschaften einer beliebigen Surface in die übergebene Beschreibungsvariable schreibt.

Abschnitt 4: Grafiken laden

Im letzten Abschnitt haben wir die Funktion InitDirectDraw geschrieben, die DirectDraw initialisiert. Leider kann man jetzt immer noch keine Grafiken auf dem Bildschirm sehen. Bevor Bilder auf den Bildschirm gezeichnet werden können, müssen sie geladen werden. In diesem Abschnitt werden wir eine Funktion schreiben, die diese Aufgabe für uns übernimmt, so dass wir sie nachher in unserem Spiel aufrufen können und nur die Bitmap und allgemeine Grafikeigenschaften angeben brauchen.

Public Function LoadSurface(DD_Surface As DirectDrawSurface7, Filename As String, Rows As Integer, Cols As Integer, SurfaceWidth As Integer, SurfaceHeight As Integer, ColorKey As Integer) As Boolean

End Function


Beim Aufruf der Funktion übergeben wir eine noch leere Surface (DD_Surface), die ja von der Funktion mit unserer Grafik gefüllt werden soll. Filename gibt den Dateinamen der Bitmap an, aus der die Grafik entnommen werden soll.

Da wir von vorneherein daran denken, dass wir in unserem Spiel vielleicht später auch einmal Animationen benötigen, gibt es die Funktions-Parameter Rows und Cols, die später angeben ab welcher Stelle die Grafik aus der Bitmap in die Surface kopiert werden soll. Dabei geben SurfaceWidth und SurfaceHeight nicht die Abmessungen des Ausschnitts an, sondern die der Gesamtgrafik. Die Abmessungen des Ausschnitts werden von uns berechnet. Das klingt jetzt ziemlich kompliziert, aber das ist es gar nicht, wie man an der folgenden Grafik sieht:



So kann man dann später mit einer Schleife nebeneinander liegende Bilder in die entsprechenden Surfaces einlesen. Diese interessante Technik werde ich aber erst in einem späteren Abschnitt genauer erklären.

Auch bei dieser Funktion kümmern wir uns erst mal um die Fehlerbehandlung. Mit On Error Goto ErrOut legen wir fest, dass wenn ein Fehler auftritt, der Computer zur Markierung mit dem Namen ErrOut springen soll. Damit diese nicht immer aufgerufen wird, müssen wir davor ein Exit Function einfügen. Vorher teilen wir dem Computer explizit mit, dass die Funktion erfolgreich durchlaufen wurde:

Public Function LoadSurface(DD_Surface As DirectDrawSurface7, Filename As String, Rows As Integer, Cols As Integer, SurfaceWidth As Integer, SurfaceHeight As Integer, ColorKey As Integer) As Boolean

On Error Goto ErrOut

LoadSurface = True
Exit Function

ErrOut:
InitDirectDraw = False

End Function


Die Fehlerauswertung, wie z. B. das Anzeigen einer MsgBox mit entsprechender Fehlermeldung sollte auf einer höheren Ebene geschehen, also z. B. nach dem Aufruf der Funktion LoadSurface, damit man individuell reagieren kann und das Ereignis nicht schon fest im Code vorgegeben ist, so dass man nicht nachher mit viel Mühe alles wieder umschreiben muss.

Der eigentliche Sourcecode wird nun in das bereits vorhandene Grundgerüst der Funktion eingefügt:

Dim Ckey As DDCOLORKEY
Dim ddsdSprite As DDSURFACEDESC2
Im oberen Abschnitt deklarieren wir zuerst die benötigten Variablen. Ckey ist dabei die ColorKey Variable in der der Wert, der die transparenten Teile unserer Grafik beschreiben soll, gespeichert wird. ddsdSprite beschreibt die zu erstellende Surface. Alle gewünschten Eigenschaften der Surface werden in dieser Variablen festgelegt. Aber das kennen wir ja schon von der Erstellung der primären Surface.

Mit

ddsdSprite.lFlags = DDSD_CAPS Or DDSD_WIDTH OR DDSD_HEIGHT

teilen wir dem Computer mit, dass wir nachfolgend die weiteren Eigenschaften Capabilities (dt. Fähigkeiten) (DDSD_CAPS), Breite (DDSD_WIDTH) und Höhe (DDSD_HEIGHT) der Surface festlegen möchten.

Also tun wir dies in den folgenden Zeilen:

ddsdSprite.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN
ddsdSprite.lWidth = SurfaceWidth
ddsdSprite.lHeight = SurfaceHeight

Die letzten beiden Zeilen sind schnell erklärt; hier werden einfach die beiden Variablen, die die Größe der Grafik bestimmen, aus dem Funktionsaufruf von LoadSurface genommen und den entsprechenden Surface-Eigenschaftsvariablen zugewiesen.

Um die erste Zeile zu erklären muss man schon etwas weiter ausholen. DDSCAPS_OFFSCREENPLAIN bedeutet, dass eine Offscreen Surface erstellt werden soll. Aber was genau ist eine Offscreen Surface und welche Bedeutung hat sie für ein DirectDraw-Programm?

In einer Offscreen Surface werden die Daten kleiner Grafiken, den sogenannten Sprites, gespeichert. Der Trick an der Sache ist, dass die Offscreen Surface, wie der Name ja schon sagt, nicht auf dem Bildschirm zu sehen ist, sondern erst explizit dort hingezeichnet werden muss. Damit wird sich dann das nächste Abschnitt beschäftigen. So erreicht DirectDraw eine hohe Geschwindigkeit, da die Berechnungen der Daten im Speicher der Grafikkarte und nicht auf dem Bildschirm stattfinden. Viele hunderte dieser kleinen Sprites nebeneinander gezeichnet, erzeugen ein Gesamtbild auf dem Bildschirm des Spielers.

Set DD_Surface = DD.CreateSurfaceFromFile(Filename, ddsdSprite)

Dies ist die Schlüsselzeile in dieser Funktion. Dem Objekt DD_Surface wird das Ergebnis des Aufrufs der CreateSurfaceFromFile-Funktion zugewiesen. Diese ist eine Funktion des DirectDraw-Objekts, deshalb müssen wir das DD. davor schreiben. Die Funktion lädt eine Grafik aus einer Datei in eine Surface; dazu übergibt man, wie oben gut zu sehen ist, den Dateinamen der Grafikdatei als ersten Parameter und die Surfacebeschreibung als zweiten Parameter.

Wir möchten nun gerne wissen, wie hoch und wie breit ein Sprite in der Bitmap ist, um nachher die Animation berechnen zu können.

Cols = ddsdSprite.lWidth / Cols
Rows = ddsdSprite.lHeight / Rows

Da eine Funktion in Visual Basic immer nur eine Rückgabe haben kann, müssen wir einen kleinen Umweg in Kauf nehmen. Wir haben in der Funktionsdeklaration Rows und Cols als ByRef deklariert, da wir ja nichts genaues angegeben haben und ByRef als Standard-Einstellung verwendet wird. ByRef bedeutet, dass die Werte in der Funktion selbst verändert werden können und sich die Veränderungen auch nach Verlassen der Funktion auf die Variable auswirken. Dies können wir ausnutzen und Rows und Cols sozusagen noch als weitere Rückgaben verwenden. Das wird weiter unten auch noch mal an einem beispielhaften Funktionsaufruf demonstriert.

Im Allgemeinen Deklarationsabschnitt des Moduls deklarieren wir für die SpriteBreite (Cols) und die SpriteHöhe (Rows) einen Typ, der weitere Informationen zum Objekt und dessen Verhalten aufnimmt.

Public Type T_SPRITE
	SpriteBreite As Integer
	SpriteHöhe As Integer
	DD_SURFACE As DirectDrawSurface7
End Type
Die Informationen brauchen wir im nächsten Abschnitt. Deshalb speichern wir sie in einem benutzerdefinierten Typen zwischen.

Jetzt aber wieder zurück zum Funktions-Quellcode. Da wir auch nicht-rechteckige Sprites einsetzen möchten, müssen wir die transparenten Bereiche der Bitmap bestimmen. Dazu malen wir bei der Erstellung die Bitmap in einer bestimmten Farbe, z. B. Schwarz, an den Stellen an, an der sie später transparent sein soll. Alle schwarzen Bereiche werden beim Kopieren auf den Bildschirm nicht mitgezeichnet.

Oben hatten wir einen DirectDraw-ColorKey deklariert. Dieser hat nur zwei Eigenschaften, nämlich low und high. So kann man auch mehrere Farben als transparent festlegen. Meistens reicht aber eine Farbe aus.

	Ckey.low  = ColorKey
	Ckey.high = ColorKey
Wir weisen beiden Eigenschaften den gleichen Wert zu, dadurch wird nur eine Farbe transparent gezeichnet.

Nun müssen wir das ColorKey-Objekt mit der Surface verbinden.

	DD_Surface.SetColorKey DDCKEY_SRCBLT, Ckey
Das bewerkstelligen wir über die SetColorKey-Methode des Surface-Objekts. Diese erwartet zwei Parameter: Der erste gibt an, auf welchen Bereich der ColorKey angewendet werden soll, der Zweite ist der ColorKey selbst. Mit DDCKEY_SRCBLT legen wir fest, dass beim Zeichnen der Transparenz-Effekt auf die Quelle, also auf die Surface, angewendet werden soll.

Hier noch mal der komplette Sourcecode der fertigen Funktion:

Public Function LoadSurface(DD_Surface As DirectDrawSurface7, Filename As String, Rows As Integer, Cols As Integer, SurfaceWidth As Integer,_ SurfaceHeight As Integer, ColorKey As Integer) As Boolean

On Error Goto ErrOut

Dim Ckey As DDCOLORKEY
Dim ddsdSprite As DDSURFACEDESC2

ddsdSprite.lFlags = DDSD_CAPS Or DDSD_WIDTH OR DDSD_HEIGHT
ddsdSprite.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN
ddsdSprite.lWidth = SurfaceWidth
ddsdSprite.lHeight = SurfaceHeight

Set DD_Surface = DD.CreateSurfaceFromFile(Filename, ddsdSprite)

Cols = ddsdSprite.lWidth / Cols
Rows = ddsdSprite.lHeight / Rows

Ckey.low = ColorKey
Ckey.high = ColorKey

DD_Surface.SetColorKey DDCKEY_SRCBLT, Ckey

LoadSurface = True
Exit Function

ErrOut:
InitDirectDraw = False

End Function


Wie oben versprochen, hier ein Beispielaufruf um eine nichtanimierte Grafik zu laden:

Dim MySprite As T_SPRITE

Dim Cols As Integer
Dim Rows As Integer

If LoadSurface(MySprite.DD_SURFACE, App.Path & "Bild.bmp", Rows, Cols, 320, 200, 0) = False Then

MsgBox "Beim Laden der Grafikdatei ist ein Fehler aufgetreten.", vbCritical, "FEHLER"
End

End If

MySprite.SpriteBreite = Cols
MySprite.SpriteHöhe = Rows

Zuerst deklarieren wir eine Variable von unserem benutzerdefinierten Typen T_SPRITE und setzen Rows und Cols auf jeweils 1, da wir nur eine statische Grafik haben. Beim Aufruf der Funktion LoadSurface wird eine Surface der Größe 320x200 Pixel erzeugt. Den ColorKey setzen wir auf 0, d. h. Schwarz ist die transparente Farbe. Die Variablen Rows und Cols werden von der Funktion verändert, sie beinhalten jetzt die Höhe und die Breite eines einzelnen Sprites, falls sich mehrere Grafiken in der Bitmap befanden. Ansonsten bleiben die Abmessungen der Grafik unverändert, wie in diesem Beispiel: 320x200 Pixel.

Auch nach diesem langen Abschnitt, sind wir leider immer noch nicht so weit, dass wir eine Grafik auf dem Bildschirm sehen können. Aber dafür sind wir jetzt bestens vorbereitet auf das nächste Abschnitt, das nun endlich zeigen wird, wie man Grafiken auf den Bildschirm zeichnet und welche verschiedenen Techniken es dafür gibt.

Abschnitt 5: Grafiken zeichnen:

Im letzten Abschnitt wurde gezeigt, wie man Grafiken mit DirectDraw lädt. Die Grafiken nutzen uns aber gar nichts wenn sie nur im Speicher liegen, denn wir wollen sie ja auf dem Bildschirm ausgeben. Das Zeichnen von Sprites auf den Bildschirm nennt man in DirectDraw blitten.

Welche Technik die Beste zum Blitten der Grafiken ist, habe ich schon ausführlich im Abschnitt Allgemeine Funktionsweise von DirectDraw beschrieben. Falls du es noch nicht gelesen hast, solltest du es jetzt tun um die Hintergründe zu verstehen. Nun wollen wir das Double Buffering mal in der Praxis anwenden.

Wir wollen jetzt die im Abschnitt DirectDraw Grundgerüst vorgestellte Funktion DrawSurfaces entwickeln. Diese muss für jedes neue Projekt anders geschrieben werden, deshalb ist sie auch im Grundgerüst noch nicht aufgetaucht.

Im vorigen Abschnitt Grafiken laden haben wir die Grundlagen geschaffen um nun endlich die geladenen Grafiken anzuzeigen, bzw. Animationen auf dem Bildschirm auszugeben. Das Ziel dieses Abschnitts ist, die Grafik, die wir vorher geladen haben mithilfe der Funktion DrawSurfaces anzuzeigen. Wir gehen aber diesmal davon aus, dass unsere Grafikdatei eine Animation enthält, um auch diesen Fall einmal vorzustellen.

DrawSurfaces hat keine Parameterliste und gibt auch keinen Wert zurück. Man könnte nun auch eine Fehlerbehandlung einbauen, aber das würde die Funktion nur unnötig kompliziert machen. Außerdem habe ich schon in den Abschnitts vorher oft genug gezeigt, wie man eine Fehlerbehandlung einbaut, also solltest du in der Lage sein, sie auch selber noch dazu zu schreiben.

Public Function DrawSurfaces()

End Function


Die Parameterliste der Funktion ist wie gesagt leer. Aber wie soll die Funktion nun wissen, was sie zeichnen soll? Dafür haben wir im vorigen Abschnitt einen eigenen Typen angelegt, der die Spritebreite und Spritehöhe sowie die Surface des Sprites beinhaltet. Für dieses Beispiel gibt es nur die öffentliche (public) Variable MySprite, die vom oben genannten Typ T_SPRITE ist. Wenn man ein Spiel mit vielen Sprites entwickeln möchte, wäre es natürlich einfacher ein dynamisches Array MySprites() oder ähnliches anzulegen, um alle Grafiken besser zu verwalten. Aber bleiben wir bei unserem einfachen Beispiel.

Das DirectDraw-Objekt stellt uns mehrere Blt-Methoden zum Zeichnen zur Verfügung. Hiervon wählen wir die einfachste und meist auch gebräuchlichste Methode. Sie heißt schlicht und einfach Blt().

Als ersten Parameter erwartet Blt() eine RECT-Variable, also eine Variable, die einen rechteckigen Bereich beschreibt. Diese Variable soll den Zielbereich beinhalten. Der zweite Parameter ist die Surface selbst. Als drittes Argument erwartet die Funktion Blt() eine RECT-Variable, die den Quellbereich beschreibt. Der letzte Parameter ist für Flags gedacht.

Public Function DrawSurfaces()

Dim SrcRECT As RECT
Dim DestRECT As RECT

End Function
Zuerst deklarieren wir die lokalen Variablen SrcRECT und DestRECT vom Typ RECT. SrcRECT bezieht sich dabei auf einen rechteckigen Bereich innerhalb der Quelle, also der Bitmap. So kann man auch nur Teile einer Bitmap zeichnen, was für Animationen sehr nützlich ist, wenn sich die einzelnen Bilder der Animation in einer Bitmap befinden. DestRECT funktioniert analog zu SrcRECT, nur das sich diese Variable auf den Zielbereich, den Bildschirm, bezieht.

Public Function DrawSurfaces()

Dim SrcRECT As RECT
Dim DestRECT As RECT
Static AniNr As Integer

If AniNr > 3 Then AniNr = 0

SrcRECT.Left = MySprite.SpriteBreite * AniNr
SrcRECT.Top = 0
SrcRECT.Right = srcRECT.Left + MySprite.SpriteBreite
SrcRECT.Bottom = srcRECT.Top + MySprite.SpriteHöhe

AniNr = AniNr + 1

End Function


Dieses Beispiel wird später eine Animation mit 4 Frames (Bildern) darstellen. Wir weisen der Left-Eigenschaft der SrcRECT-Variablen die SpriteBreite multipliziert mit der aktuellen Frame-Nummer zu. Dadurch legen wir den Anfangspunkt des RECTs immer auf die richtige Stelle in der Bitmap fest, da sich jedes Bild immer um SpriteBreite Pixel nach rechts vom vorigen Bild entfernt befindet. SrcRECT.Top setzen wir auf 0, da unsere Bitmap-Datei wie folgt aussieht:



Die einzelnen Frames befinden sich nebeneinander, also muss der Anfangspunkt auf der Y-Achse nicht verändert werden, sondern er bleibt immer 0. SrcRECT.Right sieht für einen Visual Basic Programmierer etwas seltsam aus. Normalerweise erwartet man etwas ähnliches wie Width. Deshalb muss man bei dieser Wertzuweisung besonders als Anfänger aufpassen, dass man sich nicht mit Width vertut. Right ist eigentlich nur X + Width, eben der rechte Rand. Für Bottom gilt das gleiche, nur das es sich dabei um den unteren Rand handelt.

Public Function DrawSurfaces()

Dim SrcRECT As RECT
Dim DestRECT As RECT
Static AniNr As Integer

If AniNr > 3 Then AniNr = 0

SrcRECT.Left = MySprite.SpriteBreite * AniNr
SrcRECT.Top = 0
SrcRECT.Right = srcRECT.Left + MySprite.SpriteBreite
SrcRECT.Bottom = srcRECT.Top + MySprite.SpriteHöhe

AniNr = AniNr + 1

DestRECT.Left = 100
DestRECT.Top = 100
DestRECT.Right = DestRECT.Left + SpriteBreite
DestRECT.Bottom = DestRECT.Top + SpriteHöhe

End Function

Wir wollen die Animation an den Koordinaten 100, 100 auf dem Bildschirm ausgeben. Die Größe der Grafiken soll dabei nicht verändert werden, was natürlich auch möglich gewesen wäre. Hätte man z. B. anstatt

DestRECT.Right = DestRECT.Left + SpriteBreite

geschrieben:

DestRECT.Right = DestRECT.Left + (SpriteBreite / 2)

so würde die Grafik in der Breite um die Hälfte gestaucht dargestellt werden. Dieses Umrechnen der Grafikdaten übernimmt DirectDraw ganz automatisch. Wenn man dies allerdings zu intensiv einsetzt kann die Geschwindigkeit des Programms erheblich darunter leiden. Deshalb sollte man Sprites immer in der Originalgröße, also in der Größe der Bitmap, blitten.

Nun folgt der Aufruf, der den aktuellen Frame auf den Bildschirm zeichnet.

BackBuffer.Blt destRECT, MySprite.DD_SURFACE, SrcRECT, DDBLT_KEYSRC Or DDBLT_WAIT

Die Blt-Funktion schneidet nun den Bereich srcRECT aus der Bitmap, bzw. der DirectDraw Surface DD_SURFACE aus, und zeichnet sie in den Bereich DestRECT auf den Bildschirm. Das Flag DDBLT_WAIT schreibt DirectDraw vor, dass gewartet werden soll, bis die Grafik auch wirklich vom Monitor gezeichnet wurde, und das erst dann weitere Grafiken geblittet werden können. Mit dem Flag DDBLT_DONOTWAIT kann man bewirken, dass DirectDraw nicht auf den Monitor wartet, allerdings kann es dabei zu Bildfehlern kommen, was man besser vermeiden sollte, auch wenn die Geschwindigkeit u. U. etwas höher wäre.

Nun haben wir ja immer rechteckige Bereiche mit unseren RECTs beschrieben. Wir wollen aber vielleicht auch nicht-rechteckige Grafiken zeichnen. Mit DDBLT_KEYSRC geben wir an, dass der ColorKey der zu blittenden Surface berücksichtigt werden muss. Der im ColorKey angegebene Farbbereich wird von Blt() nicht mit auf die Zielsurface gezeichnet sondern einfach ausgelassen. So kann man auch nicht-rechteckige Grafiken ausgeben.

Wenn wir nun unser Beispielprogramm starten, werden wir feststellen, dass auf dem Bildschirm nichts ausgegeben wird. Das liegt daran, dass wir die primäre und die sekundäre (Backbuffer) Surface noch nicht ausgetauscht haben. Das Bild, nämlich unsere Animation, liegt schon fertig zusammengestellt auf dem Backbuffer bereit, es fehlt uns nur noch eine kleine Funktion, die den Backbuffer auf dem Bildschirm sichtbar macht.

Auch an diese Funktion haben wir schon beim Erstellen des DirectDraw-Grundgerüstes gedacht. Die Funktion FlipSurfaces, die für den Austausch der primären und der Backbuffer Surface zuständig ist, fällt relativ schlank aus. Sie besteht nur aus einer einzigen Zeile, die aber immens wichtig für unser Programm ist, wie wir gerade bemerkt haben.

Public Function FlipSurfaces()

Primary.Flip Nothing, DDFLIP_WAIT

End Type
Wir rufen die Flip-Methode der primären Surface auf. Diese erwartet zwei Parameter. Der erste ist vom Typ DirectDrawSurface7, der Zweite ist ein Flag, mit dem man bestimmen kann, ob auf den Monitor gewartet werden soll (DDFLIP_WAIT) oder nicht (DDFLIP_DONOTWAIT). Das Prinzip ist also das gleiche wie bei Blt().

Jetzt wirst du dich vielleicht wundern, warum wir als Surface nicht das Backbuffer-Objekt übergeben haben. DirectDraw flippt immer zur nächsten Surface in der Flipping Chain wenn Nothing übergeben wird und da es beim Double Buffering nur zwei Surfaces insgesamt gibt, ist es für DirectDraw klar, welche Surfaces miteinander ausgetauscht werden sollen. Die Übergabe einer DirectDraw Surface ist also nur bei erweiterten Techniken wie Tripple Buffering, die aber kaum Anwendung findet, nötig. Ansonsten können wir immer Nothing schreiben.

Jetzt müssen wir nur noch den Backbuffer löschen, bevor wir wieder ein neues Bild darauf zusammenstellen. Wenn wir den Backbuffer nicht löschen würden, wäre das vorangegangene Bild noch darauf zu sehen, wir müssen die Überreste also erst beseitigen.

Public Sub ClearBackBuffer()

	Dim r As RECT
	
r.Left = 0
	r.Top = 0
	r.Width = 800
	r.Height = 600

	BackBuffer.BltColorFill r, 0

End Sub
Zuerst definieren wir einen rechteckigen Bereich. Dieser hat die Ausmaße der aktuellen Bildschirmauflösung; wir möchten ja schließlich den ganzen Bildschirm löschen. Mit

	BackBuffer.BltColorFill r, 0
wird die Backbuffer Surface mit der angegebenen Farbe 0 (= Schwarz) gefüllt. Außerdem wird als Argument noch das Rechteck übergeben. Jetzt sollte unser erstes DirectDraw-Programm endlich funktionieren.

Abschnitt 6: Textausgabe

Im letzten Abschnitt wurde gezeigt, wie man Grafiken mit DirectDraw blittet. Man kann aber nicht nur Grafiken ausgeben, sondern auch Text.

Da das jeweils nächste Bild immer auf dem Backbuffer zusammengestellt werden muss, muss auch der Text erst mal in den Backbuffer geschrieben werden. Dazu stellt uns das Backbuffer-Objekt (vom Typ DirectDrawSurface7) die Methode DrawText zur Verfügung.

Die ersten beiden Parameter nehmen die Koordinaten, an dem der Text ausgegeben werden soll, auf. Der nächste Parameter beinhaltet den Text, der später auf dem Bildschirm erscheint. Der letzte Parameter vom Typ Boolean gibt bei True an, dass die gesetzten Koordinaten ignoriert werden sollen und dass der Text an der aktuellem Cursorposition ausgegeben werden soll. False gibt an, dass die Koordinaten zur Ausgabe benutzt werden sollen.

Beispiel:

Public Sub DrawSurfaces()

	BackBuffer.DrawText 100, 100, "Energie: 100%", False

	BackBuffer.DrawText 100, 200, "Schüsse: 5", True

End Sub
Beim ersten Beispiel wird der Text "Energie: 100%" an den Koordinaten 100, 100 ausgegeben. Beim zweiten Beispiel dient die aktuelle Cursorposition als Ausgabekoordinate, die übergebenen Parameter x=100 und y=200 werden also ignoriert.

Die Schriftfarbe kann auch gewechselt werden. Mit

	BackBuffer.SetForeColor 0
wird die Schriftfarbe z.B. auf 0 (=Schwarz) gesetzt, was auf hellem Untergrund sinnvoll ist.


[back to top]



Userdaten
User nicht eingeloggt

Gesamtranking
Werbung
Datenbankstand
Autoren:04510
Artikel:00815
Glossar:04116
News:13565
Userbeiträge:16552
Queueeinträge:06247
News Umfrage
Ihre Anforderungen an ein Online-Zeiterfassungs-Produkt?
Mobile Nutzung möglich (Ipone, Android)
Externe API Schnittstelle/Plugins dritter
Zeiterfassung meiner Mitarbeiter
Exportieren in CSV/XLS
Siehe Kommentar



[Results] | [Archiv] Votes: 1157
Comments: 0