3.4 |
Die Objekte |
[ Top ]
|
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?
|
|
|