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. Im zweiten Teil wurden Optimierungen vorgenommen und die Sprites erneut erläutert.

In diesem Kursabschnitt wird das Spiel "PACMAN" erstellt.


Christian Page 11/2001 Anregungen oder Tipps an Christian Page
 
  3.1 Zu diesem Kurs  voriges Thema [ Top ] nächstes Thema

Wie schon in den vorigen Kursteilen angekündigt werden Sie nun lernen, wie man ein vollständiges PacMan-Spiel auf Visual Basic schreiben kann. Dafür werden wir die folgenden, bereits besprochenen, Module einsetzen:

 MEDIA.FRM Diese Form enthält das MCI-Steurelement, das im Modul MIDI.BAS angesprochen wird (Spiele-Kurs 1)
 MIDI.BAS Dieses Modul steuert das MCI-Steuerelement an, um MIDI-Dateien abzuspielen (Spiele-Kurs 1)
 RESOURCE.FRM Diese Form fasst später die Bilder auf, die während der Laufzeit geladen werden (Spiele-Kurs 2)
 RESOURCE.BAS Die Routinen in diesem Modul dienen zum Laden von Bildern während der Laufzeit (Spiele-Kurs 2)
 SPRITE.BAS Dieses Modul dient zum Erstellen und Zeichnen von Sprites
(Spiele-Kurs 2).
 WAVE.BAS Dieses Modul enthält zwei Routinen zum Ausgeben von WAVE-Dateien

Beachten Sie bitte, dass es für Visual Basic 3.0 bzw. VB 4.0 - 16 Bit andere Module gibt, als für VB 4.0 - 32 Bit bis VB 6.0.

Die 16-Bit-Dateien für die folgende Demo finden Sie im Verzeichnis "DEMO", die 32-Bit-Dateien im Verzeichnis "DEMO32". Diese Module wurden in den angegebenen Kursteilen ausführlich besprochen. Das Verständnis der einzelnen Funktionen ist aber in diesem Teil nicht so wichtig. Wenn Sie also im letzten Teil - der zugegebenermaßen recht schwer war - nicht alle Routinen verstanden haben, so wird sich das in diesem Teil nicht auswirken.

Die in diesem Kurs besprochen Module (und das fertige Programm als Ausblick auf den nächsten Teil) finden Sie komplett in den Verzeichnissen "PACMAN", und "PACMAN32".

Also auf ein Neues :-)

 
  3.2 Sprite DEMO voriges Thema [ Top ] nächstes Thema

Die Sprite-Routinen aus dem letzten Teil sind ein wichtiger Bestandteil des Spieles. Daher kommt – bevor wir mit dem Spiel anfangen – ein kleines Programm, dass die Funktion dieser Routinen veranschaulicht.

Erstellen Sie bitte als erstes ein neues Projekt, und fügen Sie nun die folgenden Dateien hinzu (diese finden Sie im Archiv dieses Kurses bzw. die MCI-Datei im WINDOWS\SYSTEM-Verzeichnis auf Ihrer Festplatte ):

MCI.VBX bzw. MCI.OCX

RESOURCE.FRM

RESOURCE.BAS

SPRITE.BAS

WAVE.BAS

Gleich etwas wichtiges zu Anfang: Wenn Sie mit einer 32-Bit-VB-Version arbeiten, so dürfen die Module nie die Namen tragen, die in dem Modul als Datentypen, Prozeduren oder Variablen verwendet werden! Setzen Sie die Name-Eigenschaft vom Modul SPRITE.BAS nicht auf SPRITE sondern ändern Sie diese in MODUL_SPRITE. Ansonsten gibt Visual Basic beim Startversuch Fehler aus!

Öffnen Sie nun die noch leere Form "Form1". Setzen Sie auf diese ein Bildfeld mit dem Namen Display, und setzen Sie die Eigenschaft AutoRedraw auf "True" und ScaleMode auf "3 - Pixel".

Plazieren Sie das Bildfeld möglichst in der Mitte der Form, da wir in diesem Beispiel keine anderen Steuerelemente verwenden. Wenn der Benutzer später mit der Maus auf diese Bildfeld klickt soll an der Stelle ein kleiner Sprite angezeigt werden

In diesem Programm wollen wir die Funktion SPRITE_SET_MESH benutzen um über einen Befehl eine ganze Reihe von Sprites zu erstellen. Diese Funktion wird aufgerufen mit:

SPRITE_SET_MESH Datenfeld(), Anfang, X, Y, W, H, Bitmap, Maske
 Datenfeld()
Hier wird ein Sprite-Datenfeld übergeben.
   Dim Test(100) As Sprite
SPRITE_SET_MESH Test(), ....
Wenn Sie ein Datenfeld übergeben, dann schreiben Sie den Namen gefolgt von einer leeren Klammer. Sie dürfen keine Zahl schreiben, sonst wird nämlich nur der Eintrag mit diesem Index übergeben und nicht das ganze Datenfeld.
 Anfang

Gibt den Index im Datenfeld an, ab der die Sprites erstellt werden sollen.
In unserem Beispiel verwenden wir hier 0, damit der erste erstelle Sprite den Eintrag 0 bekommt, der zweite den Eintrag 1 usw.

 X Gibt an, wie viele Kästchen in X-Richtung in der Ressource sind
 Y Gibt an, wie viele Kästchen in Y-Richtung in der Ressource sind
 W Gibt die Breite eines Kästchens in Pixel an
 H Gibt die Höhe eines Kästchens in Pixel an
Bitmap Hier wird ein Bildfeld übergeben, aus der die Bitmap-Daten kommen
Maske Und hier wird noch das Masken-Bildfeld übergeben

Um die Geschichte mit den Kästchen noch zu verdeutlichen, kommt hier eine kurze Erläuterung. Die Routine SPRITE_SET_MESH geht davon aus, dass das Bildfeld in rechteckige Kästchens aufgeteilt ist. Jedes Kästchen enthält einen Sprite. Um nun aus diesen Kästchen einzelne Sprites zu machen, die man dann einfach über eine Nummer anspricht, benötigt die Routine ein Datenfeld, in dass die Sprites kommen. Außerdem muss die Routine noch wissen, wie viele Kästchen sich auf dem Bild befinden und wie groß die einzelnen Kästchen sind.

In dem Bild oben sehen Sie, wie aus 4 x 3 Rechtecken zwölf neue Sprites entstehen. Die Kästchen müssen übrigens durch Linien voneinander getrennt werden. W und H geben nämlich nur die Ausdehnung des eigentlichen Sprites (innerhalb der roten Umrandung) an. Bei diesem Beispiel muss X = 4 und Y = 3 sein, damit aus allen Kästchen Sprites entstehen. Die Kästchen in diesem Kurs haben immer die Größe 20 x 20 Pixel. Damit müsste der Aufruf für die obige Situation lauten:

SPRITE_SET_MESH Spr(), 0, 4, 3, 20, 20, Bild1, Bild2

Anschließend sind die Einträge 0 - 11 des Datenfeldes Spr() mit den Sprites aus dem Bild belegt. Daran sehen Sie, wie effektiv sich diese Routine einsetzen lässt.

Nun müssen wir aber für unser Beispiel-Programm erst das Datenfeld definieren. Öffnen Sie dazu die Prozedur "(allgemein) / (Deklarationen)" der Form.

Dim PFAD$ 
Dim Spr(100) As Sprite

In der ersten Zeile definieren wir die Variable PFAD$, die, wie schon im vorletzten Teil, angibt, wo sich die Daten des Programms befinden. In der zweiten Zeile wird unser Sprite-Datenfeld definiert, dass alle Sprites aufnimmt, die wir benutzen werden.

Jetzt kommt die Ereignisprozedur FORM_LOAD.

Sub FORM_LOAD ()

  PFAD$ = "F:\DATEN\DOCS\VBKURS\SPIELE3\DEMO\"

Zuerst setzen wir unsere Variable PFAD$, so dass VB alle Dateien finden kann, die zu dem Spiel gehören. Hier müssen Sie den Pfad auf Ihrer Festplatte eintragen. Befinden sich die Dateien beispielsweise im Verzeichnis "C:\VBKURS\SPIELE3\DEMO\" so setzen Sie auch die PFAD$-Variable auf dieses Verzeichnis. Wenn Sie später eine EXE-Datei erstellen, müssen Sie PFAD$="" schreiben, damit die Dateien im aktuellen Verzeichnis gesucht werden.

  B1 = RES_NEW(): RES_LOAD B1, PFAD$ + "BIT\TEST.BMP"
  M1 = RES_NEW(): RES_LOAD M1, PFAD$ + "MASKE\TEST.BMP"

Nun werden die Bilder geladen. Dazu wird mit der Funktion RES_NEW() erst eine neue Ressource erstellt, die das Bild aufnehmen soll. Den Rückgabewert sichern wir in der Variable B1 (der Name ist abgeleitet von Bitmap 1). Danach folgt ein Doppelpunkt. Dieser trennt zwei Anweisungen und ermöglicht es, mehrere Befehle in eine Zeile zu schreiben. Beispiel:

     A=10
     B=5
 
entspricht
 
    
A=10: B=5

Anschließend wird mit dem Befehl RES_LOAD das Bild BIT\TEST.BMP in die Ressource mit der Nummer B1 geladen. Das gleiche Spielchen wiederholen wir mit der Variable M1 (abgeleitet von Maske 1) und der Datei MASKE\TEST.BMP. Nun haben wir zwei Bitmaps geladen, die eine enthält die Bitmapdaten, die andere die Maske.

Die Bilder sind wieder in Kästchen aufgeteilt, wobei ein Kästchen innen 20*20 Pixel groß ist. Uns interessiert nur ein Bereich von 8 Kästchen Länge und drei Kästchen Höhe:

Jetzt verwenden wir den SPRITE_SET_MESH-Befehl um die Sprites zu erstellen:
  SPRITE_SET_MESH Spr(), 0, 8, 3, 20, 20, RESSOURCEN.RES(B1), _
         RESSOURCEN.RES(M1)

An SPRITE_SET_MESH übergeben wir als erstes das Datenfeld Spr(), daß wir definiert haben. Anschließend übergeben wir die Anzahl der Kästchen in X-Richtung (in diesem Fall 8, wie man an dem Bild oben sehen kann) und in Y-Richtung (3 Kästchen). Anschließend folgt die innere Breite und Höhe eines Kästchens in Pixeln. Nun müssen wir die Bildfelder übergeben, in denen die Bilder für die Bitmapdaten und für die Maske sind.

Jetzt fehlt nur noch das Ende der Prozedur:

End Sub

Damit hätten wir das schwerste dieses Beispiels hinter uns. Die Sprites befinden sich nun in dem Datenfeld Spr() und können mit der Prozedur SPRITE_DRAW ausgegeben werden. In diesem Beispiel soll jeweils ein zufälliger Sprite ausgegeben werden, wenn der Benutzer auf das Bildfeld klickt. Dazu benutzen wir die Ereignisprozedur DISPLAY_MOUSEDOWN.

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

  SPRITE_DRAW Spr(Fix(Rnd * 20)), X, Y, DISPLAY
  DISPLAY.Refresh

End Sub

Der Ereignisprozedur MOUSEDOWN werden einige wichtige und vor allem nützliche Parameter übergeben: Button enthält den Status der Mausknöpfe. 1 steht hierbei für die linke Maustaste, 2 für die rechte und 3 für beide. Shift gibt an, ob die Umschalttaste gedrückt ist (=1) oder nicht (=0). X und Y geben die Mausposition relativ zur oberen, linken Ecke des Bildfeldes an.

In der zweiten Zeile wird die Prozedur SPRITE_DRAW aufgerufen. Als erstes übergeben wir SPRITE_DRAW den Sprite, der gezeichnet werden soll. Hierbei benutzen wir den Eintrag des Datenfeldes Spr() mit dem Index FIX(RND*20), was eine Zufallszahl zwischen 0 und 20 ist. Dadurch werden zufällige Sprites ausgegeben.

Anschließend übergeben wir die Position. Hier verwenden wir einfach die Mauskoordinaten. Der letzte Übergabeparameter ist das Bildfeld, auf dem der Sprite ausgegeben werden soll. In dem Beispiel ist dies das Bildfeld DISPLAY.
In der letzten richtigen Zeile der Prozedur wenden wir die REFRESH-Methode des Bildfeldes DISPLAY an, damit die Änderung auch angezeigt werden. Die Prozedur schließen wir nun wieder mit End Sub ab.
Damit haben wir das Beispielprogramm abgeschlossen. Führen Sie es mit F5 aus und sehen Sie sich an, was Sie mit diesen wenigen Zeilen Programmcode erreicht haben.

 
  3.3 Typen für Spieler 2 voriges Thema [ Top ] nächstes Thema

Im vorigen Kurs haben wir ja schon angefangen, Typen zu definieren, auf denen das spätere Spiel basieren soll. Die Routinen für Ressourcen und Sprites sind zwar sehr wichtig, jedoch spielen sie nur für die Ausgabe auf dem Bildschirm eine Rolle. Der Kern des Spiels fehlt bis jetzt.

Wir wollen natürlich nun nicht wieder mit dem relativ schlechten Programmierstil aus Kurs 1 weitermachen und alle Werte in einzelnen Datenfeldern mit solchen Namen wie ObjetcX(9) speichern. Daher verwenden wir wieder eigene Datentypen.

Auch dieses Spiel soll auf Objekten basieren, die sich auf dem Spielfeld bewegen und agieren können. Damit haben wir praktisch schon die zwei wichtigsten Datentypen: FieldType und ObjType (ObjectType). Fangen wir erst einmal mit FieldType an, da dass Spielfeld ja zum Rahmen des Spiels gehört. Legen Sie dazu ein Modul an, dass Sie unter dem Dateinamen OBJECT.BAS speichern. Öffnen Sie die Prozedur "(allgemein) / (deklarationen)".

Option Explicit

Type FieldType
  V As Integer     'Visible - Ist das Feld überhaupt belegt?
  S As Sprite      'Sprite, der für das Feld benutzt wird
  B As Integer     'Block - handelt es sich um eine solid Wand?
End Type

Der Datentyp ist recht einfach aufgebaut. Das Element V gibt an, ob das Spielfeld sichtbar, also überhaupt benutzt wird. S ist eine Variable des Datentyps Sprite und gibt an, was an dieser Stelle gezeichnet werden soll, und B gibt an, ob der Spieler bzw. die Monster sich durch dieses Feld bewegen können oder ob es eine Wand ist.

Jetzt müssen wir natürlich ein Datenfeld anlegen, dass die Felder aufnimmt:

Global FIELD() As FieldType

Global
FIELD_W    'Breite eines Feldes
Global FIELD_H    'Höhe eines Feldes
Global
FIELD_X    'Breite des Spielfeldes (in Feldern
Global FIELD_Y    'Höhe des Spielfeldes (in Feldern)

Das globale Datenfeld FELD() des Typs FieldType bekommt vorläufig noch keine festgelegten Dimensionen. Diese werden erst beim Laden eines Levels festgelegt. Anschließend werden noch einige globale Variablen definiert, die Informationen über das Spielfeld speichern. FIELD_W gibt an, wie breit (in Pixel) ein Feld ist, FIELD_H gibt die Höhe eines Feldes in Pixel an, FIELD_X und FIELD_Y geben die Größe des Feldes an.

Nun folgen noch die Prozeduren, die mit diesem Datentyp arbeiten. Es hat ja schließlich keinen Sinn, wenn wir erst ein eigenes Modul für diesen Typ erstellen und dann die ganze Verwaltungsarbeit auf herkömmliche Weise erledigen.

Sub FIELD_SIZE (X, Y, W, H)

  ReDim FIELD(X, Y) As FieldType

  FIELD_X = X
  FIELD_Y = Y
  FIELD_W = W
  FIELD_H = H

  For AX = 0 To X
     For AY = 0 To Y
       FIELD(AX, AY).V = 0
     Next AY
  Next AX

End Sub

Die Prozedur FIELD_SIZE ist sehr wichtig, damit man überhaupt etwas mit dem Datenfeld FIELD() machen kann. Es bekommt die Werte X, Y, W und H übergeben. X und Y geben die Größe des Spielfeldes an, W und H geben die Größe eines Feldes in Pixel an.

Jetzt wird mit dem Befehl ReDim das Datenfeld auf die erforderliche Größe eingestellt. Das Datenfeld FIELD() ist zweidimensional, genau wie das Spielfeld eines Brettspiels.

Die Werte von X, Y, W und H werden in den Variablen FIELD_X, FIELD_Y usw. gespeichert, damit man später einfach darauf zurückgreifen kann. 

Nun werden alle Einträge von FIELD() durchgegangen und jeweils das Element V auf 0 gesetzt. Dadurch sind standardmäßig alle Felder unsichtbar.

Wir brauchen auch eine Prozedur, mit der wir gezielt auf einzelne Felder zugreifen können, um diese zu verändern. Ansonsten wäre das Spielfeld immer leer, was nicht ganz in unserem Interesse sein dürfte.

Sub FIELD_SET (X, Y, V, S As Sprite, B)

  FIELD(X, Y).V = V
  FIELD(X, Y).B = B

  FIELD(X, Y).S = S
  FIELD(X, Y).S.NACH.W = FIELD_W
  FIELD(X, Y).S.NACH.H = FIELD_H

End Sub

Der Prozedur FIELD_SET werden alle Werte übergeben, die für das Feld wichtig sind: X und Y geben an, welches Feld geändert werden soll, V gibt an, ob das Feld sichtbar sein soll (=1), S ist eine Variable des Typs Sprite die angibt, was überhaupt hier gezeichnet werden soll, B gibt eben an, ob das Spielfeld eine Wand(=1) ist oder nicht (=0). Nun werden die Elemente des Eintrags von FIELD() nach und nach auf die übergebenen Werte gesetzt.

FIELD(x,y).V wird natürlich auf V gesetzt. Parallel dazu setzten wir auch FIELD(x,y).B auf B und FIELD(x,y).S auf S.

Jetzt wird FIELD(x, y).S noch etwas bearbeitet, damit auch der übergebene Sprite in das Feld passt. Die Ausgabebreite und -höhe des Sprites werden auf die Größe eines Feldes gesetzt. So ist sichergestellt das der übergebene Sprite auch wirklich ein Feld ausfüllt und nicht kleiner oder größer ist.

Mit diesen beiden Prozeduren können wir eigentlich schon recht einfach das Spielfeld belegen. Nun muss es aber natürlich noch gezeichnet werden. Dies übernimmt die Prozedur FIELD_DRAW

Sub FIELD_DRAW (DISPLAY As PictureBox, X, Y)

  For AX = 0 To FIELD_X
    For AY = 0 To FIELD_Y
      If FIELD(AX, AY).V Then 
        FX = X + AX * FIELD_W
        FY = Y + AY * FIELD_H
        If FX <= DISPLAY.ScaleWidth And FY _
           <= DISPLAY.ScaleHeight Then 
          If FX + FIELD_W >= 0 And FY + FIELD_H >= 0 Then
            SPRITE_DRAW_SOLID FIELD(AX, AY).S, FX, FY, DISPLAY
          End If
        End If
      End If
    Next
AY
  Next AX

End Sub

Der Prozedur FIELD_DRAW wird das Bildfeld übergeben, auf das das Spielfeld gezeichnet werden soll. Danach folgt das X- und das Y-Offset des Spielfeldes. Das bedeutet, wie stark das Spielfeld verschoben dargestellt sein soll.

In dem Bild sehen Sie ein Spielfeld, das nach links und nach oben verschoben ist. Das können wir erreichen, indem wir für X einen Wert von –25 (1.5 Kästchen) und für Y ebenfalls –25 übergeben. Wenn wir positive Werte angeben wird das Spielfeld in die entgegengesetzte Richtung verschoben. Nun aber weiter mit der Prozedur.

Es werden alle Einträge des Datenfeldes FIELD() durchlaufen. Dabei wird jeweils geprüft, ob das Feld sichtbar ist. Ist dies der Fall, so wird die Position des Feldes ausgerechnet und geprüft, ob das Feld überhaupt innerhalb des gerade sichtbaren Bereichs liegt.

Anschließend wird eine neue SPRITE-Routine benutzt, um das Feld endgültig auszugeben. Diese haben wir im letzten Kurs nicht besprochen: SPRITE_DRAW_SOLID. Sie funktioniert im Grunde genauso wie die normale SPRITE_DRAW-Funktion, nur wird hier keine Transparenz berücksichtigt. Daher ist die Prozedur schneller als die andere, kann aber auch keine transparenten Stellen darstellen. Hier das Listing dieser Prozedur (fügen Sie diese Bitte auch in das Modul SPRITE.BAS ein, damit Sie die Übersicht behalten):

Sub FIELD_DRAW (DISPLAY As PictureBox, X, Y)

  S.NACH.hDC = PicBox.hDC

  r% = StretchBlt(S.NACH.hDC, X, Y, S.NACH.W, S.NACH.H,_
       S.Bit.hDC, S.Bit.X, S.Bit.Y, S.Bit.W, S.Bit.H, BIT_COPY)

End Sub

Mit diesen Routinen können wir ein Spielfeld erstellen und anzeigen lassen. Das alleine macht natürlich noch kein Spiel aus. Es fehlt der Spieler, die Gegner, die Gegenstände...

 
  3.4 Die Objekte voriges Thema [ Top ] nächstes Thema

Bevor wir damit anfangen, einen Datentyp für die Objekte zu erstellen, sollten wir uns überlegen, was alles zu einem Objekt gehört.

Das Objekt muss natürlich X- und Y-Koordinaten haben, da wir sonst nicht wissen, wo es sich auf dem Spielfeld befindet. Das Objekt soll sich bewegen können, also benötigen wir zwei Variablen die die X- und Y-Geschwindigkeit angeben. Weiterhin muss das Objekt einen Energiezähler haben, eine Variable die den Typ des Objekts speichert, einen Sprite, der gezeichnet werden soll und eine Variable, die angibt, ob das Objekt sichtbar ist. Zusätzlich benutzen wir noch drei Reservevariablen die wir R1, R2 und R3 nennen.

Um die Handhabung von Extras und PowerUps zu vereinfachen, bekommt jedes Objekt ein Inventar, das Gegenstände aufnehmen kann. Dazu ist ein Unter-Datentyp von Nöten. Öffnen Sie nun wieder das Codemodul OBJECT.BAS und dort die Prozedur "(allgemein)" / "(Deklarationen)".

Type ItemType
  T As Integer   'Typ des Items
  E As Integer   'Energie / Anzahl / verbleibende Zeit für Extras
End Type

Dieser Datentyp hat nur zwei Elemente: T gibt den Typ an, und E gibt an, wie viele Gegenstände dieses Typs im Inventar sind bzw. wie lange der Gegenstand noch aktiv ist usw.

Jetzt erstellen wir den Datentyp ObjType:

Type ObjType
  X As Integer     'X-Position
  Y As Integer     'Y-Position
  OX As Integer    'Vorherige Position (X)
  OY As Integer    'Vorherige Position (Y)

  SX As Integer    'X-Geschwindigkeit
  SY As Integer    'Y-Geschwindigkeit

  E As Integer     'Energie
  T As Integer     'Typ des Objekts
  FRAME As Sprite  'Sprite des Objekts

  R1 As Integer    'Reserve-Wert #1
  R2 As Integer    'Reserve-Wert #2
  R3 As Integer    'Reserve-Wert #3

  V As Integer     'Für andere Objekte"sichtbar"

  ITEM(20) As ItemType  'Welche Items hat das Objekt bei sich?
End Type

Wie Sie sehen, habe ich "eigenmächtig" noch die Elemente OX und OY eingeführt. Diese Variablen enthalten Sicherungswerte. Wir werden sie noch benötigen, wenn wir uns mit der Steuerung auseinandersetzen.

Auch für diesen Datentyp brauchen wir einige globale Variablen als Hilfe:

Global ObjDummy As ObjType
Global MAX_DIFF

ObjDummy ist ein leeres Objekt und MAX_DIFF gibt an, wie genau Kollisionen geprüft werden sollen. Nachdem wir den globalen Teil des Moduls erstellt haben, kommen jetzt die OBJ_xxxxxxx Routinen. Auf ihnen basiert das ganze Spiel.

Fangen wir mit OBJ_SET an, da man mit dieser Routine neue Objekte erstellen kann.

Sub OBJ_SET (O As ObjType, X, Y, E, SX, SY, T, FRAME As Sprite, V)

  O.X = X
  O.Y = Y
  O.OX = X
  O.OY = Y
  O.E = E
  O.T = T
  O.FRAME = FRAME
  O.V = V

  O.SX = SX
  O.SY = SY

End Sub

Der Routine wird einmal das zu setzende Objekt (O As ObjectType) übergeben und die Werte, die für die Einstellungen relevant sind. Nach den Koordinaten, der Energie und der Geschwindigkeit in X- und Y-Richtung wird der Typ des Objekts übergeben. Danach kommt noch der Sprite, der für das Objekt gezeichnet werden soll und die Unsichtbar-Eigenschaft des Objekts.

Parallel dazu die Routine OBJ_CLEAR, die ein bestehendes Objekts löscht, indem es alle Eigenschaften des Objekts auf Null setzt:

Sub OBJ_CLEAR (O As ObjType)

  O.X = 0
  O.Y = 0
  O.E = 0
  O.T = 0
  O.V = 0
  O.SX = 0
  O.SY = 0
 
  O.R1 = 0
  O.R2 = 0
  O.R3 = 0

  For I = 0 To UBound(O.ITEM)
      O.ITEM(I).T = 0
  Next I

End Sub

In dieser Routine werden erst die "normalen" Eigenschaften des übergebenen Objekts O auf Null gesetzt und anschließend wird auch noch das Inventar geleert.

Mit diesen beiden Prozeduren können wir nun Objekte anlegen und wieder löschen. Uns fehlen jetzt noch Routinen zum Anzeigen, zur Kollisionsprüfung und zur Verwaltung des Inventars. Und wenn wir die fertig haben? Wie sollen wir dann das Spiel programmieren? Es gibt zwei Möglichkeiten:

Wir packen den eigentlich Quellcode des Spiels in eine eigene Prozedur und von dort werden die Prozeduren zur Objekt- und Sprite-Verwaltung aufgerufen, ähnlich wie es im letzen Spiel der Fall war.

Jedes Objekt verwaltet sich selbst. Das hört sich vielleicht etwas abstrakt an, allerdings steckt ein einfacher Gedanke dahinter: Immer wenn etwas mit dem Objekt passieren soll, z.B. wenn es gezeichnet wird oder es mit einem anderen Objekt kollidiert wird eine vom Objekttyp abhängige Routine aufgerufen, die dafür sorgt, dass das Objekt auf dieses Ereignis reagiert.

Die Wahl die ich für Sie getroffen habe ist klar: Wir arbeiten entsprechend Punkt 2, da wir die andere Möglichkeit ja schon kennen und wissen, dass sie nicht sehr flexibel ist.

Um diese abgewandelte Form der richtigen objektorientierten Programmierung (OOP) zu verwirklichen benötigen wir einige globale Konstanten. Eine Konstante ist nichts anderes als eine Variable deren Wert nicht geändert werden kann. Um eine globale Konstante zu definieren benutzten wir den Befehl:

Global Const <Konstante> =  <Wert>

Diese Anweisung funktioniert nur im Deklarationenteil eines Moduls. Also öffnen Sie im Modul OBJECT.BAS die Prozedur "(allgemein) / (Deklarationen)". Jetzt müssen wir uns überlege welche Konstanten wir benötigen.

Um einfach Ereignisse verarbeiten zu können, bekommt jedes mögliche Ereignis eine feste (konstante) Nummer. Wir brauchen dann nur noch die übergebene Variable, die den Ereigniswert beinhaltet, mit einer Konstante vergleichen und können so entsprechend darauf reagieren. Und warum nehmen wir dafür nicht einfach Zahlen wie etwa "If Ereignis = 1 Then..."? Ganz einfach weil man sich Wörter leichter merken kann als Zahlen und man später den Sinn der Abfrage besser nachvollziehen kann, wenn dort steht "If Ereignis = ID_DRAW Then...".

Nun zu den Konstanten, die wir für das Spiel benötigen:

Global Const ID_COLL = 1
Global Const ID_COLL_WALL = 2
Global Const ID_HIT = 3
Global Const ID_DRAW = 4
Global Const ID_MOVE = 5
Hier sehen Sie schon, dass wir nur wenige "Ereignis"-Konstanten für das Spiel benötigen:
ID_COLL tritt immer dann auf, wenn das Objekt mit einem anderen Objekt kollidiert.
ID_COLL_WALL tritt auf, wenn ein Objekt eine feste Wand berührt.
ID_HIT wird dann übergeben, wenn ein Objekt ein anderes angegriffen hat.
ID_DRAW ist wohl klar, oder? Es gibt an, dass das Objekt gezeichnet werden soll.
ID_MOVE teilt dem Objekt mit, dass es sich bewegen soll.

Diese Ereignisse werden natürlich nicht ohne unser Zutun ausgelöst. Die Routine OBJ_DRAW, die wir gleich kennen lernen, löst beispielsweise vor dem Zeichnen das Ereignis ID_DRAW aus, damit das Objekt vorher noch den zu zeichnenden Sprite setzen kann.

Jetzt kommt die einfache aber sehr elementare Routine OBJ_DRAW, die ein Objekt überhaupt erst auf den Bildschirm bringt:

Sub OBJ_DRAW (O As ObjType, X, Y, PicBox As PictureBox)

  If O.T = 0 Then Exit Sub

  OBJ_EVENT O, ID_DRAW, ObjDummy, 0, 0
 
  XX = X + O.X
  YY = Y + O.Y
  SPRITE_DRAW O.FRAME, XX, YY, PicBox

End Sub

Der Routine OBJ_DRAW wird außer dem zu zeichnenden Objekt noch ein Koordinatenpaar übergeben, das genauso wie beim Spielfeld aus Offset zur eigenen Position zu verstehen ist. Das bedeutet, der Sprite wird relativ zu seiner Position um X Einheiten nach rechts und um Y Einheiten nach unten verschoben. Dadurch können wir, wenn wir das Offset für das Spielfeld haben, es ebenfalls an die Routine OBJ_DRAW übergeben. Als letzter Parameter wird noch das Ausgabebildfeld übergeben.

In der Routine selbst wird zuerst geprüft, ob das Objekt überhaupt belegt ist. Bekanntlich gibt das Element T ja an, welcher Objekttyp vorliegt. Ist dieser Typ=0 geht die Routine davon aus, dass das Objekt nicht benutzt wird und lässt den Sprite aus. In der nächsten Zeile wird das Ereignis ID_DRAW ausgelöst. Dazu wird die Routine OBJ_EVENT (die wir gleich besprechen werden) ausgelöst. Diese verlangt folgende Parameter:

 OBJ_EVENT <Objekt>, <Ereignis>, <Auslöser>, <Zusatzwert1>, <Zusatzwert2>

Das Objekt ist in diesem Fall natürlich O, das Ereignis ist ID_DRAW. Der Auslöser ist ein Objekt, das für das Auslösen des Ereignisses verantwortlich ist. In unserem Fall übergeben wir hier ObjDummy, da kein Auslöser vorhanden ist. Für die Zusatzwerte übergeben wir 0. Diese dienen nur für bestimmte Ereignisse, die weitere Parameter erfordern.

Im Anschluss daran werden die Koordinaten berechnet und der Sprite des Objekts (O.FRAME) wird mit Hilfe der Prozedur SPRITE_DRAW ausgegeben.
Nun zur Routine OBJ_EVENT, die, ob Sie es glauben oder nicht sehr kurz ausfällt:

Sub OBJ_EVENT (Self As ObjType, MESSAGE, OTHER As ObjType, _
               ByVal Par1, ByVal Par2)

  PACMAN_EVENT Self, MESSAGE, OTHER, Par1, Par2

End Sub

Tja, kurz ist die Routine schon, aber der Prozedurkopf hat es diesmal in sich. Vor einigen Variablen steht ByVal, vor anderen nicht. Was bedeutet das?

ByVal gibt an, dass bei der Parameterübergabe der Wert übergeben werden soll. ByVal ist abgeleitet von Call By Value (Aufruf mit Wert).

Die anderen Möglichkeit ist eine Parameterübergabe, die die Variable an sich übergibt. Diese Methode nennt man in der Programmierung Call By Reference.

Aha. Und worin besteht der Unterschied?

 
  3.5 Gute Referenzen voriges Thema [ Top ] nächstes Thema

Normalerweise wird eine Variable mit Call By Reference übergeben. Das bedeutet, dass die aufgerufene Routine den Wert der Variable die übergeben wurde ändern kann. Hier ein Beispiel dazu. 

(Geben Sie es, wenn Sie es ausprobieren möchten, separat ein und schreiben Sie es nicht in das Pacman-Spiel!)

Sub Test (B)
  B = 5
End Sub

Sub Form_Load

  A = 10
  MsgBox STR$(A), 0, "A hat den Wert:"
  Test A
  MsgBox STR$(A), 0, "A hat den Wert:"

End Sub

Zuerst wird die Variable "A" der Prozedur Form_Load auf 10 gesetzt. Dann wird der Wert ausgegeben. Anschließend übergibt Form_Load die Variable an die Prozedur Test.

In dieser Prozedur wird B abgeändert. Da wir die Variable "A" mit Call By Reference übergeben haben ändert sich dadurch automatisch der Wert von A! Wenn nun die Prozedur beendet ist und A erneut ausgegeben werden soll hat A auch den Wert 5.

Nun die andere Variante:

Sub Test (ByVal B)
  B = 5
End Sub

Sub Form_Load

  A = 10
  MsgBox STR$(A), 0, "A hat den Wert:"
  Test A
  MsgBox STR$(A), 0, "A hat den Wert:"

End Sub

Auch hier wird als erstes die Variable "A" der Prozedur Form_Load auf 10 gesetzt. Dann wird der Wert ausgegeben. Anschließend übergibt Form_Load die Variable an die Prozedur Test. Diesmal jedoch mit der Methode Call By Value.

In dieser Prozedur wird B abgeändert. Da wir Call By Value eingesetzt haben wird A von der Änderung der Variable B nicht beeinflusst! Nachdem die Prozedur beendet ist wird nun die Variable A, die weiterhin den Wert 10 hat, ausgegeben.

Noch etwas: Eigene Datentypen wie z.B. ObjType werden immer mit Call By Reference übergeben.

 
  3.6 Noch mehr Objekte voriges Thema [ Top ] nächstes Thema

Wozu benutzen wir denn nun im Prozedurkopf von OBJ_EVENT ByVal? Wir verwenden es hier, um zu verhindern, dass die als Par1 und Par2 übergebenen Variablen geändert werden. Wenn wir beispielsweise OBJ_EVENT O, ID_MOVE, ObjDummy, SX, SY aufrufen, würden wir Gefahr laufen, dass die Werte SX und SY abgeändert werden. Und das liegt hier nicht in unserem Interesse.

Die einzige Quellcode-Zeile in der Prozedur OBJ_EVENT ist:

USER_EVENT Self, MESSAGE, OTHER, Par1, Par2

Hier wird einfach das Ereignis an eine andere Ereignisroutine mit dem Namen USER_EVENT weitergeleitet. Das scheint vielleicht umständlich, da wir ja direkt in diese Routine den Code packen könnten. Jedoch wollen wir ja ein Engine-Prinzip verwirklichen. Und ein Grundgedanke einer Engine ist wiederverwendbarer Code, der nicht mehr geändert werden muss. Daher wird hier die Kontrolle an eine vom User (oder besser gesagt vom Programmierer) erstellte Routine weitergegeben. Diese Routine befindet sich aus dem oben genannten Grund auch nicht in dem Modul OBJECT.BAS sondern in unserem Spiel in dem Modul PACMAN.BAS.

Aber wir werden jetzt noch nicht mit diesem Modul anfangen, da noch einige OBJ_xxxx-Routinen fehlen. Im nächsten Teil werden wir dann zum letzen Modul PACMAN.BAS übergehen und das Spiel zum Laufen bringen.

 
  3.7 Crashtests voriges Thema [ Top ] nächstes Thema

Wir machen nun mit den Kollisions-Routinen weiter, da diese für einen fehlerfreien Spielablauf sehr wichtig sind. Der erste Vertreter dieser Routinen ist die Funktion OBJ_FIELD_ISFREE(x, y, w, h). Diese Funktion gibt an, ob eine Fläche mit den Koordinaten X, Y und der Größe W*H frei ist, also nicht von Wänden des Spielfelds blockiert wird.

Function OBJ_FIELD_ISFREE (X, Y, W, H)

  X1 = X \ FIELD_W
  Y1 = Y \ FIELD_H
  X2 = (X + W) \ FIELD_W + 1
  Y2 = (Y + H) \ FIELD_H + 1

Zuerst werden die Felder errechnet, die überprüft werden müssen. Dazu wird die X-Koordinate durch die Breite eines Feldes geteilt und der Nachkommateil wird abgeschnitten (Division mit einem Backslash "\" statt normal mit einem Slash "/"). Das gleiche wird mit der Y-Koordinate gemacht. Das Entfernen der Nachkommastellen ist wichtig, weil wir hier immer abrunden wollen. Hat ein Objekt beispielsweise die Position X=1.9, so müssen wir ab Feld 1 (abgerundet) und nicht erst ab Feld 2 (aufgerundet) auf eine Kollision überprüfen.

Anschließend werden die Koordinaten der linken unteren Ecken errechnet. Auch hierbei wird durch FIELD_W und FIELD_H geteilt und der Nachkommateil abgeschnitten. Nun haben wir die Koordinaten X1,Y1 und X2,Y2. Diese geben die Felder an, die wir nun überprüfen müssen.

 For AX = X1 To X2
   For AY = Y1 To Y2

    If AX >= 0 And AY >= 0 And AX <= FIELD_X And AY <= FIELD_Y Then

Es wird bei jedem Feld geprüft, ob das Feld auch innerhalb des wirklichen Spielfelds liegt. So wird verhindert, dass beispielsweise ein Feld mit dem Index -1/-1 überprüft wird, denn dies würde zu einem Fehler führen.

   If FIELD(AX, AY).B And FIELD(AX, AY).V Then

Zusätzlich muss das aktuelle Feld sichtbar und "blockierend" sein, damit es überhaupt weiter geprüft wird. Andernfalls ist es ja auch nicht wichtig

      FX = AX * FIELD_W
      FY = AY * FIELD_H

Nun werden die Koordinaten des Feldes errechnet. Vorher hatten wird ja mit X1,Y1 usw. nur den Index des Feldes (1,2 oder 5,10) nun errechnen wir die richtigen Koordinaten, indem wir den Index mit der Breite bzw. Höhe eines Feldes multiplizieren.

  If X + W - MAX_DIFF > FX And X < FX + FIELD_W - MAX_DIFF Then
    If
Y + H - MAX_DIFF > FY And Y < FY + FIELD_H - MAX_DIFF Then

Kollidiert die übergebene Fläche nun wirklich mit dem Spielfeld (hierbei wird übrigens der Toleranzabstand MAX_DIFF berücksichtigt), so wird 0 (=nicht frei) zurückgegeben. Und die Funktion wird beendet.

              OBJ_FIELD_ISFREE = 0
              Exit Function

            End If
          End If

        End If
      Else

Wenn eines der Spielfelder außerhalb des definierten Bereichs liegt (wenn es zum Beispiel einen Index von -1/-1 hat), so gilt das Feld auf jeden Fall als blockiert und es wird ebenfalls 0 zurückgeliefert.

        OBJ_FIELD_ISFREE = 0
        Exit Function
      End If

    Next AY
  Next AX

Wenn keine Kollision festgestellt worden ist wird 1 (=frei) zurückgegeben.

  OBJ_FIELD_ISFREE = 1
  
End Function

Puh, das war ja eine ganz schöne Arbeit - aber dafür können wir diese Routine gleich mehrfach einsetzen.

Hier folgt nun eine ganze Reihe von Kollisions-Routinen, die in dem Spiel benutzt werden und auf der obigen Routine basieren:

Sub OBJ_COLL_FIELD (C As ObjType)

  If C.T = 0 Then Exit Sub

  If OOBJ_FIELD_ISFREE(C.X, C.Y, C.FRAME.NACH.W, _
     C.FRAME.NACH.H) = 0 Then
 
     OBJ_EVENT C, ID_COLL_WALL, ObjDummy, 0, 0
  End If

End Sub

Diese Funktion wird aufgerufen, um zu überprüfen, ob ein bestimmtes Objekt mit einem Feld kollidiert. In der ersten Zeile wird überprüft, ob das Objekt überhaupt belegt ist, damit man sich keine unnötige Mühe macht. Anschließend wird OBJ_FIELD_ISFREE aufgerufen. Dabei werden die X- / Y-Koordinaten des Objekts und die Breite und Höhe des zugehörigen Sprites übergeben. Gibt nun die Funktion 0 zurück (der angegebene Bereich ist nicht passierbar), so wird das Ereignis ID_COLL_WALL ausgelöst.

Function OBJ_DOWN (C As ObjType, D)
  OBJ_DOWN = OBJ_FIELD_ISFREE(C.X, C.Y + D, (C.FRAME.NACH.W), _
             (C.FRAME.NACH.H))
End Function

Die Funktion OBJ_DOWN gibt an, ob das übergebene Objekt D Einheiten nach unten gehen kann, ohne mit einer Wand zu kollidieren. Diese Funktion wird später, genauso wie die Kollegen OBJ_UP, OBJ_LEFT und OBJ_RIGHT für die Gegnersteuerung benötigt.

Function OBJ_UP (C As ObjType, D)
  OBJ_UP = OBJ_FIELD_ISFREE(C.X, C.Y - D, C.FRAME.NACH.W, _
           C.FRAME.NACH.H)
End Function

Function
OBJ_LEFT (C As ObjType, D)
  OBJ_LEFT = OBJ_FIELD_ISFREE(C.X - D, C.Y, (C.FRAME.NACH.W), _
            (C.FRAME.NACH.H))
End Function

Function
OBJ_RIGHT (C As ObjType, D)
  OBJ_RIGHT = OBJ_FIELD_ISFREE(C.X + D, C.Y, (C.FRAME.NACH.W), _
              (C.FRAME.NACH.H))
End Function

Diese Routinen bespreche ich nicht mehr, da Sie das Gleiche machen, wie OBJ_DOWN, nur für eine andere Bewegungsrichtung.

Jetzt kommt die letzte Kollisionsroutine:

Sub OBJ_COLL_OBJ (C1 As ObjType, C2 As ObjType)

  If C1.T = 0 Then Exit Sub
  If C2.T = 0 Then Exit Sub

  X1 = C1.X
  Y1 = C1.Y
  W1 = C1.FRAME.NACH.W
  H1 = C1.FRAME.NACH.H

  X2 = C2.X
  Y2 = C2.Y
  W2 = C2.FRAME.NACH.W
  H2 = C2.FRAME.NACH.H

  If X1 +W1-MAX_DIFF > X2 And X1 < X2+W2-MAX_DIFF Then
    If Y1+H1-MAX_DIFF > Y2 And Y1 < Y2+H2-MAX_DIFF Then
      OBJ_EVENT C1, ID_COLL, C2, 0, 0
      OBJ_EVENT C2, ID_COLL, C1, 0, 0
    End If
  End If

End Sub

Diese Routine ist ähnlich aufgebaut wie die Kollisionsabfrage für Felder, jedoch werden hier zwei Objekte übergeben. Als erstes wird überprüft, ob beide Objekte belegt sind, damit nicht unnötig Rechenzeit geopfert wird. Anschließend werden die Koordinaten beider Objekte geladen und verglichen. Auch hierbei wird MAX_DIFF benutzt, um eine Toleranzgrenze zu setzen. Wenn die Objekte kollidieren, wird für jedes Objekt das ID_COLL-Ereignis ausgelöst. Bei jedem der beiden Aufrufe ist jeweils das andere Objekt der Auslöser. So kann zum Beispiel das Objekt C1 auf C2 zugreifen, wenn es das ID_COLL-Ereignis bekommt. Auch hier verzichten wir auf weitere Übergabewerte an die Ereignisroutine.

 
  3.8 Inventar voriges Thema [ Top ] nächstes Thema

“press i to see see the inventory” Solche Sätze kennt vielleicht der eine oder andere unter ihnen noch aus den guten alten Adventure-Spielen. Nun, in diesem Spiel werden wir auch ein Inventar verwenden. Jedoch beschränken wir uns dabei nicht auf “Aufnehmen” und “Ablegen” sonder wir schreiben flexible Routinen zum Verwalten des Inventars. Das klingt vielleicht etwas schwierig, aber es ist eine recht einfache Aufgabe:

Sub OBJ_ITEM_GET (O As ObjType, ITEM, Energy)

  If O.T = 0 Then Exit Sub

  For I = 0 To UBound(O.ITEM)
    If O.ITEM(I).T = 0 Then
      O.ITEM(I).T = ITEM
      O.ITEM(I).E = Energy
      Exit Sub
    End If
  Next I

End Sub

Die Prozedur OBJ_ITEM_GET sorgt dafür, dass ein neuer Gegenstand dem Inventar hinzugefügt wird. Am Anfang kommt die obligatorische Abfrage der Objektbelegung. In den nächsten Prozedur-Beschreibungen werde ich diesen Teil weglassen. Nun werden alle Einträge des Inventars durchlaufen und es wird überprüft, ob noch ein freier Platz da ist. In diesem Fall wird dieser mit der übergebenen Objektnummer und der "Energy" belegt. Anschließend wird die Routine beendet.

Sub OBJ_ITEM_DROP (O As ObjType, ITEM)

  If O.T = 0 Then Exit Sub

  For I = 0 To UBound(O.ITEM)
    If O.ITEM(I).T = ITEM Then
      O.ITEM(I).T = 0
      Exit Sub
    End If
  Next I

End Sub

OBJ_ITEM_DROP ist die genaue Umkehrung: Es durchsucht das Inventar und wirft den Gegenstand, falls vorhanden, heraus.

Function OBJ_ITEM_HAVE (O As ObjType, ITEM)

  If O.T = 0 Then Exit Function

  For I = 0 To UBound(O.ITEM)
    If O.ITEM(I).T = ITEM Then
      OBJ_ITEM_HAVE = O.ITEM(I).E
      Exit Function
    End If
  Next I

End Function

OBJ_ITEM_HAVE gibt den Energy-Wert des Gegenstandes zurück. Damit kann man nicht nur prüfen, ob der Gegenstand im Inventar vorhanden ist, sondern man kann damit gleichzeitig feststellen, wie viele Gegenstände es sind bzw. wie lange das Extra noch wirkt o.ä.. Der Aufbau dieser Funktion sollte auch nicht schwer zu verstehen sein, da er parallel zu den bisherigen ist.

Function OBJ_ITEM_ENERGY_GET (O As ObjType, ITEM)

  OBJ_ITEM_ENERGY_GET = OBJ_ITEM_HAVE(O, ITEM)

End Function

Was soll denn das? Warum schreiben wir eine Funktion, die das genau das Gleiche macht, wie eine andere? Tja, das hat etwas mit Einheitlichkeit zu tun ;-)

Die Funktion OBJ_ITEM_ENERGY_GET soll das Gegenstück zu OBJ_ITEM_ENERGY_SET sein. Dass die Funktion OBJ_ITEM_HAVE auch die Energie zurückgibt, ist eigentlich nur eine Spielerei.

Sub OBJ_ITEM_ENERGY_SET (O As ObjType, ITEM, Energy)

  If O.T = 0 Then Exit Sub

  For I = 0 To UBound(O.ITEM)
    If O.ITEM(I).T = ITEM Then
      O.ITEM(I).E = Energy
      If Energy <= 0 Then
         O.ITEM(I).T = 0
      End If
      Exit Sub
    End If
  Next I
  OBJ_ITEM_GET O, ITEM, Energy

End Sub

Die Routine OBJ_ITEM_ENERGY_SET sucht den übergebenen Gegenstand und setzt seine Energie neu. Ist der Gegenstand noch nicht im Inventar vorhanden, so wird er aufgenommen.

Noch etwas: Alle Inventar-Routinen arbeiten mit Gegenstandsnummern. Sie unterscheiden also nach Gegenstand Typ 1, Typ 2 usw.. Daher werden wir für diese Funktionen später auch mit "Global Const" Konstanten festlegen, die jedem möglichen Gegenstand eine Nummer zuweisen.

Wie oben schon erwähnt hat die Energie im Zusammenhang mit Gegenständen nicht unbedingt wirklich etwas mit Energie zu tun, sondern kann auch angeben, wie lange ein bestimmtes Extra noch hält, oder es steht für eine andere numerische Eigenschaft des Gegenstandes.

Noch eine letzte OBJ-Routine, die nichts mit dem Inventar zu tun hat, bevor wir mit dem Hauptprogramm anfangen:

Sub OBJ_ANIMATE (C1, Max1, C2, Max2)

  C1 = C1 + 1
  If C > Max1 Then
     C1 = 0
     C2 = C2 + 1
     If C2 > Max2 Then C2 = 0
  End If

End Sub

Diese Routine bekommt vier Werte übergeben. C1 und C2 sind zwei Zählervariablen und Max1 bzw. Max2 sind die entsprechenden Maximalwerte. Jedes Mal wenn man diese Routine mit den gleichen Variablen aufruft wird C1 um 1 erhöht. Ist C1 größer als der Maximalwert für C1, so wird C1 auf 0 gesetzt und C2 wird um 1 erhöht. Ist C2 größer als der entsprechende Maximalwert wird auch C2 wieder auf 0 gesetzt. Ein Beispiel dazu:

Sub Form_Load

  Do
   
OBJ_ANIMATE A, 2, B, 4
    MsgBox Str$(A) + "/" + Str$(B), 0, "Status:"
  Loop

End Sub
Ausgabe: 1/0, 2/0, 0/1, 1/1, 2/1, 0/3, 1/3, 2/3, 0/4, 1/4, 2/4....

Diese Routine dient zur Animation. Aber dazu im nächsten Teil mehr.

 
  3.9 Ende Teil 3 voriges Thema [ Top ]   

Wie Ende? Warum denn? Das kann ich Ihnen wohl sagen: Wenn ich den Kurs jetzt noch weiter ausdehne fürchte ich, dass ich selbst die Übersicht verliere und nur noch unvollständige und unverständliche Sätze schreibe ;-)

In Teil 4 wird das Hauptprogramm besprochen. Ihm steht auch ein eigener Kursteil zu, da es alleine (vom Speicherbedarf her) so groß ist, wie die bisher besprochenen Module zusammen.

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

 go12power@hotmail.com

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.

  Download
Spielekurs_Teil3.zip
 (564 kB)
Downloadzeit: ca. 5 Min. - 28.8k / ca. 2 Min. - ISDN Downloads bisher: [ 4970 ]

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: Freitag, 14. Mai 2002