ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!
Wo wären wir wohl ohne flüssige kachel-basierte (engl.:
tile-based) Algorithmen? Es gäbe kein Ultima... kein Final
Fantasy (in den frühen Teilen wurden Kacheln verwendet)... kein
Diablo... KEIN STARCRAFT! (Könnte es sein, dass Lucky ein
Blizzard-Fan ist?) Kachelscrolling wird schon seid Jahren benutzt
und es gibt auch ein paar sehr fortgeschrittene Methoden um es
einzubinden... aber wir werden uns dieses mal mit den Grundlagen
beschäftigen!
Als erstes braucht ihr ein Tile Set (Kachel-Satz).
Hierbei handelt es sich einfach nur um ein Bitmap, in welchem
sich all eure Kacheln befinden. Für den Anfang würde ich mal
vorschlagen, dass sich alle Grafiken in einer einzigen Reihe oder
Spalte befinden, um uns die Sache zu erleichtern. Falls eure
Grafiksätze größer sein sollten, so könnt ihr das gerne
ändern.
Wählt für eure Kacheln eine Breite und Höhe aus, die sich
glatt durch die gewählte Bildschirmauflösung teilen läst. Ich
habe mich für 32x32 pixel Kacheln entschieden, weil sich daraus
20x15 Kacheln bei einer Auflösung von 640x480 ergeben. Schön :)
Bitte beachtet, dass die größe der Kacheln eine wichtige Rolle
bei der erzielten Bildwiederholrate. Kleinere Kacheln bedeuten
mehr Zeichenoperationen und ein langsameres Spiel, also seid
wachsam.
Ok, das ist der Grafiksatz, den ich für den Quellcode des Tutorials gebastelt hab. Fragt
mich bloß nicht wofür die gut sein sollen, ich bin mir selbst
nicht ganz sicher! Es sind vier Kacheln, jedes 32x32, die
übereinander angeordnet sind.
Jetzt bracuhen wir eine Karte (engl.: map). Diese Karte
beschreibt einfach nur, welche Kachel wo angezeigt werden soll...
sie definiert, was der Spieler sehen wird, wenn er durch eure
Welt läuft. Die einfachste mögliche Karte ist ein
zweidimensionales Datenfeld (engl.: array). Jedes Element dieses
Datenfeldes wird einen Verweis zu einer Kachel enthalten... das
heißt wiederum, dass wir jeder Kachel eine Zahl (oder einen
anderen Wert) zuordnen müssen. Ich werde meine Kacheln von oben
nach unten von 0-3 nummerieren. Diese Nummerierung wird uns
später die Arbeit erleichtern!
Beispieldaten für ein kleines Kartendatenfeld (5x5):
0 2 3 3 1
2 2 2 1 0
3 0 3 1 2
3 1 1 0 1
2 3 2 1 2
Es beschreibt eine Fläche von 160x160 Pixeln (die
Kachelbreite und -höhe sind je 32 Pixel und unsere Kartengröße
ist 5x5). Wenn wir also die Karte größer machen als die
Bildschirmauflösung erlaubt, müssen wir umher-scrollen um alle
Felder zu sehen! Also brauchen wir eine Zeichen-Routine, die die
Szene aufgrund der Karteninformationen und ein paar Koordinaten
erstellt. Diese Koordinaten sollen den Mittelpunkt der Szene
bilden - normalerweise handelt es sich hierbei um das
Spielerobjekt. Wenn wir diese Koordinaten nach und nach
verändert, sollten wir in der Lage sein den gewünschten
scrolling-Effekt zu erzeugen.
Es ist wichtig, dass die Zeichen-Routine mehr Kacheln zeichnen
muß als eigentlich auf den Bildschirm passen (also mehr als
20x15 Kacheln bei einer Auflösung von 640x480, die ich schon
vorhin erwähnt habe). Manchmal könnt ihr einen Teil einer
Kachel auf der einen Seite des Bildschirms sehen und einen
anderen Teil einer anderen Kachel auf der anderen Seite -
flüssiges scrollen erfordert also, dass die Kartenansicht sich
Pixel für Pixel bewegt und NICHT um ganze Kacheln! Seht hier:
Dieser Bereich befindet sich außerhalb des Bildes
(also abgetrennt)
21x16 Kacheln werden derzeit gezeichnet, aber einige werden
abgeschnitten, solange sie sich außerhalb der Ränder des Bildes
befinden.
Alles klar! Genug Theorie, lasst uns dieses Biest
programmieren! Wir müssen für jede dieser Kacheln in unserem
21x16 Feld bestimmen, welche angezeigt werden soll (indem wir die
Karte zu Rate ziehen), sie gegebenenfalls abschneiden und dann
zeichnen. Als erstes kümmern wir uns um die Funktion, die uns
die benötigte Kachelnummer zurückgibt:
Const SCREEN_WIDTH = 640
Const SCREEN_HEIGHT = 480
Const TILE_WIDTH = 32
Const TILE_HEIGHT = 32
Dim mbytMap(100, 100) As Byte
Dim mintX as Integer
Dim mintY as Integer
Private Function GetTile(intTileX As Integer, intTileY As Integer) As Integer
GetTile = mbytMap((intTileX + TILE_WIDTH \ 2 + mintX - SCREEN_WIDTH \ 2) \ TILE_WIDTH, (intTileY + TILE_HEIGHT \ 2 + mintY - SCREEN_HEIGHT \ 2) \ TILE_HEIGHT)
End Function
Okay, sieht zwar ein bißchen komisch aus, aber es klappt.
Lasst es micht erklären. intTileX und intTileY
sind die Koordinaten der Kachel die als nächstes bezeichnet
werden soll. Bei mbytMap handelt es sich um
unser Kartendatenfeld (100x100 - gehen wir mal davon aus, dass es
bereits mit Daten gefüllt ist), und mintX und mintY
sind die "Spieler"-Koordinaten. Solange der Spieler
sich in der Mitte des Bildschirms befindet, müssen wir folgende
Divisionen vornehmen: SCREEN_WIDTH \ 2 und SCREEN_HEIGHT
\ 2. Außerdem müssen wir die Hälfte (1/2) der TILE_WIDTH
und TILE_HEIGHT Werte hinzu addieren, um
sicher zu gehen, dass wir keine Rundungsfehler, durch eine
Integerdivision nahe null, haben... Glaubt mir einfach :) Oder
probiert es ohne das hier und ihr werdet feststellen, dass sich
die Kacheln auf mysteriöse Weise am oberen oder linken Rand
wiederholen.
Nachdem wir die Kombination der Koordinaten des Spielers und
der gewünschten Kachel haben, müssen wir diese noch durch die
Kachelgröße teilen, um den Indexwert, den wir für unser
Kartendatenfeld benötigen, zu erhalten. Einfach, oder? :)
Private Sub GetRect(bytTileNumber As Byte, ByRef intTileX As Integer, ByRef intTileY As Integer, ByRef rectTile As RECT)
With rectTile
.Left = 0
.Right = TILE_WIDTH
.Top = bytTileNumber * TILE_HEIGHT
.Bottom = .Top + TILE_HEIGHT
If intTileX < 0 Then
.Left = .Left - intTileX
intTileX = 0
End If
If intTileY < 0 Then
.Top = .Top - intTileY
intTileY = 0
End If
If intTileX + TILE_WIDTH > SCREEN_WIDTH Then .Right = .Right + (SCREEN_WIDTH - (intTileX + TILE_WIDTH))
If intTileY + TILE_HEIGHT > SCREEN_HEIGHT Then .Bottom = .Bottom + (SCREEN_HEIGHT - (intTileY + TILE_HEIGHT))
End With
End Sub
Diese Funktion erledigt gleich ein paar Dinge. Sie berechnet
das entsprechende Quellrechteck für unsere Zeichenzwecke
(schneidet auch bereiche ab) und korrigiert die X- und
Y-Koordinaten, falls nötig. (Beachtet, dass die meisten
Parameter als ByRef übergeben werden)
Als erstes nehmen wir den Wert aus bytTileNumber
(entweder 0, 1, 2 oder 3) den wir aus der GetTile-Funktion
erhalten haben und berechnen die Quellrechtecksgröße. Den Top-Wert
erhalten wir, indem wir einfach TILE_HEIGHT mit bytTileNumber
multiplizieren. Die Left-, Right- und Bottom-Werte können dann
ganz leicht errechnet werden.
Als nächstes müssen wir sicher stellen, dass der Teil der
Kachel, der außerhalb der Bildes liegt, abgeschnitten (engl.:
clip) wird, falls nötig. Wenn die X- und Y-Koordinaten negativ
sind, ist es sehr leicht dieses clipping durchzuführen. Sollte
sollte die Kachel aber über den rechten oder unteren
Bildschirmrand hinausragen, so wird das ganze etwas
komplizierter. Wir müssen errechnen, wie weit die Kachel über
den Rand hinausragt und das dann vom derzeitigen Rechteck
abziehen.
Private Sub DrawTiles()
Dim i As Integer
Dim j As Integer
Dim rectTile As RECT
Dim bytTileNum As Byte
Dim intX As Integer
Dim intY As Integer
For i = 0 To CInt(SCREEN_WIDTH / TILE_WIDTH)
For j = 0 To CInt(SCREEN_HEIGHT / TILE_HEIGHT)
intX = i * TILE_WIDTH - mintX Mod TILE_WIDTH
intY = j * TILE_HEIGHT - mintY Mod TILE_HEIGHT
bytTileNum = GetTile(intX, intY)
GetRect bytTileNum, intX, intY, rectTile
msurfBack.BltFast intX, intY, msurfTiles, rectTile, DDBLTFAST_WAIT
Next j
Next i
End Sub
Zu guter letzt kommt unsere Zeichen-Routine! Es geht durch
alle Kachelpositionen von null bis SCREEN_WIDTH /
TILE_WIDTH und SCREEN_HEIGHT / TILE_HEIGHT.
intX und intY werden dann
berechnet und enthalten die X- und Y-Koordinaten der oberen
linken Ecke der nächsten Kachel, die auf den Bildschirm
gezeichnet werden soll. Dies kann erreicht werden, indem wir
unseren Schleifenzähler mit der Höhe oder Breite der Kachel
multiplizieren und davon die derzeitige Position (der Abstand der
Spielerkoordinaten vom Gitternetz) subtrahieren. Danach rufen wir
unsere GetTile- und GetRect-Funktionen
auf und schließlich... zeichen wir das Bild!
Das war alles, was man über einfaches Kachel-scrolling wisses
sollte. Klickt hier um den
Tutorial-Quellcode herunterzuladen. Wie ich schon am Anfang
erwähnt habe, gibt es fortgeschrittenere und effizientere
Techniken. Falls euch die frame rate wichtig ist, würde ich
folgendes vorschlagen: Anstatt für jedes Bild jede Kachel auf
den Backbuffer zu zeichnen, solltet ihr lieber eine
"Offscreen Surface" (siehe DirectX) erstellen und nur
auf diese blitten wenn es unbedingt notwendig ist. Diese
Surface kann dann benutzt werden, um den Backbuffer in einem
Schwung zu aktualisieren (einmal ein großes Objekt zu zeichnen
ist schneller als viele kleine zu zeichnen). Diese Surface muß
jedesmal aktualisiert werden, wenn der Spieler sich über die
derzeitige Kachel hinaus bewegt. Das erfordert zwar ein bißchen
mehr Leistung von euch (mein Quellcode nutzt nicht die Vorteile
dieser Methode), aber es sollte eure FPS deutlich erhöhen.