ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!
Effizientes Zeichnen und
Textausgabe
Ich weiß, was Anfänger jetzt wahrscheinlich denken:
"Bei DirectX sind doch schon einfache Methoden, um Linien zu
zeichnen oder Texte auszugeben, dabei, wir brachen kein Tutorial
dafür!", aber die DirectX Methoden sind ineffizient! Ich
hab' keine Ahnung was MS sich dabei gedacht hat, aber jedesmal,
wenn Ihr die DirectDrawSurface7.DrawLine oder DirectDrawSurface7.DrawText
Methode aufruft, sichert (lock) ihr die Surface und gebt sie
wieder frei (unlock) ! Das ist zwar ganz schön, wenn ihr nur ein
oder zwei Dinge zeichnen wollt... aber wenn ihr größere Menge
zeichnen müsst, werdet ihr den Nachteil erkennen. Denn ihr
müsst die Surface jedesmal sichern und wieder freigeben - eine
teure Operation. Denn es besteht kein Zweifel, dass die
verlorenen Mikrosekunden sich summieren werden.
Die Alternative? GDI! Ihr denkt jetzt vielleicht, dass
API-Funktionen immer langsamer als DirectX-Funktionen sind, aber
das stimmt nicht. Durch die Benutzung der Zeichen-Funktionen, die
in Windows' GDI enthalten sind, könnt Ihr verhindern, dass die
Surfaces zu oft gesichert und wieder freigegeben werden. Sichert
die Surface nur einmal (um an ihren DC zukommen), gebt sie nur
einmal wieder frei (um ihren DC freizugeben) und erledigt alle
anfallenden Operationen dazwischen.
Jetzt lernen wir die API-Aufrufe kenn, um Text oder Linien zu
zeichnen.
Private Declare Function LineTo Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
Fangen wir mit etwas einfachem an :) Die LineTo-Funktion
zeichnet eine Linie von den aktuellen X-,Y-Koordinaten zu den
Koordinaten, die beim Aufruf übergeben werden. Aber wo sind
diese "aktuellen" Koordinaten? Nun, hierbei handelt es
sich um die Koordinaten, der letzten GDI-Operation, die
stattgefunden hat. Das ist zwar in einigen Fällen ganz nützlich
(zum Beispiel, wenn aneinander liegende Flächen gezeichnet
werden sollen), aber in den meisten Fällen ist es das nicht.
Also müssen wir die Möglichkeit haben die aktuellen Koordinaten
zu verändern:
Private Declare Function MoveToEx Lib "gdi32.dll" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, lpPoint As POINT_TYPE) As Long
Private Type POINT_TYPE
x As Long
y As Long
End Type
Übergebt die Koordinaten, die Ihr als aktuelle
Koordinaten haben wollt, einfach der MoveToEx-Funktion.
Ihr müsst außerdem ein Variable vom Typ POINT_TYPE
übergeben, lasst diese einfach leer.
Ok, Linien sind also ein Klacks. Aber was ist mit Kreisen?
Private Declare Function Ellipse Lib "gdi32" (ByVal hdc As Long, ByVal x1 As Long, ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long) As Long
Eine Ellipse? Natürlich... denn ein Kreis ist nur eine
spezielle Ellipse. Ihr könnt die Ellipse-Funktion
für jede Ellipse (oder jeden Kreis) eurer Wahl verwenden. Die
Koordinaten, die Ihr übergebt, formen ein Rechteck, welches die
Ausdehnungen der Ellipse formt. Wenn Ihr einen Kreis zeichnen
wollt, geht einfach sicher, das Höhe und Breite des Rechtecks
gleich groß sind.
Private Declare Function Arc Lib "gdi32" (ByVal hdc As Long, ByVal x1 As Long, ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long, ByVal X3 As Long, ByVal Y3 As Long, ByVal X4 As Long, ByVal Y4 As Long) As Long
Manchmal ist eine ganze Ellipse zuviel des Guten... dann
benutzt den Kreisbogen (Arc)! Die ersten vier Argumente formen
ein Rechteck, das die Form der Ellipse bestimmt. Diese Ellipse
wird dann, mit Hilfe der letzten vier Parameter zurecht
geschnitten. Hier wird euer Vorstellungsvermögen gefragt...
stellt euch einfach eine Halbgerade (einseitig unendliche Linie)
vor, die dem Mittelpunkt der Ellipse entspringt und die
Koordinaten x3 und y3 schneidet. Nun stellt euch eine ähnliche
Halbgerade vor, die aber x4 und y4 schneidet. Diese beiden
Halbgeraden teilen die Ellipse in zwei Kreisbögen. Wenn man sich
von der ersten Halbgerade aus gegen den Uhrzeigersinn zur zweiten
bewegt erhält man den Teil der von der Ellipse übrig bleibt.
TADAA! Ein Kriesbogen.
Private Declare Function TextOut Lib "gdi32" Alias "TextOutA" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal lpString As String, ByVal nCount As Long) As Long
Die gute alte TextOut-Funktion.
Übergeben werden müssen nur die Koordinaten, bei denen der Text
gezeichnet werden soll, der String der ausgegeben werden soll und
die Länge des Strings (in Zeichen). Das ist alles.
Private Declare Function SetTextColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long) As Long
Ohne Zweifel werden weiße Texte nach einer Weile ein
bißchen langweilig... deshalb hat Gott uns auch die SetTextColor-Funktion
gegeben. Übergebt Ihr einfach Wert einer Farbe (als
Long-Integer), die Ihr sehen wollt, und Ihr sollt Sie sehen! Auch
die VB-Konstaten, wie vbGreen und die anderen funktionieren
einwandfrei.
Private Declare Function SetBkMode Lib "gdi32" (ByVal hdc As Long, ByVal nBkMode As Long) As Long
Const TEXT_TRANSPARENT = 1
Const TEXT_OPAQUE = 2
Ich weiß nicht wie es euch geht... aber mag die
häßlichen, undurchsichtigen Hintergrund auf dem mein Text steht
nicht. Dies könnt Ihr mit der SetBkMode-Funktion
ändern. Übergebt die Konstante TEXT_TRANSPARENT
an diese Funktion und die komischen Hintergründe werden euch
nicht mehr stören.
Private Declare Function SetBkColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long) As Long
Oder vielleicht mögt Ihr diesen komischen,
undurchsichtigen Hintergrung! Wer bin ich, dass ich darüber
entscheide? Ihr könnt die Hintergrundfarbe mit der SetBkColor-Funktion
ändern. Es funktioniert genauso wie bei der SetTextColor-Funktion
außer, dass es eben die Hintergrundfarbe verändert.
Jetzt wird es ein bißchen komplizierter. Wenn Ihr vorhabt,
Änderungen am "Stift" (um zum Beispiel dicke oder
farbige Linien zu Zeichnen) oder der Schriftart vorzunehmen,
müsst ihr einige zusätzliche Schritte beachten. Ihr könnt
nicht einfach einen "Stift" (Pen) oder eine Schrift
einem DC zuweisen, Ihr müsst den "Stift" oder die
Schrift erst erstellen und könnt es dann in ein DC selektieren.
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Ihr MÜSST unbedingt daran denken die alten
Zeicheneinstellungen ("Stift"(Pen)) und die alte
Schrift zu speichern und sie nach eurer Zeichenoperation wieder
dem DC zuzuweisen. Außerdem muss der von euch erstellte
"Pen" oder die Schrift, nach dem Ihr fertig seid,
gelöscht werden. Falls Ihr dies nicht beachtet, werdet Ihr
schnell feststellen, dass euer Programm ein schweres
"Speicherleck" erzeugt!
Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long
Const PS_SOLID = 0
Const PS_DASH = 1
Const PS_DOT = 2
Const PS_DASHDOT = 3
Const PS_DASHDOTDOT = 4
Es gibt eine Menge verschiedener Zeicheneinstellungen, die
Ihr verwenden könnt... und offensichtlich könnt Ihr auch die
Stärke (Breite) und Farbe eures Stifts aussuchen.
Private Declare Function CreateFont Lib "gdi32" Alias "CreateFontA" (ByVal H As Long, ByVal W As Long, ByVal E As Long, ByVal O As Long, ByVal W As Long, ByVal i As Long, ByVal u As Long, ByVal s As Long, ByVal C As Long, ByVal OP As Long, ByVal CP As Long, ByVal Q As Long, ByVal PAF As Long, ByVal F As String) As Long
Verrückt! Es gibt hier eine ganze Menge Argumente...
einige werdet Ihr wohl nie ändern müssen, also werde ich nicht
weiter auf sie eingehen. Trotzdem gibt es ein paar wichtige. Die
ersten 2 Argumente definieren die Schrifthöhe und -breite. Das
vierte steht für Fettdruck, das fünfte für Kursivschrift und
das sechste für Unterstreichen. Im letzten Argument könnt Ihr
den Schriftnamen als String übergeben. "Arial" oder
"Courier" sind Beispiele für gültige Werte. Die
restlichen Argumente können über Konstanten verändert werden
(siehe Quellcode).
So, nun wißt Ihr wie diese API-Aufrufe theoretisch
funktionieren. Seht euch diesen Quellcode
an, um zu sehen, wie sie im richtigen Leben funktionieren!