Понедельник, 06.05.2024, 03:36
Personal dimanche13 site
Главная | Регистрация | Вход Приветствую Вас Гость | RSS
Категории каталога
Мои статьи [22]
BlitzMax [17]
раздел содержит статьи относящиеся к языку программирования BlitzMax
SDL [9]
раздел содержит статьи на тему SDL
Code::Blocks [3]
в этом разделе рассказывается как прикрутить движок к интегрированной среде разработки(IDE) Code::Blocks
Форма входа
Поиск
Друзья сайта
Главная » Статьи » BlitzMax

Тайловая игра на BlitzMax, часть 1(1)

Тайловая игра на BlitzMax

Содержание

Введение

Привет! Этот тутор поможет вам понять, как строятся игры на основе плиток (далее - тайлов). Надеюсь, что приведенная здесь информация будет полезной для начинающих гейм-девелоперов.

Я не претендую на абсолютное знание предмета и всех его тонкостей, это, так сказать, мой взгляд на данную тему. Многое, что здесь написано, взято не с потолка, а уже много лет используется другими людьми, я лишь адаптировал код под BlitzMax, конечно, добавив некоторые моменты и от себя. Вот главные ресурсы, с которых была взята информация по данной теме:

Это далеко не всё, есть и другие источники, можете накопать еще что-нибудь в Интернете.

Сразу хочу заметить несколько моментов:

  • Я пишу программы с директивой SuperStrict. Где-то читал, что эта директива заставляет программу работать быстрее, каких-то документальных подтверждений этой теории я не нашел, но если это правда, то почему бы ей и не пользоваться. А использую я ее лишь для того, чтобы избежать ошибок в правильном написании переменных и чтобы видеть к каким типам они принадлежат.
  • Я не использую всякие значки типа $, % и т.д. в объявлении переменных. И все параметры функций заключены в скобки ( ). Так уж у меня повелось, и это единственное обоснование.
  • В названии типа я всегда ставлю вначале букву T , так если хочу создать тип Level, то я называю его TLevel,(буква T от слова Type).
  • Функцию создания любого объекта, я называю Create() и она всегда находится внутри типа, для которого используется. Доступ к ней производится через точку. Например: TLevel.Create() .
  • Функция, в которой происходит рисование объекта на экране, всегда называется Draw().
  • Функция, в которой происходит изменение состояния объекта, изменение размеров, перемещение и другие действия, всегда называется Update().
  • Я не утверждаю, что надо делать так и только так. Вовсе нет, каждый сам хозяин своего кода. Но на протяжении всего тутора, я буду придерживаться своих правил, поэтому и считаю правильным заранее вас об этом предупредить.

Тайловые карты

Что же такое тайл? Тайл в переводе с англицкого - это плитка. В нашем случае это прямоугольник (чаще - квадрат, реже - шестиугольник) с картинкой. Вы когда-нибудь собирали пазл? Одну большую картинку из множества кусочков. Каждый из кусочков не имел никакой ценности. Помните, что нельзя было понять, что изображено на каждом кусочке, и только соединив несколько соседних можно было увидеть часть рисунка или картины. Так же и в играх, разработчики дробят весь уровень игры (или карту) на маленькие равные кусочки. Вы ,конечно, спросите, а зачем они это делают? Попытаюсь объяснить подоходчивей. Даже имея 5 разных тайлов, можно нарисовать приличную карту для игры. Просто размещая эти тайлы в нужном вам порядке. Гляньте:
Мои художественные способности оставляют желать лучшего, так что эта карта - ради объяснения принципа, а не ради красоты. Видите, слева я нарисовал тайлы, которые использую в своей карте, а справа карта, собранная из этих тайлов. Думаю, всё наглядно и понятно. Зачем рисовать новую большую карту, затратив при этом много усилий, когда можно собрать ее по кусочкам, за несколько минут. Разработчики просто рисуют набор тайлов для карты и нумеруют их. Например так: трава - 0, вода - 1, деревцо - 2, дом - 3, куст - 4. И тогда в памяти компьютера карту можно представить так :
Думаю всем очевидны преимущества данного метода: можно собрать множество разных карт с помощью ограниченного набора тайлов. Тайлы обычно бывают квадратными с размерами (ширина х высота) : 16х16, 24х24, 32х32, 64х64, т.е. ширина = высоте. Но вас никто не ограничивает и вы можете делать тайлы, например, 16х8 или другого размера, всё зависит лишь от вашей фантазии и движка вашей игры. Я использую такие тайлы лишь потому, что наборы тайлов таких размеров легко можно найти в интернете, чтобы не рисовать самому(мои худ. способности вы видели выше). Хорошо, с этим мы разобрались. Но есть и еще один параметр тайла. Этот параметр - проходимость. То есть, сможет ли игрок пройти по этому тайлу. Допустим, в вашей игре можно идти по тайлам, обозначающим траву, дорогу или песок, а по другим тайлам идти нельзя, таким как дерево или куст, стена или еще что-то. Так что, договоримся , что для каждого тайла установим параметр <проходимость>! Который будет принимать значения <Да> и <Нет>, разные для каждого тайла.(?) Посмотрите на нашу карту:
Она имеет размеры: 6 тайлов в ширину (от 0 до 5) и 7 тайлов в высоту (от 0 до 6). Или по-другому - 6х7 тайлов. Теперь мы можем видеть, что тайлы по координатам (1,3) ; (3,3) и (4,6) содержат цифру 3, то есть тайл - дом, ну и так далее. Видите: все очень просто. Итак, перечислим все параметры для нашего тайла. Это: ширина и высота тайла, его координаты на тайловой карте, координаты на экране, проходимость и номер картинки в нашем наборе тайлов.

Так будет выглядеть тип тайла в Блитце:

Type TTile
  Field width               ' ширина тайла
  Field height              ' высота тайла
  Field xTile,yTile         ' координаты на карте(тайловые координаты)
  Field x,y                 ' координаты на экране
  Field walkable            ' проходимость
  Field image               ' картинка
End type
Постойте, я ничего не рассказывал про экранные координаты. Ладно, тут дело состоит в том, что у нас пока имеются только координаты тайла в карте, но не на экране. Как их перевести в экранные? Давайте подумаем. Допустим размеры тайла 32х32.
Тайл по координатам (0,0) - это трава, следующий за ним тайл (1,0) - это куст, дальше (2,0) - опять трава и т. д. Допустим, первый тайл рисуется прямо с левого верхнего угла экрана, т.е. его х и у равны 0, второй тайл будет рисоваться сразу за первым, правильно. Третий будет рисоваться начиная с окончания второго. Ладно смотрите:
Я изобразил три тайла, идущих друг за другом, разного цвета, для наглядности примера.

Ок. Перевод тайловых координат в экранные очень прост. Давайте домножать тайловые координаты на ширину тайла. Смотрите сами первый тайл (0,0) домножим на 32 - экранные координаты стали (0,0) потому что умножение на 0 дает 0, дальше интереснее - второй тайл (1,0) домножим на 32 (1*32, 0*32) , экранные координаты - (32, 0), третий тайл -(2*32,0*32) - экранные координаты (64, 0). А теперь посмотрите на картинку выше. Ого! Так оно и есть - первый тайл (синенький) начинается с (0,0) ; второй(зелененький) - начинается с (32,0), третий(малиновый) - (64,0). Итак повторю, чтобы перейти от тайловых координат к экранным надо домножать на РАЗМЕР ТАЙЛА. Вот так:

x = xTile * Tile_Width
y = yTile * Tile_Height

ну ,соответственно, если ширина и высота тайла равны, то проще:

x = xTile * TileSize
y = yTile * TileSize

Ну теперь нам ничего не стоит сделать функцию создания тайла, но для начала создадим список(или лист) в котором будут содержаться(храниться) все созданные нами тайлы, не буду ничего придумывать и назову лист так: TileList.

Global TileList:TList = CreateList()

А теперь функция создания тайла:

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 Draw() ), никаких сложностей возникнуть не должно. До этого я писал, про тайловые карты с видом сверху, но хочу заметить, что также все это относится и к аркадам, у которых вид сбоку. Таким как Марио, Соник и им подобные. Так вот посмотрите еще раз на рисунок карты: как проще ее хранить? Ну конечно же в массиве. Если перевести нашу карту в массив языка Блиц, то получим что-то наподобие этого:

Global TileMap:Int[] =
[0, 4, 0, 1, 2, 0, ..
 1, 1, 1, 1, 1, 1, ..
 0, 0, 2, 0, 0, 0, ..
 0, 3, 0, 3, 0, 0, ..
 4, 0, 0, 0, 4, 0, ..
 0, 2, 0, 0, 0, 4, ..
 0, 0, 0, 4, 3, 0]

Ну, теперь создадим наш главный тип - УРОВЕНЬ! Он будет читать карту из массива, создаст на основе этой карты тайлы и затем уже нарисует нашу тайловую карту!

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
End type

Для карты я специально использовал одномерный массив (TileMap:Int[]), но в типе TLevel этот массив будет переведен в двумерный, я сделал так, а вы делайте, как пожелаете, хоть грузите из файла на диске или прочитайте из DefData, это, опять же, зависит от вашей фантазии. Функция принимает в параметрах одномерный массив карты и ее размеры - ширину и высоту. Для нашего случая это будет так:

MyLevel:TLevel = TLevel.Create(TileMap, 6, 7)

Теперь напишем метод load, который грузит нашу карту в двумерный массив Мар , создает нужные тайлы и заносит их в список объектов.

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] < 15
        TTile.Create(j , i , true)
      Else
        TTile.Create(j , i , false)
      End if
    Next
  Next
End Method

Что же, ничего хитрого в этой функции тоже нет, мы проходим по всему одномерному массиву и заполняем значениями двумерный массив, а заодно и создаем на этих местах тайлы уже известной нам функцией Create, которую мы писали ранее в типе TTile. Здесь стоит заметить, что я разделил тайлы на две группы: одни проходимы (true), а другие нет (false). Разделение произошло в условном операторе (if): первые тайлы, до 15, все проходимы, а остальные - не проходимы. Думаю, это понятно. Кстати, если вы переписали функцию Create в TTile, чтобы еще и загружалась картинка, то надо переделать и эту функцию, чтобы кол-во параметров совпадало. Например так:

TTile.Create(j , i , true, tileSet ])
Но это, опять же, зависит от вашего кода и полностью на ваше усмотрение. Ок. Теперь давайте перейдем непосредственно к созданию нашего тайлового уровня. До этого момента мы только создали (инициализировали) наши тайлы и поместили их в отдельный список (лист), теперь их надо нарисовать. Я не буду использовать никакие картинки, я буду использовать простые квадраты рисуемые БлитцМаксом функцией DrawRect (x,y,width,height). А чтобы отличать проходимые тайлы от непроходимых, я сделаю их разноцветными. Вот так: зеленый - проходимые (true), а непроходимые (false) - красный.
 
$IMAGE6$

Тип TTile остается прежним, просто добавим 2 метода Update и Draw:

  Method Update()                             ' здесь установим цвет тайла
    If Walkable = false
      SetColor(255 , 0 , 0)        'red
    Else
      SetColor(0 , 255 , 0)        'green
    EndIf
  End Method

  Method Draw()                                ' здесь нарисуем
    DrawRect(x , y , Width , Height)
  End Method

Теперь приступим к самому волнительному моменту: нарисуем уровень, то есть все созданные тайлы, на экране. Для этого в типе TLevel сделаем функцию Render(), которая и нарисует все тайлы.

  Method Render()
    For Local CurTile:TTile = EachIn TileList  ' пройти по всему листу тайлов
      CurTile.Update()                         ' установить цвет текущего тайла
      CurTile.Draw()                           ' нарисовать текущий тайл
    Next
  End Method

Уровень я рисовал прямо в массиве. Вот он:

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 Level : TLevel = New TLevel.Create(LevMap , 20 , 10)

А вот главный цикл всей программы:

Graphics(640, 480)           ' установить видеорежим 640х480
  Repeat                     ' повторять ..
    Cls                      ' очистить экран

    Level.Render()           ' рисовать уровень в буфер

    Flip                     ' поместить буфер на экран
  Until KeyDown(KEY_ESCAPE) Or AppTerminate()  ' .. пока не нажат ESC или крестик окна

End

Если у вас не получилось, то вот исходник:

SuperStrict
 
Global ScrWidth : Int  = 640
Global ScrHeight : Int = 480

Global TileList:TList = CreateList()

Const TILESIZE:Int   = 32

Type TTile
  Field x:Int
  Field y:Int

  Field xTile:Int
  Field yTile:Int
 
  Field Width:Byte
  Field Height:Byte

  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(TileList , TT)
  End Function  

  Method update()
    If    walkable         'walkable
      SetColor(255 , 0 , 0)  'red
    Else             'NOT 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 CurTile:TTile = EachIn TileList
      CurTile.Update()
      CurTile.Draw()
    Next  
  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)

Graphics(ScrWidth, ScrHeight)

Repeat
   Cls

    MyLevel.Render()
   
   Flip
Until KeyDown(KEY_ESCAPE) Or AppTerminate()

End
Категория: BlitzMax | Добавил: dimanche (24.05.2008) | Автор: Dmitriy
Просмотров: 3267 | Комментарии: 2 | Рейтинг: 4.0/1 |
Всего комментариев: 0
Имя *:
Email *:
Код *:
Copyright MyCorp © 2024