Version 2.0!
Features
Tutorials
Files
Glossary
Projects
Contact
Links
Message Board
Extras
LuckyCam
Old News
Sign Guestbook
View Guestbook
VB Horoscope
VB Photo Album
.
ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!

Flüssig scrollende Kacheln (smooth tile scrolling)

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.