Основы тайловой графики
VB Edition
Приветствую!
Сначала я хотел назвать эту статью "Программирование тайловой графики
с помощью DirectDraw для Visual Basic", но потом подумал, что это звучит
слишком в стиле зав.кафедрой Информационных Технологий и Электронно Вычислительных
Устройств - профессора Ивана Васильевича Зелепукина, то-бишь слишком умно и
длинно. Поэтому я назвал эту статью просто - "Основы тайловой графики".
Эта статья предназначена для тех, кто уже знаком с основами программирования
DirectDraw. Ежели нет, вам лучше сначала прочитать мой учебник DirectDraw, находящийся
тут.
Итак, если вы уже знакомы с основами DirectDraw, то скорее всего вы уже знаете
что такое тайлы. Ежели нет, то наверняка догадываетесь. Вобщем тайлы - это такой
небольшой прямоугольник (чаще квадрат) содержащий некое графическое изображение.
Это изображение может быть размножено на какой-то области (экране), в совокупности
с другими тайлами создавая изображение. Если вы уже третий раз перечитываете
эти строки и все-равно не можете понять в чем дело, то вспомните какую-нибудь
real-time стратегию, наподобии Warcraft, в которую вы играли. Помните карту?
Помните, какое там все квадратное - квадрас леса, квадрат травы? Так вот, эти
квадраты и есть тайлы, а карта целиком из них состоит.
Тайлы нужны для того, чтобы не рисовать все карту ручками в пайнтбраше. Вы
рисуете заготовки, эдакие двухмерные кубики, и затем составляете из них карту
в специально придуманном редакторе, но это уже мелочи. Смысл в том, что это
коллосально экономит место и время обработки. Конечно из-за этого возможность
составлять уникальные и неповторимые пейзажи очень затруднительна, однако тайл
- это второе после спрайта слово в двухмерной графике. Практически все 2D-игры,
в которых происходит скроллинг базируются на тайлах.
Тайлы - это карты стратегий, бесконечно скроллирующиеся уровни аркад, и даже
подземелья Дума, но только тайлы в последнем уже трехмерные.
В качестве наглядного пособия посмотрите на эти рисунки. 
Вы видите что можно сотворить всего из четырех тайлов. При всем этом нам понадобится
видеопамять, чтобы хранить четыре тайла, а также память, которая потребуется
для хранения матрицы с индексами этих тайлов - карты. А теперь представьте,
сколько видеопамяти мы бы положили, если бы рисовали эту карту в ручную.
Так, теперь к делу! Чему будет учить вас эта статья: далее мы рассмотрим как
сгенерировать карту из четырех тайлов, которые видите чуть выше. Кроме этого
мы еще заставим карту плавно скроллироваться, подчиняясь клавишам курсора. В
качестве подзадачи, нам надо обеспечить "обрезание" тайлов так, чтобы
в видимый экран влезала часть тайла - это для того, чтобы нам не прыгать при
скроллинге через тайл, а перемещаться по карте плавно.
А теперь внимание! Этот туториал будет использовать модуль mdlDirectDraw7,
создание которого подробно описано в учебнике
по DirectDraw. В этом модуле находятся процедуры, необходимые для работы
с объектами DirectX. Модуль можно найти в архиве с примером к этой статье. Ссылку
смотрите в самом низу. Также вам понадобится библиотека Win32.tlb для работы
этого модуля. Если у вас ее еще нет, то скачайте и поместите в директорию Windows\System
Ссылка в конце статьи.
Итак.
Создайте новый проект, подключите библиотеки DirectX7 for VB и Win32 Type Library.
Добавьте готовый модуль mdlDirectDraw7.mdl и сохраните все это дело. В папке
с проектом у вас должна лежать картинка с тайлами. Можете взять ее из архива
с примером, а можете нарисовать сами. Все тайлы размерами 32x32 образуют квадрат
размером 64x64.
Для начала напишем декларации:
'Определяем нашу карту
Dim Map(30, 30) As Byte
'Количество колонок тайлов на поверхности ddsTiles
Const TileColumns As Integer = 2
Const TileWidth As Integer = 32
Const TileHeight As Integer = 32
Dim XOffset As Integer, YOffset As Integer '"Проскролленные"
отступы от краев карты
Dim MapClipLeft As Integer, MapClipTop As Integer 'Далее
- координаты "видимого"
Dim MapClipWidth As Integer, MapClipHeight As Integer
'окна
Dim bQuit As Boolean 'Пора выходить?
'Флаги движения по направлениям
Dim bGoingLeft As Boolean
Dim bGoingUp As Boolean
Dim bGoingDown As Boolean
Dim bGoingRight As Boolean
'Поверхность DD для хранения тайлов
Dim ddsTiles As DirectDrawSurface7
Теперь будем писать по одной вспомогательные процедуры.
Первая процедура генерирует случайную карту. Вы можете сами придумать способ,
составляющий карты, например загружая их из файла, однако в нашем случае это
несущественно.
'Эта процедура генерирует случайную карту
Sub GenerateMap()
Dim X As Integer, Y As Integer
'Для каждого тайла на карте
For Y = LBound(Map, 2) To UBound(Map, 2)
For X = LBound(Map, 1) To UBound(Map, 1)
'Взять случайное значение между 0 и 3 (включительно)
'для тайла. Наибольшее предпочтение тайлу "пустоты" (0), а наименьшее
'предпочтение тайлу "ключа" (3) - для этого используем
'квадратный корень.
Map(X, Y) = 3 - Int(Sqr(Rnd(1) * 16))
Next X
Next Y
End Sub
Хитрая формула нахождения случаного значения нужна для того, чтобы в нашей
карте было побольше свободных мест. Опять же вы можете придумать свою формулу
для нахождения случайных значений.
'Эта процедура ищет координаты тайла #TileIndex и помещает
их в структуру RECT
'(координаты ищутся на поверхности ddsTile)
Private Sub GetTileRect(ByVal TileIndex As Byte, ByRef rcTile As RECT)
rcTile.Left = (TileIndex Mod TileColumns) * TileWidth
rcTile.Top = Int(TileIndex / TileColumns) * TileHeight
rcTile.Right = rcTile.Left + TileWidth
rcTile.Bottom = rcTile.Top + TileHeight
End Sub
Самая важная процедура. Она будет копировать с поверхности тайлов на задний
буфер собственно сами тайлы, попутно вычисляя по какую координату надо обрезать
карту.
'Нарисовать карту, хранящуюся в массиве Map() с отступами
от краев XOffset, YOffset
'Точка верхнего левого угла экрана на карте находится в MapClipLeft, MapClipTop,
'а размер видимого экрана в MapClipWidth и MapClipHeight
Sub DrawMap()
Dim rcSource As RECT 'Координаты на ddsTile тайла, который будем рисовать
'Координаты на ddsPrimary - куда рисовать
Dim DestX As Integer, DestY As Integer
Dim UseWidth As Integer, UseHeight As Integer
Dim Y As Integer
Dim X As Integer
'Пройти через каждый из видимых тайлов и нарисовать
их правильно обрезанными.
'Обрабатываются только тайлы, которые входят в видимое "окно"
'Int(YOffset / TileHeight) вычисляет самый верхний видимый ряд тайлов в Map()
'Int((YOffset + MapClipHeight - 1) / TileHeight) самый нижний
'Int(XOffset / TileWidth) самую левую видимую колонку
'Int((XOffset + MapClipWidth - 1) / TileHeight) самую правую
For Y = Int(YOffset / TileHeight) To Int((YOffset + MapClipHeight - 1) / TileHeight)
For X = Int(XOffset / TileWidth) To Int((XOffset + MapClipWidth - 1) / TileHeight)
'Получить координаты картинки-тайла на ddsTile исходя
из индекса
'записанного в текущей позиции карты
If X > UBound(Map, 1) Or Y > UBound(Map, 2) Then Exit Sub
GetTileRect Map(X, Y), rcSource
'Вычислить координаты копирования для BltFast
DestX = X * TileWidth - XOffset + MapClipLeft
DestY = Y * TileHeight - YOffset + MapClipTop
'Теперь нам надо правильно "обрезать" тайлы,
выходящие за
'видимую область
UseWidth = TileWidth
UseHeight = TileHeight
If DestX < MapClipLeft Then
rcSource.Left = rcSource.Left + (MapClipLeft - DestX)
UseWidth = UseWidth - (MapClipLeft - DestX)
'Теперь левой координатой для BltFast будет левая
сторона карты
DestX = MapClipLeft
'Если все правильно, мы никогда не должны получить
тайлы, которые
'целиком "вырежутся" из видимой области
Debug.Assert rcSource.Left < rcSource.Right
End If
If DestY < MapClipTop Then 'Теперь по вертикали
rcSource.Top = rcSource.Top + (MapClipTop - DestY)
UseHeight = UseHeight - (MapClipTop - DestY)
DestY = MapClipTop
Debug.Assert rcSource.Top < rcSource.Bottom
End If
'Правая сторона карты находится в MapClipLeft + MapClipWidth
If DestX + UseWidth > MapClipLeft + MapClipWidth Then
rcSource.Right = rcSource.Right - (DestX + _
UseWidth - (MapClipLeft + MapClipWidth))
End If
If DestY + UseHeight > MapClipTop + MapClipHeight Then
'Теперь по вертикали
rcSource.Bottom = rcSource.Bottom - (DestY + _
UseHeight - (MapClipTop + MapClipHeight))
End If
'Скопировать тайл с поверхности ddsTile на его место
на заднем буфере
'Координаты тайла уже автоматически "обрезаны", поэтому все должно
работать
ddsBack.BltFast DestX, DestY, ddsTiles, rcSource, DDBLTFAST_WAIT
Next X
Next Y
End Sub
Следующие две процедуры обрабатывают нажатия клавиш. При нажатии Escape происходит
выход из цикла рисования и завершение программы.
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
'Что это мы там нажали?
Select Case KeyCode
Case vbKeyEscape
bQuit = True
Case vbKeyLeft
bGoingLeft = True
Case vbKeyRight
bGoingRight = True
Case vbKeyUp
bGoingUp = True
Case vbKeyDown
bGoingDown = True
End Select
End Sub
Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
'Что это мы там отпустили?
Select Case KeyCode
Case vbKeyLeft
bGoingLeft = False
Case vbKeyRight
bGoingRight = False
Case vbKeyUp
bGoingUp = False
Case vbKeyDown
bGoingDown = False
End Select
End Sub
Теперь, процедура Form_Load. После инициализации DirectDraw в полноэкранном
режиме, мы загружаем графику тайлов на специально созданную поверхность и запускаем
цикл рисования, предварительно указав видимую область. Вам не обязательно распахивать
ее на весь экран, вы можете определить какую-то свою часть экрана, а остальное
отвести под статус-бар или еще что-нибудь в этом роде.
Private Sub Form_Load()
'Инициализировать приложение в полноэкранном режиме
mdlDirectDraw7.CreateDDFullscreen Me.hWnd, 640, 480, 16
'Загрузить графику
Set ddsTiles = mdlDirectDraw7.CreateDDSFromFile(App.Path & "\tiles.bmp")
GenerateMap 'Генерация карты
'Главный цикл
bQuit = False
Do
'Установить видимое "обрезающее" окно внутри
формы
MapClipLeft = ScaleX(Me.Left, Me.ScaleMode, vbPixels) + 4
MapClipTop = ScaleY(Me.Top + (Me.Height - Me.ScaleHeight), _
Me.ScaleMode, vbPixels) - 4
MapClipWidth = ScaleX(Me.ScaleWidth, Me.ScaleMode, vbPixels)
MapClipHeight = ScaleY(Me.ScaleHeight, Me.ScaleMode, vbPixels)
'Это, если вы будете пытаться переводить эту программу
в оконный режим
If MapClipLeft < 0 Then MapClipWidth = MapClipWidth + _
MapClipLeft: MapClipLeft = 0
If MapClipTop < 0 Then MapClipHeight = MapClipHeight + _
MapClipTop: MapClipTop = 0
If MapClipTop + MapClipHeight > ScaleY(Screen.Height, _
vbTwips, vbPixels) - 1 Then
MapClipHeight = ScaleY(Screen.Height, vbTwips, vbPixels) _
- 1 - MapClipTop
End If
If MapClipLeft + MapClipWidth > ScaleX(Screen.Width, vbTwips, _
vbPixels) - 1 Then
MapClipWidth = ScaleX(Screen.Width, vbTwips, vbPixels) - _
1 - MapClipLeft
End If
'Двигаем карту в зависимости от нажатых клавиш
If bGoingUp Then YOffset = YOffset - 2
If bGoingLeft Then XOffset = XOffset - 2
If bGoingRight Then XOffset = XOffset + 2
If bGoingDown Then YOffset = YOffset + 2
'Пофиксить некорректные координаты карты
If YOffset < 0 Then YOffset = 0
If XOffset < 0 Then XOffset = 0
If XOffset + MapClipWidth > (UBound(Map, 1) + 1) * TileWidth _
Then XOffset = (UBound(Map, 1) + 1) * TileWidth - _
MapClipWidth
If YOffset + MapClipHeight > (UBound(Map, 2) + 1) * _
TileHeight Then YOffset = (UBound(Map, 2) + 1) * _
TileHeight - MapClipHeight
'Рисовать карту
mdlDirectDraw7.ClearBuffer ddsBack
DrawMap
ddsPrimary.Flip Nothing, DDFLIP_WAIT
DoEvents
Loop Until bQuit
Unload Me
End Sub
И наконец, для правильного выхода, нам надо написать кое-что в Form_Unload
Private Sub Form_Unload(Cancel As Integer)
Set ddsTiles = Nothing
mdlDirectDraw7.DestroyDD
End Sub
Запускайте проект и Ура! Заработало!
Понажимайте на курсорные клавиши, попробуйте "проехать" до конца карты.
Теперь вы знакомы с одним из алгоритмов создания и скроллинга тайловой карты.
Следующим этапом может быть добавление на карту бегающего человечка, его врагов
и т.п. Советую прочитать третью часть учебника
по DirectDraw, там есть некоторые соображения по этому поводу.
В качестве упражнения:
- Придумайте алгоритм для загрузки карты из файла
- Поменяйте координаты "видимой области"
- Добавьте на карту кого-нибудь. Пусть он бегает и наталкивается на некие
"препятствия"
Пока вроде бы все.
Мое спасибо: Benjamin Marty и его замечательному туториалу :)
Приложения:
Готовый
проект для этой статьи
Библиотека
Win32.tlb
Приятного программирования, Antiloop
Posted: 23.01.2k1
Autor: Antiloop
<anti_loop@mail.ru>
|