|
|
Тайловая игра на BlitzMax, часть 2
Материал из Blitz Et Cetera
Введение
Привет! Это снова я, dimanche13, и продолжение моего тутора по созданию движка игры на основе тайлов. Увы, вдохновение посещает меня редко и перерыв между первой частью и второй получился большим. Но, думаю, все, кому пригодился прошлый тутор, получили время на размышление и осмысление. Даже, наверное, провели свои эксперемнты над кодом, которые дают драгоценный опыт и ничем не заменимую радость от проделанной работы. На этот раз я расскажу, как делать скроллинг тайловой карты и её масштабирование. Это самые частые вопросы начинающих на форумах.
Немного о ViewPort-е
Что же, начнем мы пожалуй из далека. Сначала я хочу рассказать вам о командах SetViewPort и GetViewPort. Как вы можете догадаться из названия, это установка (Set) и чтение(Get) параметров ViewPort-а. Что же такое ViewPort ? Это прямоугольник, в котором отображаются все объекты по команде Draw. Это относится и к картинкам TImage и к примитивам Oval, Rect и другим. То есть другими словами, это порт вывода информации на экран, то что не попадает в этот прямоугольник — рисоваться не будет. При запуске программы, по умолчанию, размеры ViewPort-а соответствуют размерам окна. Это легко проверить. Скомпилируйте и запустите вот эту программу:
SuperStrict
SuperStrict
' ========== Параметры графики ===========
Global ScrWidth:Int = 640
Global ScrHeight:Int = 480
'=======================================
' инициализация графики
Graphics(ScrWidth, ScrHeight)
' главный цикл
Repeat
Cls()
SetColor(255,0,0)
DrawRect(50,180,150,150)
SetColor(0,255,0)
DrawOval(250,180,150,150)
SetColor(0,0,255)
DrawRect(450,180,150,150)
Flip()
Until ( KeyDown(KEY_ESCAPE) Or AppTerminate())
End
Как видите программа выводит на экран 3 примитива разного цвета.
Ок. А теперь давайте установим наш собственный ViewPort и посмотрим как он отсечет все детали, которые в него не попадут. Допишите эту строку, после установки графики.
SetViewport(100,200,450,100)
Теперь снова компильте и запускайте.
Ага, видите, ViewPort обрубил наши объекты (обрубленные части я изобразил более темным цветом, для наглядности, потому что на экране вы их не увидите). Теперь закомментируйте SetViewPort и снова запустите. Все нормально, примитивы отображаются полностью. Как на рисунке 1. Думаю, здесь все понятно. Параметры функции:
- SetViewPort(startX, startY, Width, Height)
- startX,startY координаты верхнего угла,
- Width - ширина ViewPort-a,
- Height - высота ViewPort-a.
Скроллинг
Теперь пробежимся немножко по геометрии. Все мы ее изучали в школе, ничего сложного и тем более нового я вам не поведаю, просто освежим наши знания. Итак для двумерного пространства мы имеем систему координат. Это две оси, пересекающиеся в точке (0,0). Они абсолютно перпендикулярны друг другу. Первая ось зовется Х и идет вертикально, вторая зовется Y и проходит горизонтально. Вот я изобразил так.
Это называется "Декартова система координат".
Для чего вообще нужны координатные оси? Для того, чтобы можно было четко сказать, где на данный момент находится определенная точка в пространстве. Представьте, что перед вами чистый лист бумаги. А на нем, в случайном месте стоит точка А. Нельзя однозначно сказать, где конкретно на листе она находится. Так если я вас попрошу указать мне, местоположение этой точки, вы наверное затруднитесь с ответом ;) Но как только мы нарисуем систему координат, к примеру в центре листа и обозначим координатные оси, сразу же четко и уверенно сможем сказать, что точка А располагается по координатам (5,8) .
Итак, система координат нужна для того, чтобы охарактеризовать положение точки в пространстве и выразить это положение через цифры.
Координаты нашей точки могут измениться в двух случаях:
- если мы передвинем эту точку в пространстве
- и если мы сместим начало системы координат в другое место
По первому пункту и так все понятно. Если мы передвинем точку, то и координаты ее изменятся. Все согласны? А вот второе уже интереснее: передвинув систему координат в другое место, а точку оставив на прежнем, мы получим для нее уже другие координаты. Правильно, ведь мы же поменяли систему отсчета.
Напомню вам, что плоскость монитора - это та же двумерная плоскость, на которую проецируется изображение. Соответственно, эта плоскость тоже имеет свои координатные оси. Система координат начинается в левом верхнем углу. То есть самая верхняя левая точка(пиксель) имеет координаты (0,0). Но направление осей здесь немного отличается от привычной нам школьной системы. Ось Y имеет направление увеличения координат — вниз, а не вверх.
Так что же такое скроллинг тайловой карты? Скроллинг - это плавное изменение координат отрисовки тайлов, в нужном направлении. Так если мы, к примеру, нажмем кнопку вправо, то все наши тайлы переместятся ... нет не вправо, а влево. И отрисуются немного левее прошлого положения (проверьте это на любой карте в любой игре). Тогда напрашивается такое решение для скроллинга карты: при нажатии на кнопки управления мы будем изменять координаты тайлов, обратно направлению взгляда.
If( KeyDown(KEY_LEFT ) ) Then CameraX = CameraX - 5
If( KeyDown(KEY_RIGHT) ) Then CameraX = CameraX + 5
If( KeyDown(KEY_UP) ) Then CameraY = CameraY - 5
If( KeyDown(KEY_DOWN ) ) Then CameraY = CameraY + 5
Тогда при выводе тайлов и всех объектов карты на экран (в методе Draw() ), надо будет высчитывать их новое положение, то есть изменять x и y объекта на экране. Вот так: drawRect(x – CameraX, y – CameraY, w, h), от каждой x и y координаты необходимо отнимать координаты сдвига. Ок. Вот весь листинг программы.
SuperStrict
Global ScrWidth : Int = 640
Global ScrHeight : Int = 480
Global CameraX:Int = 0
Global CameraY:Int = 0
Global ALLGameObjList : TList = CreateList()
Const TILESIZE:Int = 32
Type TBase
Field x:Int
Field y:Int
Field Width:Byte
Field Height:Byte
Method update() Abstract
Method draw() Abstract
End Type
Type TTile Extends TBase
Field xTile:Int
Field yTile:Int
Field Walkable:Byte
Function Create(sx:Int, sy:Int, WB:Byte)
Local TT:TTile = New TTile
TT.width = TiLESIZE
TT.height = TiLESIZE
TT.xTile = sx
TT.yTile = sy
TT.x = TT.xTile * TT.width
TT.y = TT.yTile * TT.height
TT.Walkable = WB
ListAddLast(ALLGameObjList , TT)
End Function
Method update()
If walkable 'not walkable
SetColor(255 , 0 , 0) 'red
Else ' walkable
SetColor(0 , 255 , 0) 'green
End If
End Method
Method Draw()
DrawRect(x - CameraX,y - CameraY,Width,Height)
End Method
End Type
Type TLevel
Field Width : Byte
Field Height : Byte
Field Map:Int[,]
Function Create:TLevel(MyMap:Int[],map_width:Int,map_height:Int)
Local TM : TLevel = New TLevel
TM.Width = map_width
TM.Height = map_height
TM.map = New Int [TM.Width, TM.Height]
TM.Load(myMap)
Return TM
End Function
Method Load(arrMap:Int[])
For Local i:Int = 0 Until Height
For Local j:Int = 0 Until Width
Map[j , i] = arrMap[j + (i * Width)]
If Map[j , i]
TTile.Create(j , i , True)
Else
TTile.Create(j , i , False)
End If
Next
Next
End Method
Method Render()
For Local CurObj:TBase = EachIn ALLGameObjList
CurObj.Update()
CurObj.Draw()
Next
End Method
End Type
Type TBonus Extends TBase
Field points:Int
Field miny:Int
Field maxy:Int
Field diry:Int
Field speed:Int
Field AnimDelay:Int
Function Create(sx:Int,sy:Int,pts:Int)
Local TB : TBonus = New TBonus
TB.Width = 6
TB.Height = 16
TB.x = sx * TiLESIZE + ((TiLESIZE - TB.width) / 2) 'center it
TB.y = sy * TiLESIZE + ((TiLESIZE - TB.height) / 2) 'center it
TB.points = pts
TB.miny = TB.y - 4
TB.maxy = TB.y + TB.Height + 4
TB.diry = -1
TB.speed = 1
TB.AnimDelay = 5
ListAddLast(ALLGameObjList , TB)
End Function
Method Update()
If(Animdelay < 0)
If ( miny >= (y) Or maxy <= (y + height) )
diry = -diry
End If
y = y + speed * diry
AnimDelay = 5
End If
AnimDelay :- 1
End Method
Method Draw()
SetColor(255 , 255 , 0)
DrawRect(x - CameraX,y - CameraY,Width,Height)
End Method
End Type
Type TMovingX Extends TBase
Field mindir:Int
Field maxdir:Int
Field dirx:Int
Field speed:Int
Function Create(xt:Int , yt:Int , mn:Int , mx:Int , d:Int)
Local TMX:TMovingX = New TMovingX
TMX.x = xt * TILESIZE
TMX.y = yt * TILESIZE
TMX.width = TILESIZE
TMX.height = TILESIZE
TMX.speed = 1
TMX.dirx = d
TMX.mindir = (mn + xt) * TILESIZE
TMX.maxdir = (mx + xt + 1) * TILESIZE
ListAddLast(ALLGameObjList , TMX)
End Function
Method Update()
If( mindir >= x Or maxdir <= (x + width) )
dirx = - dirx
EndIf
x :+ speed * dirx
End Method
Method Draw()
SetColor(128 , 128 , 128) ' gray
DrawRect(x - CameraX , y - CameraY , Width , Height)
SetColor(255 , 255 , 255) ' white line
DrawLine(x - CameraX , y - CameraY , (x - CameraX) + width - 1 , y- CameraY)
End Method
End Type
Global LevMap:Int[] = [1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , ..
1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , ..
1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , ..
1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 0 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , ..
1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , ..
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1]
Global MyLevel : TLevel = New TLevel.Create(LevMap , 20 , 10)
Function Init_All_Bonuses()
TBonus.Create(1 , 1 , 25)
TBonus.Create(3 , 8 , 25)
TBonus.Create(4 , 8 , 25)
TBonus.Create(5 , 8 , 25)
TBonus.Create(15 , 1 , 100)
TBonus.Create(7 , 4 , 100)
TBonus.Create(9 , 7 , 10)
TBonus.Create(4 , 6 , 10)
TBonus.Create(9 , 2 , 10)
TBonus.Create(14 , 7 , 10)
TBonus.Create(17 , 6 , 10)
TBonus.Create(17 , 4 , 10)
TBonus.Create(18 , 4 , 10)
TBonus.Create(3 , 3 , 10)
End Function
TMovingX.Create(13 , 3 , - 2 , 5 , 1)
'----------------------------- main loop -----------------------------
Graphics(ScrWidth, ScrHeight)
Init_All_Bonuses()
Repeat
Cls
If( KeyDown(KEY_LEFT) ) Then CameraX :- 5
If( KeyDown(KEY_RIGHT) ) Then CameraX :+ 5
If( KeyDown(KEY_UP) ) Then CameraY :- 5
If( KeyDown(KEY_DOWN) ) Then CameraY :+ 5
MyLevel.Render()
Flip
Until KeyDown(KEY_ESCAPE) Or AppTerminate()
End
Как вы уже догадались, мы сделали скроллинг первым методом, то есть изменением координат объектов. А почему бы не сделать теперь скроллинг вторым методом: изменением начала система координат? Ок, приступим. А, нет, погодите, сначала я должен вам показать, какая функция отвечает за это в БМаксе. Вот она: SetOrigin(newX, newY) – эта функция устанавливает начало системы координат в точке newX , newY. Чтобы узнать где сейчас начало координат, надо воспользоваться функцией GetOrigin(GetX, GetY). Привыкайте к функциям начинающимся со слов Set и Get. Первые устанавливают что-то, вторые - это что-то возвращают.
Давайте проведем маленький тест, и заодно представим как эти функции работают. Вот простенькая програмка. Она рисует синий квадрат начиная с координат 30,30. Система координат начинается здесь с 0,0. Затем, начало системы координат мы переносим в центр экрана и рисуем еще один квадрат красного цвета, опять же начиная с тех координат, что и первый квадрат 30,30. Но так как отсчет будет идти уже от центра, то он нарисуется в другом месте. Думаю и здесь все понятно. Поэксперементируйте с кодом. Надо лишь добавить, что при установке графического режима, начало системы координат устанавливается по-умолчанию в (0,0).
SuperStrict
' ========== Параметры графики ===========
Global ScrWidth:Int = 640
Global ScrHeight:Int = 480
'=======================================
AppTitle = "Test 003"
' инициализация графики
Graphics(ScrWidth, ScrHeight)
Cls()
SetColor(0,0,255)
DrawRect(30,30,100,100)
SetOrigin(ScrWidth / 2, ScrHeight / 2)
SetScale(0.5 , 0.5)
SetColor(255,0,0)
DrawRect(30,30,100,100)
Flip()
WaitKey()
End
Так как в этом методе скроллинга, меняется только начало системы координат, то объекты в пространстве нам двигать не надо, как в первом случае. И соответственно ничего прибавлять или отнимать от их координат тоже не надо. Их x и y остаются теми же что и были. Ок. Вот листинг второго способа скроллинга.
SuperStrict
Global ScrWidth : Int = 640
Global ScrHeight : Int = 480
Global CameraX:Int = 0
Global CameraY:Int = 0
Global ALLGameObjList : TList = CreateList()
Const TILESIZE:Int = 32
Type TBase
Field x:Int
Field y:Int
Field Width:Byte
Field Height:Byte
Method update() Abstract
Method draw() Abstract
End Type
Type TTile Extends TBase
Field xTile:Int
Field yTile:Int
Field Walkable:Byte
Function Create(sx:Int, sy:Int, WB:Byte)
Local TT:TTile = New TTile
TT.width = TiLESIZE
TT.height = TiLESIZE
TT.xTile = sx
TT.yTile = sy
TT.x = TT.xTile * TT.width
TT.y = TT.yTile * TT.height
TT.Walkable = WB
ListAddLast(ALLGameObjList , TT)
End Function
Method update()
If walkable 'not walkable
SetColor(255 , 0 , 0) 'red
Else ' walkable
SetColor(0 , 255 , 0) 'green
End If
End Method
Method Draw()
DrawRect(x ,y ,Width,Height)
End Method
End Type
Type TLevel
Field Width : Byte
Field Height : Byte
Field Map:Int[,]
Function Create:TLevel(MyMap:Int[],map_width:Int,map_height:Int)
Local TM : TLevel = New TLevel
TM.Width = map_width
TM.Height = map_height
TM.map = New Int [TM.Width, TM.Height]
TM.Load(myMap)
Return TM
End Function
Method Load(arrMap:Int[])
For Local i:Int = 0 Until Height
For Local j:Int = 0 Until Width
Map[j , i] = arrMap[j + (i * Width)]
If Map[j , i]
TTile.Create(j , i , True)
Else
TTile.Create(j , i , False)
End If
Next
Next
End Method
Method Render()
For Local CurObj:TBase = EachIn ALLGameObjList
CurObj.Update()
CurObj.Draw()
Next
End Method
End Type
Type TBonus Extends TBase
Field points:Int
Field miny:Int
Field maxy:Int
Field diry:Int
Field speed:Int
Field AnimDelay:Int
Function Create(sx:Int,sy:Int,pts:Int)
Local TB : TBonus = New TBonus
TB.Width = 6
TB.Height = 16
TB.x = sx * TiLESIZE + ((TiLESIZE - TB.width) / 2) 'center it
TB.y = sy * TiLESIZE + ((TiLESIZE - TB.height) / 2) 'center it
TB.points = pts
TB.miny = TB.y - 4
TB.maxy = TB.y + TB.Height + 4
TB.diry = -1
TB.speed = 1
TB.AnimDelay = 5
ListAddLast(ALLGameObjList , TB)
End Function
Method Update()
If(Animdelay < 0)
If ( miny >= (y) Or maxy <= (y + height) )
diry = -diry
End If
y = y + speed * diry
AnimDelay = 5
End If
AnimDelay :- 1
End Method
Method Draw()
SetColor(255 , 255 , 0)
DrawRect(x ,y ,Width,Height)
End Method
End Type
Type TMovingX Extends TBase
Field mindir:Int
Field maxdir:Int
Field dirx:Int
Field speed:Int
Function Create(xt:Int , yt:Int , mn:Int , mx:Int , d:Int)
Local TMX:TMovingX = New TMovingX
TMX.x = xt * TILESIZE
TMX.y = yt * TILESIZE
TMX.width = TILESIZE
TMX.height = TILESIZE
TMX.speed = 1
TMX.dirx = d
TMX.mindir = (mn + xt) * TILESIZE
TMX.maxdir = (mx + xt + 1) * TILESIZE
ListAddLast(ALLGameObjList , TMX)
End Function
Method Update()
If( mindir >= x Or maxdir <= (x + width) )
dirx = - dirx
EndIf
x :+ speed * dirx
End Method
Method Draw()
SetColor(128 , 128 , 128) ' gray
DrawRect(x , y , Width , Height)
SetColor(255 , 255 , 255) ' white line
DrawLine(x , y , x + width - 1 , y)
End Method
End Type
Global LevMap:Int[] = [1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , ..
1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , ..
1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , ..
1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 0 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , ..
1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , ..
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1]
Global MyLevel : TLevel = New TLevel.Create(LevMap , 20 , 10)
Function Init_All_Bonuses()
TBonus.Create(1 , 1 , 25)
TBonus.Create(3 , 8 , 25)
TBonus.Create(4 , 8 , 25)
TBonus.Create(5 , 8 , 25)
TBonus.Create(15 , 1 , 100)
TBonus.Create(7 , 4 , 100)
TBonus.Create(9 , 7 , 10)
TBonus.Create(4 , 6 , 10)
TBonus.Create(9 , 2 , 10)
TBonus.Create(14 , 7 , 10)
TBonus.Create(17 , 6 , 10)
TBonus.Create(17 , 4 , 10)
TBonus.Create(18 , 4 , 10)
TBonus.Create(3 , 3 , 10)
End Function
TMovingX.Create(13 , 3 , - 2 , 5 , 1)
'----------------------------- main loop -----------------------------
Graphics(ScrWidth, ScrHeight)
Init_All_Bonuses()
Repeat
Cls
If( KeyDown(KEY_LEFT) ) Then CameraX :- 5
If( KeyDown(KEY_RIGHT) ) Then CameraX :+ 5
If( KeyDown(KEY_UP) ) Then CameraY :- 5
If( KeyDown(KEY_DOWN) ) Then CameraY :+ 5
SetOrigin( -CameraX , -CameraY )
MyLevel.Render()
Flip
Until KeyDown(KEY_ESCAPE) Or AppTerminate()
End
Зумминг
Видели движок Матвея к игре про злобных колобков? Просто офигительный эффект приближения и удаления камеры, не правда ли? Признаюсь, меня он очень впечатлил, и я захотел сделать такой же. Сейчас мы с вами займемся его реализацией. Но для начала, уже по традиции, я вам расскажу какие функции в Бмаксе отвечают за увеличение/уменьшение масштаба. SetScale(Xscale,Yscale) – это установка коэффициента масштаба, и GetScale(Xscale, Yscale) - чтение текущего коэффициента масштаба. Как вы могли уже заметить, у этих функций 2 параметра. Первый отвечает за масштабирование по оси Х, а второй по оси У. Это значит, что мы можем масштабировать объект непропорционально, то есть без соблюдения пропорций, на каждую ось - свой коффициент. Эти коэффициенты имеют тип float, то есть - это вещественные числа. При коэффициенте 1.0 не будет ни увеличения, ни уменьшения масштаба, при 2.0 будет увеличение в 2 раза, при 0.5 уменьшение в 2 раза (0.5 = ½). Проведем еще один маленький тестик. Как говорится “лучше 1 раз увидеть, чем 100 раз услышать” :
SuperStrict
SuperStrict
' ========== Параметры графики ===========
Global ScrWidth:Int = 640
Global ScrHeight:Int = 480
'=======================================
AppTitle = "Test 004"
' инициализация графики
Graphics(ScrWidth, ScrHeight)
Cls()
SetScale(1.0 , 1.0)'один к одному
SetColor(0,0,255)
DrawRect(50,140,100,100)
SetScale(0.5 , 0.5)' в два раза меньше
SetColor(255,0,0)
DrawRect(250,140,100,100)
SetScale(2.0 , 2.0)' в два раза больше
SetColor(0,255,0)
DrawRect(400,140,100,100)
Flip()
WaitKey()
End
Как видите (по коду программы), все три квадрата имеют одинаковые размеры - 100 на 100. Но в связи с тем, что перед их отрисовкой стоят функции масштабирования(SetScale), они выводятся разного размера. Один (красный) в 2 раза меньше – 0.5, а другой (зеленый) в 2 раза больше 2.0. Поэкспериментируйте с кодом, задавая разный масштаб квадратов для каждой оси.
Ну теперь все вроде понятно с зумингом, нам надо просто создать переменную масштаба Zoom:Float , которая будет хранить текущий масштаб карты, и прицепить изменение этой переменной на кнопки. Вот так:
If( KeyDown(KEY_A) ) Then Zoom :+ 0.01
If( KeyDown(KEY_Z) ) Then Zoom :- 0.01
а перед отрисовкой всех объектов впиндюрим SetScale( Zoom , Zoom ). Да, это все так. Но не забывайте, что при введении масштаба, мы вводим как бы третье измерение для нашей карты, а это надо учитывать. Ведь чем меньше масштаб карты тем плотнее тайлы друг к другу, соответственно их координаты подвержены влиянию коэффициента зума. Проходим по всем функциям отрисовки объектов и домножаем x и y координаты на наш коэффициент масштаба, вот так:
x * Zoom, y * Zoom
Весь листинг программы здесь:
SuperStrict
Global ScrWidth : Int = 640
Global ScrHeight : Int = 480
Global CameraX:Int = 0
Global CameraY:Int = 0
Global Zoom:Float = 1.0
Global ALLGameObjList : TList = CreateList()
Const TILESIZE:Int = 32
Type TBase
Field x:Int
Field y:Int
Field Width:Byte
Field Height:Byte
Method update() Abstract
Method draw() Abstract
End Type
Type TTile Extends TBase
Field xTile:Int
Field yTile:Int
Field Walkable:Byte
Function Create(sx:Int, sy:Int, WB:Byte)
Local TT:TTile = New TTile
TT.width = TiLESIZE
TT.height = TiLESIZE
TT.xTile = sx
TT.yTile = sy
TT.x = TT.xTile * TT.width
TT.y = TT.yTile * TT.height
TT.Walkable = WB
ListAddLast(ALLGameObjList , TT)
End Function
Method update()
If walkable 'not walkable
SetColor(255 , 0 , 0) 'red
Else ' walkable
SetColor(0 , 255 , 0) 'green
End If
End Method
Method Draw()
DrawRect(x * Zoom, y * Zoom, Width, Height)
End Method
End Type
Type TLevel
Field Width : Byte
Field Height : Byte
Field Map:Int[,]
Function Create:TLevel(MyMap:Int[],map_width:Int,map_height:Int)
Local TM : TLevel = New TLevel
TM.Width = map_width
TM.Height = map_height
TM.map = New Int [TM.Width, TM.Height]
TM.Load(myMap)
Return TM
End Function
Method Load(arrMap:Int[])
For Local i:Int = 0 Until Height
For Local j:Int = 0 Until Width
Map[j , i] = arrMap[j + (i * Width)]
If Map[j , i]
TTile.Create(j , i , True)
Else
TTile.Create(j , i , False)
End If
Next
Next
End Method
Method Render()
For Local CurObj:TBase = EachIn ALLGameObjList
CurObj.Update()
CurObj.Draw()
Next
End Method
End Type
Type TBonus Extends TBase
Field points:Int
Field miny:Int
Field maxy:Int
Field diry:Int
Field speed:Int
Field AnimDelay:Int
Function Create(sx:Int,sy:Int,pts:Int)
Local TB : TBonus = New TBonus
TB.Width = 6
TB.Height = 16
TB.x = sx * TiLESIZE + ((TiLESIZE - TB.width) / 2) 'center it
TB.y = sy * TiLESIZE + ((TiLESIZE - TB.height) / 2) 'center it
TB.points = pts
TB.miny = TB.y - 4
TB.maxy = TB.y + TB.Height + 4
TB.diry = -1
TB.speed = 1
TB.AnimDelay = 5
ListAddLast(ALLGameObjList , TB)
End Function
Method Update()
If(Animdelay < 0)
If ( miny >= (y) Or maxy <= (y + height) )
diry = -diry
End If
y = y + speed * diry
AnimDelay = 5
End If
AnimDelay :- 1
End Method
Method Draw()
SetColor(255 , 255 , 0)
DrawRect(x * Zoom, y * Zoom, Width, Height)
End Method
End Type
Type TMovingX Extends TBase
Field mindir:Int
Field maxdir:Int
Field dirx:Int
Field speed:Int
Function Create(xt:Int , yt:Int , mn:Int , mx:Int , d:Int)
Local TMX:TMovingX = New TMovingX
TMX.x = xt * TILESIZE
TMX.y = yt * TILESIZE
TMX.width = TILESIZE
TMX.height = TILESIZE
TMX.speed = 1
TMX.dirx = d
TMX.mindir = (mn + xt) * TILESIZE
TMX.maxdir = (mx + xt + 1) * TILESIZE
ListAddLast(ALLGameObjList , TMX)
End Function
Method Update()
If( mindir >= x Or maxdir <= (x + width) )
dirx = - dirx
EndIf
x :+ speed * dirx
End Method
Method Draw()
SetColor(128 , 128 , 128) ' gray
DrawRect(x * Zoom, y * Zoom, Width, Height)
SetColor(255 , 255 , 255) ' white line
DrawLine(x * Zoom, y * Zoom, (x * Zoom) + width - 1, y * Zoom)
End Method
End Type
Global LevMap:Int[] = [1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , ..
1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , ..
1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , ..
1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , ..
1 , 0 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , ..
1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , ..
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1]
Global MyLevel : TLevel = New TLevel.Create(LevMap , 20 , 10)
Function Init_All_Bonuses()
TBonus.Create(1 , 1 , 25)
TBonus.Create(3 , 8 , 25)
TBonus.Create(4 , 8 , 25)
TBonus.Create(5 , 8 , 25)
TBonus.Create(15 , 1 , 100)
TBonus.Create(7 , 4 , 100)
TBonus.Create(9 , 7 , 10)
TBonus.Create(4 , 6 , 10)
TBonus.Create(9 , 2 , 10)
TBonus.Create(14 , 7 , 10)
TBonus.Create(17 , 6 , 10)
TBonus.Create(17 , 4 , 10)
TBonus.Create(18 , 4 , 10)
TBonus.Create(3 , 3 , 10)
End Function
TMovingX.Create(13 , 3 , - 2 , 5 , 1)
'----------------------------- main loop -----------------------------
Graphics(ScrWidth, ScrHeight)
Init_All_Bonuses()
Repeat
Cls
If( KeyDown(KEY_LEFT) ) Then CameraX :- 5
If( KeyDown(KEY_RIGHT) ) Then CameraX :+ 5
If( KeyDown(KEY_UP) ) Then CameraY :- 5
If( KeyDown(KEY_DOWN) ) Then CameraY :+ 5
If( KeyDown(KEY_A) ) Then Zoom :+ 0.01
If( KeyDown(KEY_Z) ) Then Zoom :- 0.01
SetOrigin( -CameraX , -CameraY )
SetScale( Zoom , Zoom )
MyLevel.Render()
Flip
Until KeyDown(KEY_ESCAPE) Or AppTerminate()
End
Здорово! Кстати заметьте, что как только коэффициент масштаба станет отрицательным, то карта снова начнет увеличиваться, но уже будет перевернута верхом вниз и правая сторона поменяется с левой. Это такая фича функции SetScale(). Если вы загрузите картинку и перед ее отображением напишите SetScale(-1.0 , 1.0) , то картинка нарисуется отраженной (инвертированной) по оси Х, если напишем SetScale(1.0 , -1.0) , то отразится по У. В нашем же случае, карта отразится сразу по обеим осям, так как мы изменяем их пропорционально и синхронно.
Выведение
Ну вот в общем-то и все на сегодня. Вы теперь знаете, что такое ViewPort и как им манипулировать в своих целях. Знаете как осуществляется скроллинг и зумминг карты. На основе этих данных вы смело можете писать уже что-то очень похожее на игру. Надеюсь я не сильно загрузил вашу голову, и в ней еще есть место для следующих туторов. ;) А ваше домашнее задание - создать карту побольше нашей, которую мы использовали для экспериментов, и ввести лимит на скроллинг и зуминг карты по ее размерам. Удачи! У вас все получится – я уверен.
Спасибо вам за внимание. И как всегда, обо всех ошибках в тексте и коде, сообщайте мне на почтовый ящик. Чао.
Автор: Dimanche13 (e-mail: dimanche13 rambler.ru)
|
|