Часть 8. Карта

Бегать по пустому полю мне уже надоело, поэтому поря заняться обстановкой.
SuperStrict
Global gW:Int=800, gH:Int=600
Graphics gW,gH
SetClsColor 255,255,255
SetLineWidth 2
Type TPlayer
 Field x:Float, y:Float, speed:Float, angle:Float
 
 Function Create:TPlayer(_x:Float,_y:Float)
 player:TPlayer = New TPlayer
 player.x=_x
 player.y=_y
 Return player
 End Function
 
 Method update()
 x:+(KeyDown(KEY_D)-KeyDown(KEY_A))*speed
 y:+(KeyDown(KEY_S)-KeyDown(KEY_W))*speed
 If KeyDown(KEY_LSHIFT) 
 speed=6
 Else
 speed=4
 EndIf
 angle=ATan2(MouseY()-y,MouseX()-x)
 If MouseDown(1) TBullet.Create(x+Cos(angle)*16, y+16*Sin(angle), angle)
 End Method
 
 Method draw()
 SetColor 122,122,255
 DrawOval x-16,y-16,32,32
 SetColor 0,0,0
 DrawLine x,y,x+15*Cos(angle),y+15*Sin(angle)
 SetColor 255,255,255
 End Method
 
End Type
Type TBullet
 Field x:Float, y:Float, angle:Float
 Function Create(x:Float,y:Float,angle:Float)
 Local bullet:TBullet = New TBullet
 bullet.x=x
 bullet.y=y
 bullet.angle=angle
 bullets.AddLast(bullet)
 End Function
 Function gupdate()
 For Local bullet:TBullet = EachIn bullets
 bullet.update()
 bullet.draw()
 Next
 End Function
 Method update()
 x:+Cos(angle)*10
 y:+Sin(angle)*10
 End Method 
 Method draw()
 SetColor 0,0,0
 DrawOval x-4,y-4,8,8
 SetColor 255,255,255
 End Method
End Type
Global player:TPlayer=TPlayer.Create(gW/2,gH/2),bullets:TList = New TList, map:Int[100,100]
init()
Function init()
For Local i:Int=0 To 99
 For Local j:Int=0 To 99
  If (i=0 Or i=99 Or j=0 Or j=99) map[i,j]=1
 Next
Next
End Function

Function update()
player.update()
End Function

Function draw()
For Local i:Int = 0 To 99
 For Local j:Int = 0 To 99
 drawTile(map[i,j],i,j)
 Next
Next
player.draw()
TBullet.gupdate()
End Function

Function drawTile(id:Int,i:Int,j:Int)
Select id
 Case 0
 SetColor 230,230,230
 DrawRect i*32,j*32,32,32
 SetColor 200,200,200
 DrawRect i*32+2,j*32+2,30,30
 Case 1
 SetColor 60,60,60
 DrawRect i*32,j*32,32,32
 SetColor 30,30,30
 DrawRect i*32+2,j*32+2,30,30
End Select
SetColor 255,255,255
End Function

While Not KeyDown(KEY_ESCAPE)
update()
draw()
Flip(1)
Cls()
Wend
Кода получилось у нас много, зато уже неплохой результат:) Теперь поэтапно.
Для начала, создадим интовый массив map размером 100х100. Хотите больше, сделайте больше, однако таких размеров пока что хватит. Массивы - они и в BlitzMax массивы, ничего здесь не намудрили. Затем, я изменил главный цикл, закинув туда функции update() и draw(). Таким образом, больше я уже не буду возвращаться к нашему циклу, все поправки будут в этих двух функциях. И ещё одна важная функция - init() - функция запуска игры. Здесь по сути должны быть загрузка медии, создание карты и ботов, но пока что у нас здесь только карта. Создается она следующим образом: каждый крайний элемент становится 1, каждый другой  - 0. Конечно, умный читатель обратит внимание на то, что цикл получился не труЪ. Сделал я его таким для дальнейших изменений, ведь это не готовый вариант создания карты. И последняя новая вещь на сегодня - конструкция Select-Case. Аналог switch в cи подобных, то есть работает следующим образом. В select мы указываем ту переменную, из которой берем значения. В case указываем тот код, который выполнится, если значение нашей переменной совпадает с case.  

Часть 7. Пиф паф!

Ну что ж, бегать и крутиться это весело, но пора и пострелять.
SuperStrict
Global gW:Int=800, gH:Int=600
Graphics gW,gH
SetClsColor 255,255,255
SetLineWidth 2
Type TPlayer
 Field x:Float, y:Float, speed:Float, angle:Float
 
 Function Create:TPlayer(_x:Float,_y:Float)
 player:TPlayer = New TPlayer
 player.x=_x
 player.y=_y
 Return player
 End Function
 
 Method update()
 x:+(KeyDown(KEY_D)-KeyDown(KEY_A))*speed
 y:+(KeyDown(KEY_S)-KeyDown(KEY_W))*speed
 If KeyDown(KEY_LSHIFT) 
 speed=6
 Else
 speed=4
 EndIf
 angle=ATan2(MouseY()-y,MouseX()-x)
 If MouseDown(1) TBullet.Create(x+Cos(angle)*16, y+16*Sin(angle), angle)
 End Method
 
 Method draw()
 SetColor 122,122,255
 DrawOval x-16,y-16,32,32
 SetColor 0,0,0
 DrawLine x,y,x+15*Cos(angle),y+15*Sin(angle)
 SetColor 255,255,255
 End Method
 
End Type
Type TBullet
 Field x:Float, y:Float, angle:Float
 Function Create(x:Float,y:Float,angle:Float)
 Local bullet:TBullet = New TBullet
 bullet.x=x
 bullet.y=y
 bullet.angle=angle
 bullets.AddLast(bullet)
 End Function
 Function gupdate()
 For Local bullet:TBullet = EachIn bullets
 bullet.update()
 bullet.draw()
 Next
 End Function
 Method update()
 x:+Cos(angle)*10
 y:+Sin(angle)*10
 End Method 
 Method draw()
 SetColor 0,0,0
 DrawOval x-4,y-4,8,8
 SetColor 255,255,255
 End Method
End Type
Global player:TPlayer=TPlayer.Create(gW/2,gH/2),bullets:TList = New TList

While Not KeyDown(KEY_ESCAPE)
player.update()
player.draw()
TBullet.gupdate()
Flip(1)
Cls()
Wend
Мы придерживаемся ООП стиля и это именно то, как я себе его представляю в BlitzMAX. Теперь, разберемся как же это работает. Нам нужен новый тип "TBullet". У нашей пули есть 3 характеристики - x,y,angle - координаты и угол направления. Так же, нам нужны 2 функции и 2 метода. Функция Create() создает нашу пулю(такой же принцип как и создание игрок), и добавляет его в bullets - лист. "TList" - ещё один тип данных в BlitzMAX. Он похож на vector из си подобных языков и нужен нам для хранения данных, в основном, когда неизвестно количество этих данных. Затем функция gupdate(global update) - ее мы используем для обновления всех наших пуль. А метода update() и и draw() очень схожи с игроком, нового здесь ничего нету. Однако у нашей системы есть 2 минуса: 1). Пули не удаляются 2). Пули создаются слишком быстро Эти пункты мы разберем немного позже, после того как создадим живые мишени для наших пуль.

Часть 6. Мелкие полезности

SuperStrict
Global gW:Int=800, gH:Int=600
Graphics gW,gH
SetClsColor 255,255,255
SetLineWidth 2
Type TPlayer
 Field x:Float, y:Float, speed:Float, angle:Float
 
 Function Create:TPlayer(_x:Float,_y:Float)
 player:TPlayer = New TPlayer
 player.x=_x
 player.y=_y
 Return player
 End Function
 
 Method update()
 x:+(KeyDown(KEY_D)-KeyDown(KEY_A))*speed
 y:+(KeyDown(KEY_S)-KeyDown(KEY_W))*speed
 If KeyDown(KEY_LSHIFT) 
 speed=6
 Else
 speed=4
 EndIf
 angle=ATan2(MouseY()-y,MouseX()-x)
 End Method
 
 Method draw()
 SetColor 122,122,255
 DrawOval x-16,y-16,32,32
 SetColor 0,0,0
 DrawLine x,y,x+15*Cos(angle),y+15*Sin(angle)
 SetColor 255,255,255
 End Method
 
End Type 
Global player:TPlayer=TPlayer.Create(gW/2,gH/2)

While Not KeyDown(KEY_ESCAPE)
player.update()
player.draw()
Flip(1)
Cls()
Wend

Ну что же, сегодня я покажу вам очень полезную вещь, а именно, работу с мышью в BlitzMax. Основные функции, которые нам потребуются:
MouseX() - возвращает x координату мыши
MouseY() - возвращает у координату мыши
ATan2(y2-y1,x2-x1) - возвращает аркотангенс угла по двум катетам. Иными словами - угол между двумя точками x1;y1 и x2;y2
Cos(angle) - возвращает косинус угла angle
Sin(angle) - возвращает синус угла angle
DrawLine(x1,y1,x2,y2) - рисует линию от точки x1;y1 до x2;y2
ВАЖНО! BlitzMax работает с градусами, а не радианами.
Ах да, и функция SetLineWidth - устанавливает ширину линии.

Часть 5. Боги ООП

Богами мы конечно не станем, рано нам. А вот об основах ООП в Blitzmax я вам расскажу.
Из минусов - у нас нету деструкторов и конструкторов, поэтому придется исхитриться.
SuperStrict
Global gW:Int=800, gH:Int=600
Graphics gW,gH
SetClsColor 255,255,255
Type TPlayer
 Field x:Float, y:Float, speed:Float
 
 Function Create:TPlayer(_x:Float,_y:Float)
 player:TPlayer = New TPlayer
 player.x=_x
 player.y=_y
 Return player
 End Function
 
 Method update()
 x:+(KeyDown(KEY_D)-KeyDown(KEY_A))*speed
 y:+(KeyDown(KEY_S)-KeyDown(KEY_W))*speed
 If KeyDown(KEY_LSHIFT) 
 speed=6
 Else
 speed=4
 EndIf
 End Method
 
 Method draw()
 SetColor 0,0,0
 DrawOval x-18,y-18,36,36
 SetColor 122,122,255
 DrawOval x-16,y-16,32,32
 SetColor 255,255,255
 End Method
 
End Type 
Global player:TPlayer=TPlayer.Create(gW/2,gH/2)

While Not KeyDown(KEY_ESCAPE)
player.update()
player.draw()
Flip(1)
Cls()
Wend
Вот нынешний код нашей игры. Смысл данного изменения - показать вам ООП, если же вас не устраивает данная парадигма, можете оставить старый код. "Type" - аналог класса из си подобных языков. Поскольку конструктора у нас нету, мы напишем функцию, которая возвращает нам объект типа TPlayer. В нашем типе есть 3 поля - x,y,speed; 2 метода - draw(), update().
Метод вызывается через объект(в нашем случае player), функция - через тип. Как пример "player.update()" - вызываем метод update() для нашего player; "TPlayer.Create()" - вызываем функцию Create() из типа. 

Часть 4. Начинаем делать игры

Так, хватит уже читать всю теорию, время заняться делом. Дабы несильно грузить вас математикой, обойдемся простым TDS. И на заметку - я совсем не художник, я программист до мозга костей, так что не ждите от меня выдающихся текстур и спрайтов:) Поэтому, я буду стараться использовать примитивную графику(прямоугольники, круги, линии). Но для тех, кто хочет сделать Crysis 2D, я расскажу как и картинки рисовать.

Немного функций

"DrawOval x,y,w,h" - рисует овал в координатах x,y шириной w и длиной h(мне привычна высота поэтому h(height), но правильно будет назвать это длиной).
"DrawLine x1,y1,x2,y2" - рисует линию от точки x1,y1 до точки x2,y2
"SetColor r,g,b" - указывает текущий цвет рисования в палитре RGB; 0<r,g,b<256. То есть вся графика, которая рисуется после этой строчки, будет использовать данный цвет. Изначально, цвет белый, то есть 255,255,255.
"SetClsColor r,g,b" - указывает цвет очистки экрана. Помните команду Cls()? Так вот, по сути она рисует черный квадрат на весь экран. И мы можем задать этому квадрату определенный цвет. Изначально, цвет черный, то есть 0,0,0.

Поехали!


В этом заключается наш сегодняшний урок:
SuperStrict
Global gW:Int=800, gH:Int=600
Graphics gW,gH
SetClsColor 255,255,255
Global pX:Float,pY:Float, speed:Float

Function Update()
pX:+(KeyDown(KEY_D)-KeyDown(KEY_A))*speed
pY:+(KeyDown(KEY_S)-KeyDown(KEY_W))*speed
If KeyDown(KEY_LSHIFT) 
 speed=6
 Else
 speed=4
EndIf
End Function

Function Draw()
SetColor 0,0,0
DrawOval pX-18,pY-18,36,36
SetColor 122,122,255
DrawOval pX-16,pY-16,32,32
SetColor 255,255,255
End Function

While Not KeyDown(KEY_ESCAPE)
Update()
Draw()
Flip(1)
Cls()
Wend
Копируем, вставляем, управляем кружком на WASD, левый Shift - бег.
Теперь подробней.
"SuperStrict" - заставляем компилятор ругаться, если что-то не так с синтаксисом(без этой функции компилятору будет все равно, если вы не указали тип переменной, он сам его определит).
"gW,gH" - переменные, которые указывают разрешение нашего экрана.
"pX,pY,speed" - переменные отвечающие за нашего игрока.
"Draw(), Update()" - функции отрисовки и управления игроком.

В Update() трудным моментом для новичков может показаться:
pX:+(KeyDown(KEY_D)-KeyDown(KEY_A))*speed
pY:+(KeyDown(KEY_S)-KeyDown(KEY_W))*speed
На самом деле, здесь ничего трудного нету. Как это работает: KeyDown(KEY_D)-KeyDown(KEY_A) Функция KeyDown(KEY) вернет нам 1, если KEY нажата, 0 - если не нажата. Получается, если мы нажмем D, выражение равно 1, если нажмем A - "-1", ничего не нажмем или нажмем одновременно 2 клавиши - 0. Умножаем это дело на скорость, и получаем нужное изменение координаты pX. Например, если у нас получилось 1*4, игрок пойдёт вправо на 4 пикселя. Теперь о Draw(). Сразу, устанавливаем черный цвет и рисуем круг 36 на 36 пикселей в координатах pX-18, pY-18. Это нужно для черной обводки. Затем рисуем уже круг голубого цвета. Вот и конец:) В следующем уроке познакомлю вас с ООП в BlitzMax, переделаем немного кода и добавим возможность стрелять.

Часть 3. Функции

Функции в BlitzMax имеют самый привычный вид.

Function name:TType(arg1:Int, arg2:String)
return 2
End function
Где "name" - имя функции, "TType" - тип данных возвращаемых функцией, "arg1, arg2" - аргументы, передаваемые в функцию. Вызывается наша функция следующим образом:"name(2, "4")". На этом можно было бы и закончить, но теперь я должен рассказать о Global/Local переменных. Local - действует в пределах данной функции и только. Например:
...
Function foo()
Local a:Int=5
End function
...
foo()
DrawText a,0,0
Данный код выведет нам 0 на экран, из-за того, что переменная "a" имеет значение 5 только в пределах функции foo(). Если же мы написали вот так:
Global a:Int
...
Function foo()
a=5
end function
...

foo()
DrawText a,0,0
То на экране мы увидели "5", так как теперь наша переменная "a" - глобальная.

Часть 2. Переменные

Сегодня я познакомлю вас с основами работы с переменными.  В этом плане, программирование на BlitzMax доставляет одно удовольствие из-за своей простоты.
И хочу сразу предупредить. Статья получилась длинная,и многим покажется неинтересной. Специально для тех, кто хочет всё да побыстрее, в начале каждого урока уже будет код, по которому мы будем работать. Кому неинтересно, смотрите код и двигайтесь дальше, остальных прошу углубиться в недри статьи.
Graphics 800,600
Local text:String="Hello world"
Local a:Int=5,b:Int=6
Local c:Int=a
c:+b
While Not KeyDown(KEY_ESCAPE)
DrawText text,0,0
DrawText a+b,0,16
DrawText c,0,32
Flip(1)
Cls
Wend
Вот с этого примера и начнём. Объявление переменной в BlitzMax имеет вид:
 Global/Local/Const var:Type 
Global/Local - показатель глобальности переменной, то есть, при Global - переменная доступна в любом месте кода, при Local - только в данном объекте, например цикле, функции. Const - указатель на константное(постоянное) значение. Все константы глобальны, поэтому выражение "Global const a:int" - неверно. var - имя переменной. BlitzMax игнорирует регистр, поэтому "A" и "a" - одинаковые имена. Из правил хорошего тона можно выделить следующие: название переменной начинается с маленькой буквы, если же название состоит из нескольких слов, каждое последующие начинается с большой. Имя констант пишется в верхнем регистре. Примеры:
Local text: String
Local bigText: String
Const TEXT: String
Основные типы данных: byte(1 байт), short(2 байта), int(4 байта), long(8 байт), float(4 байта), double(8 байт), string. Byte, short, int и long - целочисленные, float и double - с плавающей точкой. String - строка( ваш К.О. ). Теперь о приведении типов. Тут BlitzMax опять таки радует. Если вкратце, суть такова: если один из элементов выражения нецелочисленный, значит и всё выражение нецелочисленное. Это касается работы с числами. Теперь, чтоб стало яснее. "5 / 2" вернет нам 2, потому что выполняется целочисленное деление, то есть деление без остатка. " 5 / 2.0" вернет нам 2.5, так как "2.0" - уже относится к типу float. Перевод из типа к типу выполняется следующим образом: нужное выражение ставим в скобки, перед скобками указываем нужный тип. Пример: "String(5)" вернет нам строку "5". Остаток от деления узнается командой mod. Пример "5 mod 2" вернет нам 1. Для увеличения переменной на какое-то значение используется следующая конструкция: "a:+5". Вместо "+" мы можем подставить "/,-,*". "a:*5" - увеличить a в 5 раз, "a:-5" - отнять от переменной 5.