Ich möchte mit diesem Kurs zeigen, dass es entgegen anders lautenden Gerüchten sehr gut möglich ist, in Visual Basic Spiele zu programmieren, die durchaus erstaunliche Ablauf-Geschwindigkeiten erreichen.

Der vorliegende Spiele-Kurs umfasst insgesamt 125 DIN A4-Seiten. Auf Grund dieser Größe wird er in 4 Teilen veröffentlicht. Der erste Teil berichtet über die grundlegenden Techniken, wie Sprites, Sounds Tastaturabfrage usw.

In diesem Kursabschnitt wird ein kompletter Shooter erläutert und entwickelt. Dabei werden sowohl die mittlerweile fast nur noch vertretenden 32-Bit- aber auch noch 16-Bit-Umgebungen berücksichtigt. Ich wünsche also viel Vergnügen.


Dominik Scherlebeck 07/2001

Anregungen oder Tipps an Dominik Scherlebeck
 
  1.1 Zu diesem Kurs [ Top ]

Hallo! Trotz einigen bedauerlichen Verzögerungen liegt Ihnen nun endlich der Spiele-Kurs vor (ich entschuldige mich bei allen, die ich immer und immer wieder "versetzt" habe!). Nachdem in den vorherigen Kursteilen schon über Grafik gesprochen wurde, werden wir jetzt einen Schritt weiter gehen und eigene Spiele mit Visual Basic programmieren. Dass dies auch ohne Assembler oder C++ möglich ist, soll dieser Kurs zeigen.

In diesem Teil werden wir uns mit dem Genre der Actionspiele beschäftigen. Das ist natürlich ein weites Feld. Hier soll es jedoch um ein Weltraumspiel gehen, indem der Spieler ein Raumschiff über den Bildschirm steuert, Asteroiden ausweicht und Gegner abschießt. Das Geschehen wird dabei von der Seite betrachtet. Das Level scrollt dabei von rechts nach links bis das Ende erreicht ist. Das waren auch schon die wesentlichen Grundelemente eines solchen Spiels. Dieses Spielprinzip kann man natürlich mit Power-Ups (also Extras, die der Spieler aufnehmen kann) leicht erweitern. Aber in diesem Kurs werden wir uns auf eine einfache Version eines solchen Spiels beschränken.

Sie finden übrigens alle Dateien, die für diesen Kurs benötigt werden, in der gepackten Datei, die Sie am Ende dieses Teils downloaden können. Wenn möglich sollten Sie aber das Programm selbst erarbeiten, da so das vermittelte Wissen besser sitzt. Übrigens: Auch wenn Sie ein Sidescrolling-Spiel nicht sonderlich interessiert, so ist der Inhalt der ersten Seiten doch sehr wichtig, da er die Grundlagen von vielen Spielen beschreibt: Die Sprites.

In dem nächsten Teil werden wir dann ein Pacman-Spiel erarbeiten und eine bessere Grafik-Engine schreiben. In Teil 3 folgt dann ein Jump'n Run-Spiel und zu guter Letzt werden wir in Teil 4 dreidimensionale Spiele programmieren. Dazu wird eine Erweiterungsdatei für VB mitgeliefert, die den Zugriff auf die WinG-DLL erlaubt und beispielsweise auch Texture-Mapping unterstützt.

Aber nun viel Spaß mit diesem Kursteil.

 
  1.2 Die nötigen Ressourcen [ Top ]

Bevor man ein Spiel programmiert muss man sich einiges Überlegen. Wo speichere ich die Grafiken? Womit erstelle ich überhaupt die Grafiken? Wie bekomme ich die Sachen auf den Bildschirm? Was für ein Spielkonzept soll ich benutzen? Wir fangen gleich mit den ersten beiden Punkten an:

Die Grafiken für das Spiel sollten am Besten 256-Farben-Grafiken sein. Das hat nichts mit irgendwelchen Beschränkungen oder ähnlichen Sachen zu tun. Es geht dabei viel mehr um den Speicherplatz. Eine 8-Bit Grafik (also 256 Farben) ist kleiner und kann außerdem schneller auf dem Bildschirm gezeichnet werden als eine 24-Bit (TrueColor) Grafik. Die Grafiken lassen sich am besten mit einem guten Malprogramm anfertigen.

Wenn Ihr Spiel auch im 256-Farben-Modus von Windows laufen soll, so müssen (!!) die Grafiken die gleiche Palette haben. Und noch ein wichtiger Punkt in Bezug auf Grafiken: Sprites müssen Sie doppelt zeichnen! Nachdem Sie den eigentlichen Sprite haben müssen Sie noch die sogenannte Maske zeichnen! Das ist wichtig, damit Sprites transparent dargestellt werden können. Wenn Sie den eigentlichen Sprite zeichnen, so steht dort die Farbe Schwarz für transparent. In der Maske steht Weiß für transparent und Schwarz für undurchsichtig.

Am leichtesten ist es, wenn Sie erst den richtigen Sprite zeichnen, diesen speichern und dann die transparenten Stellen mit Weiß und den Rest mit Schwarz übermalen. Um Ihnen hier die Arbeit etwas zu erleichtern finden Sie zusätzlich zu diesem Kurs das Programm "BMP2SPR.EXE" in dem Archiv (\UTIL). Dieses Programm kann ein beliebiges BMP-Bild laden und dazu eine Maske erstellen. Klicken Sie nach dem Laden des Bildes einfach auf "Umwandeln" und "Speichern". Auf diese Weise erstellen Sie die nötigen Sprites für das Spiel erstellen.

Wichtig ist ebenfalls die Animation. Es ist natürlich recht langweilig, wenn der Held beim Laufen nicht die Füße bewegt, oder  das Raumschiff still daher schwebt.

Daher erstellt man mehrere "Frames", also mehrere Einzelbilder. Sie brauchen jetzt natürlich nicht für Hunderte von Bewegungen Sprites zeichnen. In dem Spiel, dass wir hier erarbeiten, werden nur sehr wenig Animationsphasen benötigt. Außerdem befinden sich die hier benötigten Bilder und Hintergründe, genauso wie das fertige Spiel im Archiv dieses Kurses.

  VB16  
Hier gibt's die 16-Bit Version des Spiels (VB 3.0 / VB 4.0 - 16 Bit)
  VB32  
In diesem Verzeichnis ist die 32-Bit-Version (VB 4.0 - 32 Bit / VB 5.0/ VB 6.0)
  DEMO16  
Und hier ist das erste Demoprogramm mit Source-Code
  DEMO32  
Und natürlich auch wieder für 32-Bit

Auch ein Hintergrund darf nicht fehlen. Eine einfarbige Fläche ist nämlich sehr langweilig.

Gute Grafik alleine reicht aber für ein Spiel nicht aus. Auch der Sound und Musik dürfen nicht zu kurz kommen. Wenn Sie keine musikalische Ader haben, macht das erst einmal nichts. Oft gibt es frei benutzbare Musikstücke oder Sounddateien im Internet oder in den Archiven von AOL. Im Zweifelsfall sollten Sie die Autoren dieser Dateien per E-Mail um Erlaubnis fragen.

Die Musikstücke für ein Spiel sollten immer zum Level passen. Wenn das Level düster aufgemacht ist, so sollte auch die Musik entsprechend "düster" sein. Die Musikdateien sollten am besten im MIDI-Format vorliegen. Dies hat den Vorteil, dass die Dateien sehr klein bleiben, sowohl auf der Festplatte, als auch im Arbeitsspeicher.

Die Sounddateien sollten im WAVE-Format sein. Das hat den Vorteil einer guten Qualität und einfacher Handhabung.

Die zusammengestellten Dateien sollten Sie in das Verzeichnis des Spiels kopieren, dass Sie erstellen wollen. Sie müssen übrigens immer gut auf die Pfade achten! Verwenden Sie in Ihrem Spiel keine Angaben wie "C:\MYGAME\1.BMP". Es ist nämlich unwahrscheinlich, dass der spätere Benutzer diese Dateien auch dort platzieren wird. Viel eleganter ist die Möglichkeit, eine Pfadvariable einzuführen. Deklarieren Sie z.B. eine Variable mit dem Namen Pfad$ als globale Variable (wie das alles geht wird später noch ausführlich beschrieben). Diese Variable bekommt vorläufig den Pfad auf Ihrer Festplatte zugewiesen. Zum Beispiel: "C:\MYGAME\". Im Programm verwenden Sie dann Angaben wie Pfad$+"1.BMP". Wenn das Spiel dann fertig ist, brauchen Sie nur der Pfadvariable keinen Wert mehr zuweisen - also " " - und schon sind alle Pfadangaben relativ, und das Spiel kann in einem beliebigen Verzeichnis installiert werden.

 
  1.3 Reine Formsache [ Top ]

Um Sie nun etwas mit Sprites und Hintergründen vertraut zu machen, werden wir nun ein erstes Programm schreiben, dass ein Sprite auf einem Hintergrund anzeigt. Dazu brauchen wir eine Form mit vier Bildfeldern. Ein Bildfeld nimmt den Hintergrund auf, zwei für den Sprite (Bitmap und Maske) und das letzte Bildfeld dient zur Ausgabe auf dem Bildschirm.

Oft wird für Spiele ein Zwischenpuffer eingesetzt. Das bedeutet, das Bild wird auf ein unsichtbares Bildfeld aufgebaut und dieses Bildfeld wird anschließend auf ein sichtbares Bildfeld kopiert. Diese Technik müssen wir allerdings nicht anwenden, wenn wir bei dem sichtbaren Bildfeld die AutoRedraw-Eigenschaft auf True setzen. Dadurch wirken sich alle Funktionen erst einmal auf den Speicher aus. Das Bildfeld kann dann anschließend mit der Refresh-Methode aktualisiert werden.

Bei allen anderen Bildfelder muss (!) AutoRedraw ebenfalls auf True stehen. Die Hintergründe sind hier unwichtig, daher werde ich auf genauere Ausführungen verzichten.

Erstellen Sie nun die folgende Form. Die Werte für die Picture-Eigenschaft in dem Diagramm geben die Datei an, die Sie in dieses Bildfeld laden sollen. Die Dateien finden Sie im Verzeichnis \DEMO1 dieses Archivs. Dort finden Sie auch das komplette Programm, wenn Sie keine Lust auf einen "Eigenbau" haben.

Nachdem Sie nun die Elemente platziert haben, machen Sie noch alle Bildfelder außer "DISPLAY" unsichtbar, indem Sie die Visible-Eigenschaften auf False setzen. Anschließend adjustieren Sie noch die Größe der Form, damit im laufenden Programm nicht so viel leerer Raum zu sehen ist.

Wie Sie vielleicht gemerkt haben, steht die AutoSize-Eigenschaft bei den Bildfeldern mit den Ressourcen auf True. Das erleichtert Ihnen die Arbeit, da so die Größe der Objekte automatisch an die Größe der Bitmap angepasst wird.

Da die Form nun fertig ist, wenden wir uns der eigentlichen Programmierarbeit zu. Um mit Sprites zu arbeiten, müssen wir API-Funktionen einsetzen. Wenn Ihnen dieser Begriff nichts sagt oder Sie sich nicht mit den Funktionen BitBlt und StretchBlt auskennen, so lesen Sie sich bitte dazu den VB-Aufbaukurs Grafik 1 & 2 durch.

 
  1.4 Die "Engine" [ Top ]

Das Wort Engine ist fast schon ein Modebegriff geworden. Wenn Sie mal eine PC-Spiele-Zeitschrift lesen, so finden Sie fast in jedem Bericht so etwas wie "Die neue 3D-Engine, die in diesem Spiel benutzt wird...". Engine heißt nichts weiter als Maschine oder Motor. Eine Engine ist in der Programmierung ein Bestandteil eines Programms, der sich um eine einzelne Aufgabe kümmert. Eine 3D-Engine führt zum Beispiel 3D-Berechnungen durch und eine Grafik-Engine sorgt für die Darstellung der Grafik auf dem Bildschirm. Solche Engines haben den Vorteil, dass man sie wiederverwenden kann. Ein populäres Beispiel ist die 3D-Engine von Ken Silverman. Diese Engine wird in verschiedenen Spielen angewendet.

Nun, so hoch wollen wir erst einmal nicht hinaus - aber die Arbeitsaufteilung ist auch für unsere Zwecke mehr als praktisch. Darum werden wir die Grafikfunktionen in einem eigenen Codemodul unterbringen. Ich werde hier nur den Quellcode zeigen. Eine genauere Beschreibung finden Sie ebenfalls in dem Grafik-Aufbaukurs.

Wie Sie vielleicht noch wissen gibt es zwei unterschiedliche Versionen der API. Einmal ist da die 16-Bit API. Diese ist die Grundlage von Windows 3.1. Auf der anderen Seite gibt es die 32-Bit API von Windows 95 bis Windows XP. An welche API Sie sich halten müssen, liegt an der Visual Basic-Version. Wenn Sie VB 3.0 oder VB 4.0 in der 16-Bit Version benutzen, verwenden Sie die 16-Bit API. Verwenden Sie VB 4.0 in der 32-Bit Version oder VB 5.0 etc., so verwenden Sie die 32-Bit API.

Legen Sie nun ein neues Codemodul an und schreiben Sie unter "Deklarationen" die entsprechenden Anweisungen:

Option Explicit

Public Declare Function BitBlt Lib "Gdi32" (ByVal NachHdc _
     As Long, ByVal x As Long, ByVal Y As Long, ByVal w As Long, _
     ByVal h As Long, ByVal vonHdc As Long, ByVal vonX As Long, _
     ByVal vonY As Long, ByVal Modus As Long) As Long

Public Declare Function StretchBlt Lib "Gdi32" (ByVal NachHdc _
    As Long, ByVal x As Long, ByVal Y As Long, ByVal w As Long, _
    ByVal h As Long, ByVal vonHdc As Long, ByVal vonX As Long, _
    ByVal vonY As Long, ByVal vonW As Long, ByVal vonH As Long, _
    ByVal Modus As Long) As Long
Public Const BIT_COPY   = &HCC0020
Public Const BIT_AND    = &H8800C6
Public Const BIT_INVERT = &H660046
Public Const BIT_BLACK  = &H42&

Beachten Sie, dass die Declare-Anweisungen in eine Zeile geschrieben werden müssen, sonst treten Fehler auf. Wenn das Programm beim Ausführen die Meldung "Falsche DLL-Aufrufkonventionen" ausgibt, haben Sie sich vertippt. Im Zweifelsfall benutzen Sie einfach die mitgelieferten Dateien.

 
  1.5 Sprites in Action [ Top ]

Nachdem Sie nun die Engine fertig haben, fangen wir mit dem Programm an. Da es ja nur eine Demo sein soll, brauchen wir nicht viel. Wenn der Benutzer die Maus über das Display-Bildfeld bewegt, soll an dieser Stelle das Sprite erscheinen.

Dazu benutzen wir die MouseMove-Eigenschaft des Display-Bildfelds. Jetzt ist nur noch die Frage, welche Anweisungen wir dort hinein schreiben. Da wir mit den API-Funktionen arbeiten und dessen Breiten- und Höhen-Angaben, ist es sinnvoll, wichtige Angaben gleich am Anfang in Variablen zu speichern. Zum Beispiel sichern wir die Breite (in Pixel) des HGrund-Bildfelds. Dann brauchen wir später nicht immer schreiben HGrund.ScaleWidth usw.

Hier jetzt die erste Version der Ereignisprozedur:
Private Sub Display_MouseMove(Button As Integer,_
        Shift As Integer, X As Single, As Single)

  Dim w As Long, h As Long

  w = Display.ScaleWidth
  h = Display.ScaleHeight
  BitBlt Display.hDC, 0, 0, w, h, HGrund.hDC, 0, 0, BIT_COPY
  Display.Refresh
End Sub

Am Anfang werden Breite und Höhe vom Display und vom Hintergrund in Variablen gespeichert, anschließend wird BitBlt benutzt, um den Hintergrund auf das Display zu bringen. Als Modus wird dabei BIT_COPY angewandt (oft wird diese Konstante SRCCOPY genannt, doch das hat auf das Ergebnis keine Auswirkungen). Zum Schluss wird ein Refresh durchgeführt, damit das Bildfeld den Inhalt sofort anzeigt. Wenn Sie das Programm starten und den Mauszeiger über das Bildfeld bewegen, so erscheint dort der Hintergrund. Jedoch fehlt noch das eigentliche Sprite.

Sie wissen schon, dass Sie für ein Sprite ein Bitmap und eine Maske benötigen. Um nun ein Sprite auf einem Hintergrund anzuzeigen sind zwei BitBlt-Operationen notwendig:

  1. Die Maske wird mit BIT_AND (bzw. SRCAND) auf den Hintergrund kopiert
  2. Das Bitmap wird mit BIT_INVERT (bzw. SRCINVERT) darauf kopiert

Nach diesen Operationen ist das Sprite transparent auf dem Hintergrund zu sehen. Warum das funktioniert, will ich hier nicht beschreiben. Es ist für uns auch nicht wichtig. Dieses Verfahren ist übrigens allgemein gültig. Jetzt müssen wir noch die entsprechenden Codezeilen eingeben.

Private Sub Display_MouseMove(Button As Integer, _
        Shift As Integer, X As Single, Y As Single)

  Dim w As Long, h As Long
  Dim sw As Long, sh As Long

  w = DISPLAY.ScaleWidth
  h = DISPLAY.ScaleHeight
  sw = BITMAP.ScaleWidth
  sh = BITMAP.ScaleHeight

  BitBlt DISPLAY.hDC, 0, 0, w, h, HGRUND.hDC, 0, 0, BIT_COPY
  BitBlt DISPLAY.hDC, X, Y, sw, sh, MASKE.hDC, 0, 0, BIT_AND
  BitBlt DISPLAY.hDC, X, Y, sw, sh, BITMAP.hDC, 0, 0, BIT_INVERT
  DISPLAY.Refresh
End Sub

Auch bei den neu eingefügten Zeilen werden Variablen als "Zwischenwerte" benutzt. Wenn Sie das Programm nun ausführen, werden Sie feststellen, dass es den gewünschten Effekt erzielt. An der Mausposition über dem Display erscheint ein Sprite auf dem Hintergrund.

Der "Schatten" des Sprites wurde übrigens durch ein Punktgitter erzeugt. Dies ist eine vielfach angewendete Methode für Schatten. Dabei erzeugt man ein Punktmuster aus schwarzen (als durchsichtigen) und anders gefärbten Punkte (beispielsweise dunkelgrau). Später nimmt der Spieler kaum war, dass es sich um ein Raster handelt, aber die Farben erscheinen dunkler an dieser Stelle, da ja jeder 2. Pixel mit dunkelgrau übermalt wird.

 
  1.6 Multimedial [ Top ]

Das Darstellen von Sprites ist nun ja kein Problem mehr, allerdings fehlt noch der Soundteil. Wie kann man mit VB WAVE- und MIDI-Dateien abspielen? Fangen wir mit den MIDI-Dateien an. Dazu benötigen wir das MCI-Steuerelement von Visual Basic (MCI = Multimedia Control Interface). Dieses Steuerelement befindet sich in der Datei MCI.VBX bzw. MCI32.OCX. Das Steuerelement selbst sieht wie die Knopfleiste eines Kassettenrecorders aus:

Was das Objekt im Einzelnen kann, spielt hier keine Rolle. Wir werden es über Unterprozeduren steuern, damit wir später mit nur einem Befehl eine beliebige MIDI-Datei abspielen können. Damit das klappt sind zunächst einige Eigenschaften anzupassen:

DeviceType = "Sequencer"
Name = "Midi"
Visible = False

Jetzt ist das MCI auf MIDI-Dateien eingestellt und trägt auch den Namen MIDI. Da Visible auf False steht ist es im laufenden Programm nicht zu sehen.

Das MCI wird über Befehle gesteuert, die dem Steuerelement mit der Command-Methode übergeben werden und der Filename-Eigenschaft, die die aktuelle Datei angibt. Eine ausführliche Beschreibung aller Befehle des MCI's finden Sie in der Online-Hilfe von VB oder in Ihrem Handbuch. Hier lernen Sie nur die Befehle kennen, die zum Abspielen einer MIDI-Datei wichtig sind.

Da wir jetzt ja neuerdings mit "Engines" programmieren, setzen wir unser MCI-Steuerelement auf eine eigene Form. Die Form bekommt den Namen "MEDIA" und das MCI-Steuerelement den Namen "MIDI". Legen Sie nun ein weiteres Codemodul an. Dieses Modul wird jetzt die Prozeduren zur Steuerung der Wiedergabe fassen. Das wir diese Prozeduren nicht in die Form setzen, hängt damit zusammen, dass man diese unter VB 3.0 dann nicht von überall im Programm ausführen kann. Nachdem Sie das Codemodul angelegt haben erstellen Sie folgende Prozeduren:

MIDI-Datei abspielen:
Public Sub MIDI_PLAY(Datei As String)

  On Local Error Resume Next

  If Dir$(Datei$) = "" Then Exit Sub
  MEDIA.MIDI.Command = "CLOSE"
  MEDIA.MIDI.filename = Datei$
  MEDIA.MIDI.Command = "OPEN"
  MEDIA.MIDI.Command = "PREV"
  MEDIA.MIDI.Command = "PLAY"
 
End Sub

Diese Prozedur gibt erst den Befehl CLOSE an das Steuerelement, um eine evtl. geöffnete MIDI-Datei zu schließen. Anschließend wird die Filename-Eigenschaft auf die Datei gesetzt, die abgespielt werden soll. Danach folgen die Befehle OPEN (öffnen der Datei), PREV (zurückspulen) und PLAY (abspielen). Am Anfang der Prozedur werden Vorkehrungen getroffen, dass kein Fehler auftreten kann.

MIDI-Datei stoppen:

Public MIDI_STOP()
  MEDIA.MIDI.Command = "STOP"
  MEDIA.MIDI.Command = "PREV"
End Sub

Diese Prozedur stoppt die Wiedergabe mit dem STOP-Befehl. Anschließend wird mit dem PREV-Befehl die Datei "zurückgespult". Somit fängt sie von vorne an, wenn die Ausgabe fortgesetzt wird.

MIDI-Datei pausieren:

Public MIDI_PAUSE()
  MEDIA.MIDI.Command = "STOP"
End Sub

Hier wird die Ausgabe nur gestoppt, sie kann dann einfach an der gleichen Stelle fortgesetzt werden.

MIDI-Datei fortsetzen:

Public MIDI_CONTINUE()
  MEDIA.MIDI.Command = "PLAY"
End Sub

Hier wird mit PLAY die Ausgabe gestartet. Ganz egal, ob die Ausgabe angehalten oder ob die Datei zusätzlich zurückgespult wurde.

Prüfen, ob MIDI-Datei noch läuft:

Public Function MIDI_PLAYING()
  If MEDIA.MIDI.Position = MEDIA.MIDI.Length Then
    MIDI_PLAYING = 0
  Else
    MIDI_PLAYING = 1
  End If
End Function

Diese Funktion gibt TRUE (1) zurück, wenn die MIDI-Datei noch läuft. Ist dagegen schon das Ende erreicht gibt sie FALSE (0) zurück. Mit Hilfe dieser Funktion lässt sich einfach eine Endloswiedergabe erzielen. Dazu prüft man öfter, ob die Wiedergabe noch läuft. Ist das nicht der Fall ruft man MIDI_STOP und MIDI_CONTINUE auf.

Jetzt haben wir schon zwei Engines in unserem Programm: Die Grafik-Engine und die MIDI-Engine. Sie sollten Ihren Dateien auch hier sinnvolle Namen geben, wie z.B. "MIDI.BAS".

Jetzt kommen wir zu den Soundeffekten, die wir mit der Windows-API abspielen.

 
  1.7 Das Tonstudio [ Top ]

Wir könnten zwar auch WAVE-Dateien mit dem MCI ausgeben, allerdings ist das Handling zu umständlich. Deshalb greifen wir einfach auf die API zurück. Diese Funktionen unterstützen zwar auch kein Mixen von Tönen, jedoch kümmern Sie sich automatisch darum, welche Töne ausgegeben werden und welche nicht. Und im Verlauf des Spiels fällt es kaum auf, dass nicht zwei Sounds zur gleichen Zeit abgespielt werden können.

Zum Abspielen benötigen wir nur eine einzige API-Funktion. Wir unterscheiden jedoch wieder nach der Definition für VB 3.0 bzw. VB 4.0 - 16 Bit und VB 4.0 - 32 Bit oder höher. Bitte erstellen Sie auch hierfür ein neues Codemodul. Vielleicht glauben Sie, dass es Platzverschwendung ist, aber so lassen sich einzelne Funktionsgruppen (Sprites, Midi, Sound) einfach in anderen Spielen und Programmen einsetzen.

VisualBasic 3.0 / VisualBasic 4.0 16-Bit:

Public Declare Function sndPlaySound Lib "MMSystem" _
      (ByVal Datei As Any, ByVal Hintergrund As Integer) As Integer
VisualBasic 4.0 32-Bit / VisualBasic 5.0:
Public Declare Function sndPlaySound Lib "Winmm" Alias _
      "sndPlaySoundA" (ByVal Datei As Any, ByVal _
      Hingergrund As Long) As Long
Auch hier müssen die Deklarationen wieder in einer Zeile geschrieben werden.

Nachdem Sie nun die Funktion eingebunden haben, sollten wir noch Routinen schreiben, die die Benutzung vereinfachen. sndPlaySound erwartet als Parameter die Sounddatei und einen Wert. Dieser gibt an, ob die Datei im Hintergrund abgespielt werden soll (1) oder ob das Programm warten soll, bis die Datei vollständig abgespielt ist (0).

Abspielen einer WAVE-Datei im Vordergrund:
Private Sub WAV_PLAY(DATEI As String)
  sndPlaySound DATEI, 0
End Sub
Abspielen einer WAVE-Datei im Hintergrund:
Private Sub WAV_PLAYBACK(DATEI As String)
  sndPlaySound DATEI, 1
End Sub

Der Aufruf dieser Funktion ist sehr einfach, wie man schon aus der Definition sieht. Um eine WAVE-Datei abzuspielen reicht der Befehl WAVE_PLAYBACK <Datei>.

Jetzt sollten Sie ein Projekt haben, dass die Hauptforum (der Sprite-Test), das Sprite-Codemodul, das Wave-Codemodul, das MIDI-Codemodul und die MEDIA-Form umfasst.

Nun sollten wir noch die neuen Funktionen testen. Erstellen Sie dazu die FORM_LOAD-Prozedur im Hauptfenster:

Sub Form_Load
  WAVE_PLAYBACK "TEST.WAV"
  MIDI_PLAYBACK "TEST.MID"
End Sub

Setzen Sie oben bitte die richtigen Dateinamen ein. Sie können beliebige WAVE- und MIDI-Dateien auswählen. Wenn Sie nun das Programm starten, hören Sie die WAVE-Datei und die MIDI-Datei. Während die Dateien abgespielt werden, können Sie schon weitermachen und die Maus wie bisher über das Display-Bildfeld schieben, da die Sounddateien alle im Hintergrund laufen.

So ähnlich werden wir auch bei dem Spiel vorgehen, zu dem wir nun endlich kommen.
 
  1.8 Bilder mit Nummern [ Top ]

Wie sie vielleicht aus dem Grundkurs noch wissen, sind auch mehrere Steuerelemente mit dem gleichen Namen in einer Form möglich. In diesem Fall werden die Steuerelemente automatisch mit einem Index versehen. Haben wir also mehrere Bildfelder mit dem Namen "BITMAP", so trägt das erste den Index 0, das zweite den Index 1 usw.

Im späteren Programm kann man auf diese Steuerelemente zugreifen, indem man den Namen gefolgt vom Index in Klammern benutzt:

BITMAP(1).Visible = 0

Dieses Verfahren verwenden wir auch in dem folgenden Spiel. So können wir (fast) beliebig viele Bilder in der Form haben, auf die wir ganz einfach zugreifen können. Auch eine Animation fällt dadurch viel leichter. Wir brauchen nur den Index des Startbildes und die Anzahl der Frames, und schon können wir auf das entsprechende Einzelbild zugreifen.

 
  1.9 SPACE JOURNEY [ Top ]

Nun kommen wir endlich zu dem richtigen Spiel. Zuerst müssen wir uns einmal überlegen, worum es im Spiel geht und was es können soll. Hier einige Stichpunkte:

 
Der Spieler steuert einen Kampfjet durch den Weltraum
 
Dabei fliegen ihm Asteroiden entgegen, die er mit seinem Laser zerstören kann
 
Die Asteroiden sollen zufällig erscheinen. Der Spieler weiß also nicht, was auf ihn zukommt
  Wenn er einen Asteroiden berührt wird dieser zerstört, der Spieler verliert aber Energie
 
Hat der Spiele sein ganze Energie verloren, so explodiert sein Raumschiff
 
Der Spieler kann nicht dauerhaft feuern. Die Feuerkraft lädt sich selbständig wieder auf.
 
Wenn der Spieler schießt, so verliert er Punkte. Zerstört er einen Asteroiden, so bekommt er dafür Punkte

Um dies alles zu bewerkstelligen, brauchen wir nun noch einen Plan, wie wir das Programm aufbauen. Da es sehr viele Möglichkeiten gibt, das oben skizzierte Spiel zu realisieren, werde ich Ihnen den Plan vorgeben. Diese Methode hat den Vorteil, dass sie sich auch leicht für andere Zwecke einsetzen lässt.

Plan zum Spiel
 1. Prüfen, ob Musik noch läuft
 2. Hintergrund zeichnen
 3. Feuerkraft des Spielers erhöhen
 4. Prüfen, ob Tasten gedrückt wurden -> Tasten speichern
 5.

Alle Objekte durchgehen:

a. Objekt bewegen
b. Kollision prüfen
c. Zeichnen
  6. Feuerkraft & Energie anzeigen
  7. Punkte anzeigen
  8. Und wieder zu Punkt 1.
 
  1.10 Objekte einmal anders [ Top ]

Alle Spielobjekte (also NICHT die Bildfelder, Knöpfe o.ä.!!), die sich auf dem Bildschirm, tummeln werden in einem Datenfeld als einzelner Eintrag gespeichert. Dieses Datenfeld enthält sowohl die Koordinaten als auch Energiezähler und andere wichtige Werte der Objekte. Dieses Verfahren hat den großen Vorteil, dass das ganze Geschehen systematisch kontrolliert werden kann. Selbst der Flieger des Spielers wird als Objekt gespeichert.

Das ganze Spiel läuft eigentlich in einer FOR-Schleife ab. Diese Schleife geht das komplette Datenfeld durch. Bei jedem Objekt, also jedem Eintrag des Datenfeldes, wird geprüft, ob das Objekt benutzt wird. Ist dies der Fall, so wird das Objekt bewegt, ggf. wird auf eine Kollision mit einem anderen Objekt geprüft und das Objekt wird gezeichnet. Ist das Objekt unbenutzt, so wird per Zufall entschieden, ob ein neuer Asteroid o.ä. erstellt werden soll. Die Einzelheiten werden wir beim Schreiben der Schleife ausarbeiten.

Es gibt mehrere Möglichkeiten, ein solches Datenfeld zu erstellen. Normalerweise würde man einen eigenen Datentyp erstellen, der alle nötigen Variablen enthält. Aus diesem Typ erstellt man nun das Datenfeld unten:

Type Object_Type
  X As Integer
  Y As Integer
  G As Integer
  E As Integer
End Type


  '...
  '...

Dim Object(100) As Object_Type

Das bedeutete allerdings, dass wir wieder ein weiteres Codemodul brauchen, da Visual Basic (zumindest Version 3.0) keine Typen in Formen definieren kann. Wir benutzen aus diesem Grund ein etwas anderes Verfahren:

Dim ObjectX(100)
Dim ObjectY(100)
Dim ObjectG(100)
Dim ObjectE(100)
'...

Jetzt überlegen wir uns, welche Werte ein Objekt benötigt, damit wir entsprechende Datenfelder erstellen können. Wichtig ist natürlich die Position und die Geschwindigkeit. Die Position halten wir mit den Feldern ObjectX und ObjectY fest. Für die Geschwindigkeit brauchen wir nur das Datenfeld ObjectG. Dieses Datenfeld gibt die X-Geschwindigkeit an. Eine Y-Geschwindigkeit benötigen wir nicht, da die meisten Objekte sich nur von rechts nach links bewegen. Die Bewegung des Spielerraumschiffs werden wir anders lösen - aber dazu später mehr.

Wir brauchen auch noch das Datenfeld ObjectE, das die Energie des Objekts angibt. Die Datenfelder ObjectA und ObjectAC sind Animationszähler. Wir benötigen diese, damit jedes Objekt "weiß", bei welcher Animationsphase es stehen geblieben ist. Und zu guter Letzt auch das Datenfeld ObjectT. Dieses Datenfeld enthält das wichtigste: Den Objekttyp. Dieses Datenfeld gibt an, ob das Objekt nun ein Jet, ein Asteroid oder ein Laserschuss ist.

Nun wissen wir, wie wir die Objekte verwalten und speichern. Jetzt müssen wir noch eine einfache Möglichkeit finden, die Objekte zu bewegen und zu zeichnen. Dazu eignet sich ein Select-Case-Block:

For i = 0 To 100

  Select Case ObjectT(i)
    Case 0: '...
            '...


    Case 1: '...
            '...

  End Select

Next i

Diese Struktur wird auch im späteren Spiel Anwendung finden. In jedem Case-Abschnitt folgen die spezifischen Anweisungen für jeden Typ von Spielobjekt.

 
  1.11 "Abgespacte" Grafiken [ Top ]

Die Grundlage eines jeden Spiels sind die Grafiken und die Sounds. Wie ich Ihnen schon gesagt habe, finden Sie alle nötigen Dateien in dem Archiv dieses Kurses. Das Spiel selbst ich recht einfach aufgebaut, wie Sie aus dem vorhergehenden Plan wissen. Der Spieler bewegt einen Jet durch ein Asteroidenfeld. Er muss den Gesteinsbrocken ausweichen oder sie mit dem Bordlaser abschießen.

Daraus ergibt sich auch schon, welche Sprites wir brauchen: Den Jet, einen Asteroiden, einen Laserschuss, einen explodierenden Asteroiden und einen explodierenden Jet, falls der Spieler seine Aufgabe nicht erfüllt ;-)

Die richtigen Sprites finden Sie im Unterverzeichnis BIT, die Masken in MASKE. Hier eine Aufstellung der Dateien:

 Datei Beschreibung Index
 JET1.BMP Jet - Frame 1  0
 JET2.BMP Jet - Frame 2  1
 JET1E.BMP Jet explodiert - Frame 1  2
 JET2E.BMP Jet explodiert - Frame 2  3
 JET3E.BMP Jet explodiert - Frame 3  4
 JET4E.BMP Jet explodiert - Frame 4  5
 JET5E.BMP Jet explodiert - Frame 5  6
 LASER.BMP Laserschuss - Frame 1  7
 ROCK1.BMP Asteroid - Frame 1  8
 ROCK2.BMP Asteroid - Frame 2  9
 ROCK3.BMP Asteroid - Frame 3 10
 ROCK4.BMP Asteroid - Frame 4 11
 ROCK1E.BMP Asteroid explodiert - Frame 1 12
 ROCK2E.BMP Asteroid explodiert - Frame 2 13
 ROCK3E.BMP Asteroid explodiert - Frame 3 14
 ROCK4E.BMP Asteroid explodiert - Frame 4 15

Wir haben ja vorher festgelegt, dass alle Sprites in den Steuerelementfeldern BITMAP und MASKE gespeichert werden. Der Eintrag "Index" gibt an, in welches Bildfeld die Dateien geladen werden sollen. Hier ein Beispiel: Bei dem Objekt BITMAP(0) laden Sie das Bild "BIT\JET1.BMP" und bei dem Objekt MASKE(0) laden Sie "MASKE\JET1.BMP". Die Steuerelementfelder BITMAP und MASKE haben also die Indizes von 0 bis 15.

Wie in dem Demoprogramm benötigen wir noch das Bildfeld DISPLAY. Da wir auch einen scrollenden Hintergrund wünschen, ist das Bildfeld HGRUND auch wieder mit von der Partie. Das Bild, was dort hinein kommt heißt "BIT\HGRUND.BMP".

 
  1.12 Tastatur unter Kontrolle [ Top ]

Die Bilder sind jetzt einsatzbereit. Um die Sounds müssen wir uns sowieso erst später kümmern, wenn die Hauptschleife geschrieben wird. Jetzt steht allerdings ein ganz anderes Problem an: Die Steuerung

Da wir ein Actionspiel programmieren, ist eine Steuerung mit der Maus nicht empfehlenswert. Wir brauchen dazu die Tastatur. Der Benutzer soll natürlich nicht auf Knöpfe klicken, sondern einfach nur die Cursortasten benutzen, um den Jet zu steuern. Dafür benutzen wir die KeyDown- und KeyUp-Ereignisse. KeyDown tritt ein, wenn der Benutzer eine Taste drückt, KeyUp tritt ein, wenn der Benutzer eine Taste wieder loslässt. Dadurch können wir einen Mechanismus entwickeln, der angibt, ob eine bestimmte Taste im Augenblick gedrückt ist oder nicht. Schließlich soll der Benutzer ja auch zwei Aktionen zu gleichen Zeit durchführen können, wie z.B. Fliegen und Schießen.

Geben Sie folgende Zeile im Deklarationsteil der Form ein:
Dim Gedrückt(255)
Hier nun die Ereignisprozeduren:
Private Sub DISPLAY_KeyDown(KeyCode As Integer, Shift As Integer)
  If KeyCode < 256 Then Gedrückt(KeyCode) = 1
End Sub

Private Sub
DISPLAY_KeyUp(KeyCode As Integer, Shift As Integer)
  If KeyCode < 256 Then Gedrückt(KeyCode) = 0
End Sub

Wenn KeyDown eintritt, wird der Eintrag mit dem Index des Codes auf 1 gesetzt. Tritt KeyUp ein, so wird der Eintrag wieder auf 0 zurückgesetzt. Wenn wir jetzt wissen wollen, ob eine Taste mit dem Code X gedrückt ist, so brauchen wir nur Gedrückt(x) überprüfen. Hier eine Übersicht von wichtigen KeyCodes:

Code  Taste
17  STRG
27  ESC
37  Links
38  Hoch
39  Rechts
40  Runter

Diese Art der Tastatursteuerung ist sehr flexibel. Sie können eine solche Steuerung leicht in Ihre eigenen Programm einbauen.

Wichtig ist aber, dass das Objekt den Focus besitzt. Ansonsten gehen die Key-Ereignisse nicht an das richtige Objekt und die Steuerung läuft nicht. Um dies zu erreichen, sollten Sie die TabIndex-Eigenschaft des Objekts auf 0 setzen. Das bedeutet, das Objekt enthält nach dem Aufruf der Form den Focus. Wenn Sie die Codes von anderen Tasten herausfinden wollen, so können Sie dies dadurch erreichen, dass Sie den Code bei jedem KeyDown-Ereignis mit Hilfe einer MessageBox anzeigen lassen.

 
  1.13 Startvorbereitungen [ Top ]

Jetzt kommen die letzten Vorbereitungen für das Spiel: Wir brauchen eine Prozedur, die sehr einfach einen Sprite auf den Bildschirm bringen kann. Nachdem wir schon das Demo-Programm fertig haben dürfte das kein Problem sein:

Private Sub ZeichneSprite(Nr As Long, x As Long, Y As Long)
  Dim sw As Long, sh As Long

   sw = BITMAP(Nr).ScaleWidth
   sh = BITMAP(Nr).ScaleHeight
   BitBlt DISPLAY.hDC, x - sw / 2, Y - sh / 2, sw, sh, _
          MASKE(Nr).hDC, 0, 0, BIT_AND

   BitBlt DISPLAY.hDC, x - sw / 2, Y - sh / 2, sw, sh, _
          BITMAP(Nr).hDC, 0, 0, BIT_INVERT
End Sub

Diese Prozedur hat allerdings eine Verbesserung gegenüber dem "Vorgänger" erfahren: Am Anfang wird die Breite und Höhe des Sprites geholt. Der zu zeichnende Sprite wird anschließend um die X-/Y-Koordinaten zentriert. Dies wird dadurch erreicht, dass von der X-Position die Hälfte der Breite abgezogen wird und der Sprite an diese Stelle kopiert wird. Gleiches gilt für die Y-Koordinate.

Um uns einen Startknopf oder gar ein Hauptmenü zu ersparen soll das Spiel erst einmal direkt nach dem Start ausgeführt werden. Dazu benutzen wir das Form_Load-Ereignis:

Private Sub Form_Load()
  Me.Show
  Call SpielStart
End Sub

Warum "Me.Show"? Ganz einfach: Da das eigentliche Spiel fast vollständig in einem Unterprogramm abläuft, sorgen wir dafür, dass die Form angezeigt wird, bevor das Spiel startet. Denn was bringt es, wenn das Spiel läuft, Sie aber nichts sehen? Also zeigen wir die Form sofort an. Anschließend wird die Prozedur SpielStart aufgerufen - und die schreiben wir jetzt.

 
  1.14 Der Countdown läuft [ Top ]

Jetzt bekommt die Form des Spiels noch den letzen Schliff:

Unter das Bildfeld Display setzen Sie zwei weitere Bildfelder. Diese zeigen später im Spiel die Energie und die Schusskraft an. Nennen Sie das obere Bildfeld ENERGY und das untere FKRAFT. Setzen Sie beim oberen die Hintergrundfarbe auf ein dunkles Rot und beim unteren auf ein dunkles Cyan. Darunter kommt ein Bezeichnungsfeld mit dem Namen PUNKTEANZ. Setzen Sie Caption auf "0", die Hintergrundfarbe auf dunkelgrün und die Vordergrundfarbe auf ein helles Gelb. Setzen Sie außerdem BorderStyle auf 1.

Das Design der Form ist nun abgeschlossen. Die Bildfelder mit den Sprites sind hier nicht zu sehen, da ich diese im unteren Teil der Form platziert habe. Denken Sie daran, dass Sie alle Bildfelder mit Sprites, mit Masken und mit dem Hintergrund unsichtbar machen. Auch sollten Sie darauf achten, dass das Display nicht (!) höher ist als der Hintergrund. In diesem Fall würde nämlich im späteren Spiel eine Lücke unter dem scrollenden Hintergrund entstehen.

Der Rahmen ist fertig - jetzt kommt das Spiel. Also los!
Private Sub SpielStart()
  Dim ObjectX(20) As Long
  Dim
ObjectY(20) As Long
  Dim
ObjectT(20) As Long
  Dim
ObjectE(20) As Long
  Dim
ObjectG(20) As Long
  Dim
ObjectA(20) As Long
  Dim
ObjectAC(20) As Long

  Dim
HGrundX As Long, Punkte As Long
  Dim
FeuerKraft As Long, Feuer As Long
  Dim
Pfad As String, w As Long, h As Long
  Dim
m As Long, i As Long, i2 As Long
  Dim
a As Long, x As Long

  Pfad = App.Path & "\"
  MIDI_PLAY Pfad & "MUSIK.MID"

  w = DISPLAY.ScaleWidth
  h = DISPLAY.ScaleHeight

Als erstes werden die Datenfelder erstellt, die die Informationen über die Spielobjekte aufnehmen. Anschließend wird die Variable Pfad auf den Pfad gesetzt, indem sich die Dateien befinden. Hier müssen Sie den Pfad einsetzen, der zu den entsprechenden Sounddateien auf Ihrer Festplatte führt. Vergessen Sie nicht, dass am Ende auf jeden Fall einen Backslash steht! Sonst kommt es zu Fehlern. Später, wenn Sie das Spiel fertig haben und kompilieren wollen, sollten Sie Pfad$ auf " " setzen, damit die Dateien im aktuellen Verzeichnis gesucht werden. Dann brauchen sich die Sounddateien nur im gleichen Verzeichnis mit dem Programm befinden, und sie werden abgespielt. Sollte dabei etwas nicht funktionieren, so ist das auch kein Beinbruch, da die Soundroutinen gegen alle Arten von Fehler "immun" sind. Schlimmstenfalls kommt der Spieler nicht in den Genuss der akustischen Untermalung ;-)

Jetzt müssen wir das Spielobjekt des Spielers initialisieren. Dazu legen wir jetzt fest, welche Typennummer (ObjectT(x)) für welches Objekt steht:

 ObjecT(x) Bedeutung
  1  Raumschiff
  2  Asteroid
  3  Laserschuss
  4  Explodierender Asteroid
  5  Explodierendes Raumschiff
Start:

  ObjectX(0) = w / 2
  ObjectY(0) = h / 2
  ObjectT(0) = 1
  ObjectE(0) = 200
  ObjectG(0) = 0
  ObjectA(0) = 0

Bevor wir dies tun setzen wir allerdings noch die Marke Start in den Quellcode. Diese benötigen wir, wenn das Raumschiff des Spielers zerstört wird und er von Vorne anfangen muss. Die Startposition des Spielers ist genau in der Mitte des Bildschirm. Der Typ ist 0, wie wir aus der Tabelle oben ersehen können. Die Startenergie des Spielers beträgt 200 Einheiten. Das Raumschiff benötigt keine Geschwindigkeitsangabe, da es direkt über die Tastatur gesteuert wird. Zum Schluss wird noch die Animationsphase auf 0 gesetzt.

  For m = 1 To 20
     ObjectT(m) = 0
  Next m

Jetzt werden alle verbleibenden Objekte (beachten Sie, das wir diesmal nur 21 Objekte haben, einschließlich dem des Spielers) auf Typ 0 gesetzt, was soviel bedeut wie: Kein Objekt

  HGrundX = 0
  Punkte = 0
  FeuerKraft = 1000

Vor der Hauptschleife noch drei wichtige Werte: Die Variable HGrundX gibt an, wie weit der Hintergrund schon gescrollt ist. Die Bedeutung der Variable Punkte muss ich wohl nicht erklären, oder? FeuerKraft gibt an, wie stark der Bordlaser des Spielers aufgeladen ist. 1000 ist hier der Maximalwert. Näheres dazu, wenn wir zur Schuss-Steuerung kommen.

Do
  If
MIDI_PLAYING() = 0 Then
    MIDI_STOP
    MIDI_CONTINUE
  End If

Zuerst wird bei jedem Schleifendurchgang geprüft, ob die MIDI-Musik noch läuft. Wenn nein, wird die Datei wieder von vorne abgespielt.

HGrundX = HGrundX + 1

If
HGrundX > (320 * 4) Then
  HGrundX = HGrundX - (320 * 4)
End If

BitBlt DISPLAY.hDC, 0, 0, w, h, HGRUND.hDC, HGrundX, 0, BIT_COPY
BitBlt DISPLAY.hDC, (320 * 4) - HGrundX, 0, w, h, _
       HGRUND.hDC, 0, 0, BIT_COPY)

Danach wird der Hintergrund gezeichnet. Dazu wird die Variable HGrundX erst einmal um den Wert 1 erhöht, damit sich der Hintergrund auch weiterbewegt. Hat sich der Hintergrund so weit bewegt, dass er komplett aus dem Bildschirm verschwunden ist, so wird die Variable HGrundX wieder auf 0 gesetzt.

 

Es wird als erstes ein Ausschnitt in der Größe des Bildfelds DISPLAY (W * H) aus dem Bildfeld HGRUND kopiert. Das ist recht leicht zu bewerkstelligen und zu verstehen:

Je weiter der Spieler fliegt, desto weiter verschiebt sich der Bereich, der kopiert wird. Und wozu nun der zweite Kopiervorgang? Wenn der Spieler am Ende des Levels angekommen ist, haben wir die Situation, dass der Bereich den wir kopieren sollen zum Teil außerhalb des Hintergrundpuffers liegt! Wenn wir keinen weiteren Kopiervorgang einbauen, erhalten wir dann an der Stelle "Müll". Deshalb wird der Anfang des Hintergrundpuffers an die Stelle kopiert, an der der erste aufhört. 

Um diese Koordinate zu bekommen, nehmen wir die Breite des Hintergrunds (in unserem Fall ist der Hintergrund 320*4 Pixel breit - das können Sie aber natürlich ändern) und ziehen die Position HGrundX davon ab. Die meiste Zeit wird dieser Bereich außerhalb des Displays liegen, jedoch beeinträchtigt das die Leistung des Spiels kaum, da die API-Funktionen "intelligent" genug sind, um nicht unnötig viel zu kopieren. Wenn Ihnen hierbei noch Zweifel kommen, so probieren Sie mal im fertigen Spiel, den zweiten Kopiervorgang herauszunehmen. Sie werden sehen, dass es ohne diesen nicht geht.

If FeuerKraft < 1000 Then
  If
FeuerKraft < 100 Then
    FeuerKraft = FeuerKraft + 5
    If FeuerKraft > 100 Then FeuerKraft = 100
  Else
    FeuerKraft = FeuerKraft + 2
  End If
End If

Da sich der Laser des Spielers automatisch regeneriert, wird an dieser Stelle die Feuerkraft des Spielers erhöht. Erst wird nachgesehen, ob der Spieler nicht schon volle Feuerkraft erreicht hat. Wenn nicht, dann wird geprüft, ob sich die Feuerkraft unter 100 Punkte befindet. Ist dies der Fall, so lädt der Laser schneller. Ab 100 Punkten wird dann nur noch mit 2 Einheiten pro Bildschirmaufbau geladen. Dadurch wird erreicht, dass Der Spieler, selbst wenn die Feuerkraft auf 0 ist, in kurzer Zeit wenigstens einen Schuss abgeben kann. Will der Spieler den Laser so stark aufladen lassen, dass er einen Feuerstoß abgeben kann, muss er viel länger warten. Eine solche Aufteilung ist natürlich kein Muss, macht aber das Spiel interessanter, da die Schüsse nicht immer gleichmäßig kommen.

Display.SetFocus
DoEvents

Mit der Methode SetFocus wird der Focus bei jedem Schleifendurchgang auf das Display-Bildfeld gesetzt. Damit soll verhindert werden, dass der Spieler aus Versehen zu einem anderen Objekt wechselt und die Kontrolle über sein Schiff verliert. Anschließend wird der Befehl DoEvents aufgerufen. Dieser sorgt dafür, dass auftretende Ereignisse verarbeitet werden. Ohne diesen Befehl hätte Windows keine Möglichkeit, das KeyDown- oder KeyUp-Ereignis an unser Bildfeld zu senden und die Steuerung würde nicht funktionieren.

Jetzt kommen wir zu der Schleife, die die Objekte durchgeht:
For i = 0 To 20
  ObjectX(i) = ObjectX(i) + ObjectG(i)

  Select Case ObjectT(i)

Die Schleife geht die Einträge 0 bis 20 durch und die Nummer wird in der Variable "i" gespeichert. Anschließend wir das aktuelle Objekt bewegt. Hier spielt es überhaupt keine Rolle, ob das Objekt benutzt wird oder nicht. Ein unbenutztes Objekt ist ja so oder so nicht zu sehen und bei einem benutzen Objekt kann uns die automatische Bewegung ja nur recht sein. Jetzt kommt der wichtigste Programmteil: Der Select-Case-Block. Aus dem Plan von eben wissen Sie ja, dass jedes Objekt eine Typennummer hat, die es identifiziert. In dem Select-Case-Block wird jeder Typ gesteuert. Zum Beispiel wird im Case 1-Abschnitt das Raumschiff des Spielers gesteuert.

 Case 0
   ObjectAC(i) = 0
   ObjectA(i) = 0

   If Feuer > 0 And FeuerKraft > 0 Then
     Feuer = 0
     FeuerKraft = FeuerKraft - 50
     ObjectX(i) = ObjectX(0) + 2
     ObjectY(i) = ObjectY(0) + 8
     ObjectT(i) = 3
     ObjectG(i) = 4
     ObjectE(i) = 3
     WAV_PLAYBACK Pfad$ + "LASER.WAV"
     Punkte = Punkte - 1

Es folgt der erste Case-Abschnitt. Dieser behandelt alle Spielobjekte mit dem Typ 0, also alle Objekte, die nicht benutzt werden. Zuerst wird hier geprüft, ob der Spieler auf die Feuer-Taste gedrückt hat. Dies wird mit der Variable Feuer geprüft. Hat diese Variable einen Wert größer als 0, so hat der Spieler die Feuertaste gedrückt. Wenn außerdem die Feuerkraft größer oder gleich 50 ist, so kann ein Schuss abgegeben werden.

In diesem Fall wird als erstes die Variable Feuer wieder auf Null gesetzt, damit erst im nächsten Schleifendurchgang wieder gefeuert werden kann. Die Feuerkraft wird um 50 Einheiten verringert. Dies ist die Kraft, die für einen Schuss benötigt wird. Nun wird das aktuelle Objekt (das ja vorher unbenutzt war) als Schuss benutzt. Dazu wird es auf die Koordinaten des Spielers gesetzt. Allerdings wird bei der X-Koordinate der Wert 2, und bei der Y-Koordinate der Wert 8 hinzuaddiert. Dies geschieht, damit es so aussieht, als würde der Laserschuss wirklich aus den Kanonen des Raumschiffs kommen. Es ist also nicht mehr, als eine kleine Verschönerung. Das Spiel würde auch ohne diese Verzierung laufen. Nun wird der Typ des Objekts auf 3 gesetzt. 

Wie Sie aus der Tabelle der Objekttypen wissen, steht in diesem Spiel der Wert 3 für einen Laserschuss. Die Geschwindigkeit eines Laserschusses beträgt vier Pixel pro Bildschirmaufbau und die Energie drei Einheiten. Nachdem diese Einstellungen getroffen sind, wird noch eine Sounddatei ausgegeben, damit der Spieler auch hört, dass er gefeuert hat. Und zum Schluss wird ihm noch ein Punkt für einen Schuss abgezogen. Diese Gemeinheit habe ich eingebaut, damit der Spieler nicht einfach immer die Feuertaste gedrückt hält, um sich seinen Weg zu bahnen.

Else
  a = Fix(Rnd * 4000)
  If a < 2 Then
    ObjectX(i) = w + 40
    ObjectY(i) = Rnd * h
    ObjectT(i) = 2
    ObjectG(i) = -2 - Rnd * 3
    ObjectE(i) = 15
  End If
End If

Wenn kein Schuss abgegeben werden kann oder soll, wird der andere Teil des IF-Blocks ausgeführt. Hier wird per Zufall entschieden, ob ein neuer Asteroid erstellt werden soll. Dafür wird ein Wert zwischen 0 und 4000 ausgelost. Ist dieser Wert kleiner als 2, so wird ein Asteroid erstellt. Das mag Ihnen vielleicht gering vorkommen, allerdings müssen Sie überlegen, dass dies pro unbenutzten Objekt und pro Bildschirmaufbau gilt. 

Sie werden schon sehen, wie viele Asteroiden herumfliegen werden. Wenn die Entscheidung zu Gunsten eines neuen Asteroiden erfolgt ist, wird dieser erstellt. Seine X-Position liegt rechts außerhalb vom Bildfeld. Seine Y-Position wird ebenfalls per Zufall festgelegt. Dazu wird ein Wert zwischen 0 und der Höhe des Spielfelds benutzt. Der Typ eines Asteroiden hat die Nummer zwei - also setzen wir sie auch. Die Geschwindigkeit beträgt mindestens -2 Pixel pro Sekunde und maximal -5 Pixel pro Sekunde. Auch hier wird der Zufallsgenerator benutzt, damit die Asteroiden nicht in Reihe und Glied fliegen. Ein Gesteinsbrocken hat 15 Energieeinheiten und muss somit 5 mal von einem Laserschuss (Energie = 3) getroffen werden, bis seine Energie gleich 0 ist, und er explodiert.

 Case 1
   ObjectAC(i) = ObjectAC(i) + 1

   If ObjectAC(i) > 10 Then
     ObjectA(i) = ObjectA(i) + 1
     ObjectAC(i) = 0
   End If

   If
ObjectA(i) > 1 Then
     ObjectA(i) = 0
   End If

Wenn der Jet des Spielers gezeichnet wird, so ist als erstes die Animation an der Reihe. Jedes Objekt hat ja zwei Animationszähler. ObjectA(x) enthält die Animationsphase und ObjectAC(x) ist ein Verzögerungszähler. Das Prinzip dieser Zähler sieht so aus: Bei jedem Durchgang wird ObjectAC erhöht. Wird ein bestimmter Wert (in diesem Fall 10) überschritten, so wir wird ObjectAC auf 0 zurückgesetzt und ObjectA um eins erhöht. Überschreitet ObjectA den zulässigen Maximalwert, so wird auch diese Variable auf 0 (oder auf einen anderen beliebigen Startwert) zurückgesetzt. Dank diesem Verfahren wechselt die Animation nicht bei jeden Bildschirmaufbau, sondern nur bei jedem 10.

ZeichneSprite ObjectA(i), ObjectX(i), ObjectY(i))

If Gedrückt(37) And ObjectX(i) > 0 Then
  ObjectX(i) = ObjectX(i) - 3
End If

If
Gedrückt(39) And ObjectX(i) < w Then
  ObjectX(i) = ObjectX(i) + 2
End If

If
Gedrückt(38) And ObjectY(i) > 0 Then
  ObjectY(i) = ObjectY(i) - 2
End If

If
Gedrückt(40) And ObjectY(i) < h Then
  ObjectY(i) = ObjectY(i) + 2
End If

If
Gedrückt(32) Then Feuer = 1
If Gedrückt(27) Then End
If
ObjectE(i) <= 0 Then
  ObjectT(i) = 5
  ObjectAC(i) = 0
  ObjectA(i) = 0
  WAV_PLAYBACK Pfad$ + "EXPLOSIO.WAV"
End If

Nachdem die Animation geändert wurde, wird das Raumschiff gezeichnet. Da der Animationszähler ObjectA() zwischen den Wert 0 und 1 schwankt, können wir hiermit auch gleich die Nummer des Sprites ermitteln, der gezeichnet werden soll. Da das Raumschiff des Spielers in den Bildfelder BITMAP(0) und BITMAP(1) liegt, können wir den Wert vom Animationszähler übernehmen. Würde das Raumschiff in den Feldern BITMAP(5) und BITMAP(6) liegen, so würden wir an dieser Stelle einfach 5 addieren. Normalerweise sollte man zwar das Raumschiff erst zeichnen, nachdem es bewegt wurde, aber der Unterschied beträgt höchstens 1 bis 2 Pixel und so spielt es keine Rolle.

Aber nun zur Steuerung an sich: Dazu werden die für die Steuerung wichtigen Tasten überprüft. Ist zum Beispiel die Pfeiltaste nach links gedrückt und ist der Spieler noch vom linken Rand entfernt, so wird die X-Position um 3 verringert. Ähnliches gilt auch für die anderen Tasten. Ist die Pfeiltaste nach rechts gedrückt und der Spieler befindet sich vor dem rechten Rand, so bewegt sich das Raumschiff um zwei Einheiten vorwärts. Die Bewegung nach links ist übrigens extra schneller als in die anderen Richtungen, damit der Spieler besser vor einem Brocken zurückweichen kann. Aber natürlich können Sie das auch ändern, wenn Sie wollen. Ist die Steuerungs-Taste (STRG oder CTRL) gedrückt, so wird die Variable Feuer auf 1 gesetzt. Wie Sie wissen hat das zur Folge, dass das nächste freie Objekt bei genügender Feuerkraft in einen Laserschuss umgewandelt wird.

Wird die Taste Escape (ESC) gedrückt, so wird das Spiel mit dem Befehl END abgebrochen. Das Spiel lässt sich nämlich auf keinem anderen Weg beenden, wenn es kompiliert wurde. Der Benutzer kann die Form zwar schließen, allerdings erscheint Sie kurz darauf neu. Das liegt daran, dass die Prozedur SpielStart aus der Form_Load-Ereignisprozedur aufgerufen wird. Aber zerbrechen Sie sich darüber nicht den Kopf. Denken Sie nur daran, dass man Ihr Spiel per Tastendruck beenden kann. Und Zum Schluss wird geprüft, ob die Energie des Spielers gleich 0 ist. In diesem Fall wird das Objekt in ein Objekt von Typ 5 (explodierendes Raumschiff) umgewandelt, die Animationszähler werden zurückgesetzt und eine Sounddatei wird ausgegeben, die dieses dramatische Ereignis untermalen soll.

Jetzt kommen wir zu den Asteroiden. Auch hier wird mit Verzögerungen und Animationszählern gearbeitet. Der Animationszähler enthält hier allerdings Werte zwischen 0 und 3, da ein Asteroid vier Frames hat.

Case 2
   ObjectAC(i) = ObjectAC(i) + 1

   If ObjectAC(i) > 10 Then
     ObjectA(i) = ObjectA(i) + 1
     ObjectAC(i) = 0
   End If

   If
ObjectA(i) > 3 Then
     ObjectA(i) = 0
   End If

   ZeichneSprite (ObjectA(i) + 8, ObjectX(i), ObjectY(i)

   If ObjectX(i) < -30 Then ObjectT(i) = 0
     If ObjectE(i) <= 0 Then
       ObjectT(i) = 4
       ObjectA(i) = 0
       ObjectAC(i) = 0
       WAV_PLAYBACK Pfad$ + "EXPLOSIO.WAV"
       Punkte = Punkte + 20
     End If

Nun wird das Sprite gezeichnet. Hier muss zum Animationszähler der Wert 8 addiert werden, da der erste Frame der Animation sich in Bildfeld acht befindet. Wenn der Asteroid links aus dem Bild verschwindet, also seine X-Koordinate in den Minusbereich geht, wird er gelöscht, indem der Objekttyp auf 0 gesetzt wird. Wenn die Energie des Asteroiden gleich 0 ist, so wird er in Typ 4, einen explodierenden Asteroiden, umgewandelt. Dabei wird genauso vorgegangen wie beim Raumschiff des Spielers: Der Typ wird gesetzt, die Animationszähler werden auf 0 zurückgesetzt und eine WAVE-Datei wird abgespielt. Zusätzlich werden hier die Punkte des Spielers um 20 erhöht. Schließlich soll er ja auch belohnt werden, wenn er einen Asteroiden vernichtet hat ;-)

Jetzt kommen wir zu einem sehr wichtigen Teil im Spiel: Der Kollisionsabfrage. Wenn der Spieler gegen einen Gesteinsbrocken "crasht", so muss dem Spieler ja auch dafür Energie abgezogen werden. Um das zu erreichen, müssen wir bei jedem Asteroiden prüfen, ob er mit dem Spieler zusammengestoßen ist. Das Prinzip einer solchen Abfrage läuft so: Es werden jeweils alle anderen Objekte durchgegangen und es wird geprüft, ob das entsprechende Objekt mit dem aktuellen kollidieren kann. Zum Beispiel sollen die Asteroiden ja nicht miteinander kollidieren. Also prüfen wir den Typ des Objekts. Ist das getan prüfen wir noch, ob der Abstand (sowohl in X- als auch in Y-Richtung) einen bestimmten Wert unterschritten hat. In dem Fall ist eine Kollision eingetreten:

For i2 = 0 To 20
  If ObjectT(i2) = 1 Then
    If
Abs(ObjectX(i2) - ObjectX(i)) < 50 Then
      If
Abs(ObjectY(i2) - ObjectY(i)) < 40 Then
        ObjectE(i2) = ObjectE(i2) - ObjectE(i)
        ObjectE(i) = 0
        WAV_PLAYBACK Pfad$ + "EXPLOSIO.WAV"
      End If
    End If
  End If
Next
i2

Es werden also wieder alle Objekte in der Schleife durchgegangen. Die aktuelle Nummer wird in der Variable I2 gespeichert. Es wird jedes Mal geprüft, ob das aktuelle Objekt den Typ 1 hat, also ob es der Jet des Spielers ist. Dann wird geprüft, ob die Entfernung der X-Koordinate vom Spielerraumschiff und der vom Asteroiden kleiner als 50 ist. In dem Fall wird auch noch geprüft, ob die Y-Entfernung kleiner als 40 ist. Wenn diese Bedingungen alle zutreffen, so hat eine Kollision stattgefunden. Dem tragen wir Rechnung, indem wir dem Spieler die Energie abziehen, die der Asteroid noch hat. Wenn der Asteroid noch keinmal von einem Laserschuss getroffen wurde, so werden hier dem Spieler 15 Energieeinheiten abgezogen. Anschließend wird die Energie des Asteroiden auf -1 gesetzt. Dadurch wird er beim nächsten Bildschirmaufbau explodieren. Eine Explosion müssen wir hier nicht extra ausgeben, da das beim nächsten Schleifendruchlauf sowieso passiert.

Sie werden sich vielleicht fragen, warum ich an dieser Stelle alle Objekte durchgehe. Schließlich wissen wir doch, dass der Spieler Objekt Nr. 0 ist. Tja, man könnte die Schleife auch weglassen, aber das bringt fast keine Geschwindigkeitsvorteile. Und außerdem wissen wir auch nicht, ob Objekt Nr. 0 immer noch ein Raumschiff ist. Wenn es mittlerweile explodiert ist, befindet sich an dieser Position eine Explosion oder vielleicht ein anderer Asteroid. Ein weiterer Grund ist, dass eine solche Kollisionsabfrage, wie wir Sie gerade besprochen haben, sehr vielseitig eingesetzt werden kann. Sie sehen sie sogar gleich wieder, wenn wir bei den Laserschüssen sind.

Noch ein Hinweis: Die minimale Entfernung der Objekte lässt sich einfach ermitteln. Angenommen der Jet des Spielers ist 20 Pixel breit und der Asteroid 60 (das stimmt zwar nicht ganz, aber es ist ja nur eine Annahme), dann beträgt der minimale Abstand zwischen dem Mittelpunkt des Jets und dem des Asteroiden 20/2 + 60 /2 Pixel. Also: 40 Pixel. Wird diese Grenze unterschritten, so überlagern Sie sich. Diese Abfrage muss dann noch für die Höhe gemacht werden. Um die Differenz zwischen zwei Werten zu errechnen, subtrahiere ich die beiden Werte voneinander (die Reihenfolge ist hier egal). Anschließend wird der Wert mit der Funktion ABS(x) in eine positive Zahl umgewandelt. Auch diese Formel ist allgemein gültig und findet oft Verwendung.

Case 3
   ZeichneSprite 7, ObjectX(i), ObjectY(i)

   If ObjectX(i) > w + 5 Then ObjectT(i) = 0
   If ObjectE(i) <= 0 Then ObjectT(i) = 0

   For i2 = 0 To 20
      If ObjectT(i2) = 2 Then
        If
Abs(ObjectX(i2) - ObjectX(i)) < 30 Then
          If
Abs(ObjectY(i2) - ObjectY(i)) < 30 Then
            ObjectE(i2) = ObjectE(i2) - ObjectE(i)
            ObjectE(i) = 0
          End If
        End If
      End If
   Next
i2

Wir behandeln die weiteren Objekt nicht mehr ganz so ausführlich. Daher kommt hier der ganze Abschnitt für den Laserschuss. Der Laserschuss hat keine Animation, folglich können wir uns Zähler und ähnliches sparen. Es wird nur Sprite Nr. 7 an die entsprechende Position gezeichnet. Wie Sie aus der Tabelle von oben wissen, ist Sprite Nr. 7 ja der Laser. Als nächstes wird überprüft, ob der Schuss den Bildschirm verlassen hat. In diesem Fall wird er einfach gelöscht. Fällt die Energie eines Schusses auf 0, so löst er sich auf. Da wir hierfür keine Animation vorgesehen haben löschen wir den Sprite einfach.

Jetzt kommt wieder die Kollisionsabfrage. Diesmal ist das Ziel nicht der Spieler (das wäre ja auch etwas gemein) sondern Asteroiden (Typ 2). Hier hat sich gegenüber der anderen Kollisionsabfrage nicht viel geändert. Nur die minimal Entfernungen haben neue Werte. Die Auswirkungen bleiben gleich. Trifft ein Laserschuss einen Asteroiden, so verschwindet der Laserschuss und dem Asteroiden wird Energie abgezogen.

Case 4
   ObjectAC(i) = ObjectAC(i) + 1

   If ObjectAC(i) > 10 Then
     ObjectA(i) = ObjectA(i) + 1
     ObjectAC(i) = 0
   End If

   If
ObjectA(i) > 3 Then
     ObjectA(i) = 0
     ObjectT(i) = 0
   Else
     ZeichneSprite(ObjectA(i) + 12, ObjectX(i), ObjectY(i)
   End If

   If
ObjectX(i) < -30 Then ObjectT(i) = 0

Nun kommen wir langsam zum Schluss. Typ 4 ist der explodierende Asteroid. Hier brauchen wir wieder einen Animationszähler. Schließlich haben wir ja nicht umsonst vier Animationsphasen gezeichnet! Ist der letzte Frame gezeichnet (also wenn der Animationszähler größer ist als 3), so wird der Zähler zurückgesetzt, und der Asteroid gelöscht. Andernfalls wird das Objekt gezeichnet. Dabei wird zum Zähler 12 addiert, da die Animation des explodierenden Asteroiden bei Sprite Nr. 12 anfängt. Beachten Sie hier, dass der Sprite NICHT gezeichnet werden darf, wenn die Animation abgelaufen ist und die Zähler zurückgesetzt wurden. Sonst würde der Spieler ein kurzes Aufblinken des ersten Frames sehen - und das wollen wir ja nicht. Verschwindet die Animation vom Bildschirm bevor die Animation abgelaufen ist, so wird das Sprite direkt gelöscht.

Case 5
   ObjectAC(i) = ObjectAC(i) + 1

   If ObjectAC(i) > 10 Then
     ObjectA(i) = ObjectA(i) + 1
     ObjectAC(i) = 0
   End If

   If
ObjectA(i) > 4 Then
     ObjectA(i) = 0
     ObjectT(i) = 0
     GoTo Start
   Else
     ZeichneSprite(ObjectA(i) + 2, ObjectX(i), ObjectY(i)
   End If

   If
ObjectX(i) < -30 Then ObjectT(i) = 0

Das explodierende Raumschiff ist genauso aufgebaut, nur dass es 5 Frames hat und so die Animation bei 4 beendet ist. Außerdem beginnt der explodierende Jet bei Sprite Nr. 2.

     End Select
Next
i

Display.Refresh

Nun haben wir alle Objekte fertig, die wir brauchen. Der Select-Case-Block wird geschlossen, genauso wie die For-Schleife. Anschließend wird das Bildfeld noch "refresht", damit wir auch den Lohn unserer Arbeit sehen. Jetzt fehlen uns noch die Instrumente:

FKRAFT.Cls
x = FKRAFT.ScaleWidth / 1000 * FeuerKraft
FKRAFT.Line (0, 0)-(x, FKRAFT.ScaleHeight), QBColor(11), BF

Fangen wir mit der Anzeige der Feuerkraft an. Erst einmal wird das Bildfeld geleert (damit die vorherige Anzeiger gelöscht wird). Da wir eine Prozentanzeige haben wollen, errechnen wir, wie weit der Balken im Verhältnis zum Bildfeld stehen muss. Dazu nehmen wir die Zeichenbreite des Bildfelds und teilen Sie durch 1000 (das ist der Maximalwert für die Schusskraft). Nun multiplizieren wir das Ganze noch mit der aktuellen Schusskraft. Das diese Formel funktioniert, kann man sich leicht überlegen:

Die volle Feuerkraft hat den Wert 1000. Und wenn wir die Balkenlänge durch 1000 teilen und mit 1000 multiplizieren, haben wir wieder die Balkenlänge heraus, und der Balken ist voll. Man kann sich das ganze natürlich auch an einer Verhältnisgleichung veranschaulichen, aber ich will Ihnen ja keine Nachhilfe in Prozentrechnung geben :)

OK, nun haben wir die Länge des Balkens und können ihn einzeichnen. Dazu verwenden wir die Line-Methode. Wir zeichnen eine Box von der linken oberen Ecke bis zu der Balkenposition und dem unteren Rand. Diese Box wird hellcyan gefüllt.

ENERGY.Cls
x = ENERGY.ScaleWidth / 200 * ObjectE(0)
ENERGY.Line (0, 0)-(x, ENERGY.ScaleHeight), QBColor(12), BF

Der Energiebalken des Spielers wird analog gezeichnet. Und wird daher nicht mehr erklärt. Er wird übrigens hellrot gefüllt.

PUNKTEANZ.Caption = Punkte

Die letzte Anzeige ist die Punktetafel. Hier setzen wir einfach die Caption-Eigenschaft auf die aktuelle Punktzahl. Das war's

   Loop
End Sub

Jetzt wird noch die Hauptschleife geschlossen. Wir sind fertig. Sie können jetzt Ihr fertig Spiel testen.

Das fertige Spiel befindet sich übrigens für Visual Basic 3.0 und 4.0 - 16 Bit im Verzeichnis "GAME16" und für 4.0 - 32 Bit oder 5.0 im Verzeichnis "GAME32".

Vielleicht haben Sie das Gefühl, dass der Aufwand für ein solches Spiel zu groß war. An dieser Stelle kann ich Ihnen versichern, je öfter Sie sich mit Spiel-Programmierung beschäftigen, desto leichter fällt es Ihnen. Dieses Demospiel habe ich (zusammen mit den Grafiken) in ca. 1 ½ Stunden geschrieben.
 
Das Beispielprojekt gibt es hier zum Download:

Download
Spielekurs_Teil1.zip
 (235,7 kB)
Downloads bisher: [ 1995 ]
 
  1.15 Houston, wir haben ein Problem [ Top ]

In diesem Kurs kam natürlich eine große Menge an Informationen auf einmal. Das liegt daran, dass ich nicht nur die Grundlagen sondern auch ein praktisches Beispiel erklären wollte. Der Umfang der folgenden Spiele-Kurse wird - so hoffe ich zumindest - etwas geringer ausfallen. Wenn Sie Probleme haben, egal ob Verständnisschwierigkeiten oder ob etwas nicht läuft, fragen Sie mich einfach per eMail:  dominik5@debitel.net

 
  1.16 Ende Teil 1 [ Top ]

Damit wäre der erste Teil des Spiele-Kurses abgeschlossen. Wenn Sie Lust haben können Sie ja probieren, einen anderen Typ von Asteroiden (vielleicht einen kleineren) in das Spiel einzubauen. Auch eine Highscore-Funktion wäre sicher nicht verkehrt.

Wenn Sie weitere Themenwünsche oder Ideen für den nächsten Aufbaukurs haben, schreiben Sie mir doch einfach eine eMail. Meine Adresse:

 dominik5@debitel.net

Wie immer freue ich mich auch über jede Art von Feedback zu dem Kurs und über Fragen zu VB oder anderen Themen. Wenn Sie Zeit haben, können Sie ja die Programmierer-Konferenz besuchen, die immer Donnerstags um 20:00 Uhr im Konferenzraum 2 von AOL stattfindet (STRG+K Konferenzen, Raum 2).

Ich hoffe, wir sehen uns dann wieder.


Startseite | VB/VBA-Tipps | Projekte | Tutorials | API-Referenz | Komponenten | Bücherecke | VB-/VBA-Forum | VB.Net-Forum | DirectX | DirectX-Forum | Foren-Archiv | VB.Net | Chat | Links | Suchen | Stichwortverzeichnis | Feedback | Impressum

Seite empfehlen Bug-Report

Letzte Aktualisierung: Dienstag, 14. Dezember 2004