Статьи

Как написать игрушку на VB

Каждый программист в своей жизни написал хотя бы одну игрушку. Не являются исключением из этого правила и те, кто пишет на VB. Другой разговор, что более ранние версии VB были более громоздкими и достаточно медленными. Относительно медленными были и сами компьютеры. С годами постепенно положение изменилось. И хотя, VB стал еще более громоздким, однако скорость работы, особенно с графикой выросла значительно.

В данной статье я хочу показать, что на обычном VB6 можно писать интересные приложения. А для тех, кто посвятит себя в дальнейшем программированию игр – это будет начальным трамплином. Итак, шаг за шагом мы создадим компьютерную игру. Для тех, кто спешит, есть готовый Листинг.

После каждого шага, нажав на клавишу F5, Вы сможете увидеть те изменения, которые Вы внесли в программу. Для того, чтобы случайно не потерять свои данные, рекомендую Вам производить сохранения проекта после каждого шага.

 

Шаг 1. Самый главный. Необходимо определить, что же мы хотим "представить миру". Именно на этапе планирования и определяется будущий вид, структура и взаимодействие различных частей программы.

Те, кто постарше, наверно помнят то время, когда персональные компьютеры были недосягаемой мечтой. Именно тогда появились в России первые игровые автоматы, на которых за 15 копеек можно было попускать торпеды в корабли, поучаствовать в скачках, ну и конечно, порулить. Отдавая дань памяти тем временам, я решил предложить Вам написать совместно игровую программу AutoRoad. Она будет состоять из двух форм (основной, содержащей игровое поле, и вспомогательной для вывода пользовательских настроек), модуля, а так же ActivX Control'а. Хочу сразу же обратить Ваше внимание на то, что в ряде случаев, для ускорения обработки графики, мы будем обращаться к API-функциям. Всем известно (а кому не известно – "мотайте на ус"), что API-функции работают только с пикселами. Поэтому на основной форме (где будет находиться игровое поле) и сама форма и все элементы, расположенные на ней, должны будут иметь свойство ScaleMode = 3 (pixel).

 

Шаг 2. Создайте новый проект StandartEXE и дайте ему имя AutoRoad. Для формы измените следующие параметры:

Name = frmMain

Caption = AutoRoad

Icon – иконку можете вставить мою, или какая Вам больше понравится

ScaleMode = 3 – Pixel

StartupPosition = 2 – CenterScreen

Разместите на форме 2 PictureBox. Первый с параметрами:

Name = picRoad

AutoRedraw = True

BorderStyle = 0 – None

Height = 461

Left = 8

ScaleMode = 3 – Pixel

Top = 0

Width = 331

Второй PictureBox с параметрами:

Name = picSrc

AutoRedraw = True

AutoSize = True

BorderStyle = 0 – None

Left = 368

Picture – загрузите Auto.bmp

ScaleMode = 3 – Pixel

Top = 364

Visible = False

NB! На случай создания своих картинок, Auto.bmp построена следующим образом: в первом ряду – управляемое игроком авто, во втором – 6 авто, двигающихся навстречу, в третьем – 6 авто по ходу движения с основным автомобилем. Размеры каждого авто 23 пиксела в ширину и 31 пиксел в высоту. Если Вы захотите нарисовать автомобили других размеров, необходимо будет сделать соответствующие изменения в кодах.

 

Лирическое отступление 1. Если Вы впоследствии переименуете проект и даже сохраните его под другим именем, все-равно VB будет предлагать компилировать его в ехе-файл только с первоначальным именем. Закройте проект, а затем в каком либо текстовом редакторе откройте файл Вашего проекта с расширением vbp, и строке ExeName32, впишите то название, какое Вы хотите увидеть.

Шаг 3. Пошли дальше. Нарисуем дорогу. В frmMain в разделе деклараций запишем процедуру, которая будет рисовать игровое поле:

Public Sub DrawRoad()

    picRoad.Line (0, 0)-(picRoad.Width * 0.05, picRoad.Height), vbGreen, BF

    picRoad.Line (picRoad.Width * 0.95, 0)-(picRoad.Width, picRoad.Height), _
    vbGreen, BF

    picRoad.Line (picRoad.Width * 0.05, 0)-(picRoad.Width * 0.95, _
    picRoad.Height), vbBlack, BF

    picRoad.Line (picRoad.Width * 0.5, 0)-(picRoad.Width * 0.5, _
    picRoad.Height), vbYellow, BF

End Sub

Первая и вторая строки рисуют зеленый бордюр по краям, третья строка – непосредственно саму дорогу, и наконец четвертая – разделительную желтую линию.

Шаг 4. Займемся теперь нашим авто. Для копирования его на игровое поле будем использовать API-функцию StretchBlt.

Вначале добавим к программе модуль Project/Add Module, и назовем его Name = mdlAuto. В раздел деклараций модуля запишем объявление данной функции

Public Declare Function StretchBlt Lib "gdi32" ( _
    ByVal hdc As Long, ByVal x As Long, ByVal y As Long, _
    ByVal nWidth As Long, ByVal nHeight As Long, _
    ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, _
    ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, _
    ByVal dwRop As Long) As Long

а так же константу, необходимую для последнего параметра

Public Const SRCCOPY = &HCC0020

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

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

Public Type POINTAPI

    x As Long

    y As Long

End Type

Наконец, объявим переменную для координат нашего авто:

Public Auto As POINTAPI

На этом закончим предварительный этап копирования нашего авто и перейдем в форму.

Лирическое отступление 2. Вместо API-функции StretchBlt можно использовать встроенную в VB функцию – PaintPicture, которая не просто повторяет ее, но и целиком и полностью на нее опирается.

Шаг 4а. В декларациях формы запишем процедуру:

Public Sub DrawAuto()

    StretchBlt picRoad.hdc, Auto.x, Auto.y, _
        23, 31, picSrc.hdc, 0, 0, 23, 31, SRCCOPY

End Sub

Здесь данная функция вырезает из общей картинки первый автомобиль и копирует его на дорогу.

Объединим процедуры перерисовки дороги и нашего авто в единую процедуру, к которой мы в дальнейшем будем неоднократно обращаться.

Public Sub RedrawPic()

    DrawRoad

    DrawAuto

End Sub

Теперь, чтобы это все заработало, вернемся в модуль и напишем запускающую процедуру:

Public Sub Main()

  With frmMain

    .Show

    .RedrawPic

  End With

End Sub

NB! Не забудьте сделать процедуру Main стартовой при запуске проекта. Для этого в меню Project/AutoRoad Properties … в выпадающем списке Startup Object выберите Sub Main и нажмите ОК.

Запустим проект (клавиша F5). На данном этапе должна быть дорога и в верхнем левом углу ее – наш автомобиль. Как говорится, все хорошо, но ему там не место. Проецирование автомобиля в верхний левый угол произошло потому, что по умолчанию тип POINTAPI дает начальное значение для x и y равным нулю. Давайте сразу же исправим это. Наш авто должен располагаться приблизительно посередине левой полосы дороги. Допишем 2 строки в процедуру Main, инициализирующие начальные координаты авто:

Public Sub Main()

  With frmMain

      Auto.x = .picRoad.Width * 0.75

      Auto.y = .picRoad.Height * 0.5

      .Show

      .RedrawPic

  End With

End Sub

Еще раз запустим проект и посмотрим все ли правильно получилось у нас.

Шаг 5. Переходим к перемещению нашего авто. В нашем случае, автомобиль должен уметь перемещаться вправо и влево. Для этого используем событие KeyDown, так как оно позволяет обращаться к конкретным клавишам клавиатуры.

Private Sub picRoad_KeyDown(KeyCode As Integer, Shift As Integer)

Select Case KeyCode

Case vbKeyLeft

    Auto.x = Auto.x - 2

Case vbKeyRight

    Auto.x = Auto.x + 2

End Select

    RedrawPic

End Sub

При нажатии на левую клавишу происходит смещение координаты х на 2 пиксела влево, на правую клавишу – соответственно на 2 пиксела вправо. После чего перерисовываем всю картинку с новыми координатами для авто. Одно маленькое неудобство – при длительном нажатии наше авто "уезжает" за край картинки. Ограничим его перемещения, учитывая так же зеленый бордюр:

Private Sub picRoad_KeyDown(KeyCode As Integer, Shift As Integer)

Select Case KeyCode

Case vbKeyLeft

    If Auto.x <= picRoad.Width * 0.05 Then Exit Sub

    Auto.x = Auto.x - 2

Case vbKeyRight

    If Auto.x >= picRoad.Width * 0.95 - 23 Then Exit Sub

    Auto.x = Auto.x + 2

End Select

    RedrawPic

End Sub

Итак, на данном этапе мы добились прорисовки дороги, размещения на нем автомобиля и перемещения его вправо и влево. Поэтому сохраним проект и можем переходить к …

Шаг 6.  … отображению других авто (встречных и попутных). В принципе, различие между отображением нашего авто и других авто – одно: наш автомобиль – один, а других – много. Поэтому воспользуемся теми же принципами прорисовки других авто, что и раньше. Единственное исключение сделаем для упрощения просчета координат в этой массе автомобилей: объявим массив. В модуле сделаем объявление:

Public OtherAuto(0 To 11) As POINTAPI ' от 0 до 5 - навстречу, от 6 до 11 - по ходу

В форме напишем функцию, описывающую прорисовку одного "другого" авто:

Public Sub DrawOtherAuto(Num As Integer)

    Select Case Num

    Case 0 To 5

    StretchBlt picRoad.hdc, OtherAuto(Num).x, OtherAuto(Num).y, _
        23, 31, picSrc.hdc, Num * 23, 31, 23, 31, SRCCOPY

    Case 6 To 11

    StretchBlt picRoad.hdc, OtherAuto(Num).x, OtherAuto(Num).y, _
        23, 31, picSrc.hdc, (Num - 6) * 23, 62, 23, 31, SRCCOPY

    End Select

End Sub

Принцип построения процедуры такой же, как и у нашего авто. Различие в вырезании из общей картинки: вырезается авто под номером Num (при ширине 23 пиксела) и начиная с соответствующей строки (соответственно, в зависимости от направления движения с 31 или 62 пиксела).

В процедуру RedrawPic внесем изменения, для отображения других авто. В итоге она должна выглядеть так:

Public Sub RedrawPic()

Dim i%

    DrawRoad

    DrawAuto

    For i = 0 To 11

        DrawOtherAuto (i)

    Next

End Sub

В запускающей процедуре Main, так же сделаем изменения, которые отобразят начальные координаты других машин:

Public Sub Main()

Dim i%

With frmMain

    Auto.x = .picRoad.Width * 0.75

    Auto.y = .picRoad.Height * 0.5

    For i = 0 To 5

        'добавляется по 2 пиксела к 23 для расстояния между машинами

        OtherAuto(i).x = (i * 25) + (.picRoad.Width * 0.05)

        OtherAuto(i).y = 0

    Next

    For i = 6 To 11

        'добавляется по 2 пиксела к 23 для расстояния между машинами

        OtherAuto(i).x = ((i - 6) * 25) + (.picRoad.Width * 0.5)

        OtherAuto(i).y = .picRoad.Height - 31

    Next

    .Show

    .RedrawPic

End With

End Sub

Для машин от 0 до 5 (двигающихся навстречу) координата х будет равна порядковому номеру авто + его ширина (23 пиксела). Но чтобы машины не "слипались" друг с другом бортами – добавим на ширину еще по 2 пиксела. Плюс учитываем ширину зеленого бордюра. Для попутных авто (от 6 до 11) – все тоже самое, только расчет будет идти от середины дороги. Координату y, временно, пока приравняем к тем цифрам, чтобы встречные авто располагались сверху картинки, а попутные – снизу. Такая симметрия нехарактерна для жизни, поэтому чуть позже мы к этому вернемся и исправим.

Шаг 7. Машины отображены, теперь самое время заставить их двигаться. Желая придать естественность движению на дороге эту часть мы будем неоднократно изменять, поэтому вместо сразу конечного результата, я покажу каким путем мы подошли к нему.

Сначала внесем изменения в форму.

Итак, откроем форму frmMain и расположим на ней таймер, со следующими параметрами:

Name = tmrMove

Enabled = False

Interval = 100

Добавим меню:

Caption = "&AutoRoad"

Name = mnuAutoRoad

и подменю:

Caption = "&Новая"

Name = mnuNew

Shortcut = F2

Caption = "Н&астройки"

Name = mnuOptions

Shortcut = F3

Caption = "-"

Name = mnuSep

Caption = "&Выход"

Name = mnuExit

Shortcut = F12

Лирическое отступление 3. В редакторе меню для VB почему-то не предусмотрена клавиша F10, которой мы привыкли закрывать приложения, начиная еще с DOS'а. Вообще-то добавить клавишу F10 не сложно, но получается более громоздко. А делается это так: На форме в окне свойств устанавливаем свойство KeyPreview = True; далее записываем процедуру:

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)

    Select Case KeyCode

    Case vbKeyF10

        'здесь мы записываем, какое меню обрабатываем, например

        mnuExit_Click

    End Select

End Sub

Ну и в самом событии меню пишем, что же мы хотели все-таки сделать.

Чтобы избежать данных громоздкостей, я поэтому остановился на клавише F12 для данной учебной программы.

Шаг 8. Сначала опишем действия меню (меню Настройки займемся чуть позже, когда будем проектировать соответствующую форму).

Private Sub mnuExit_Click()

    Unload Me

End Sub

Private Sub mnuNew_Click()

    tmrMove.Enabled = True

End Sub

Меню Exit – просто выгружает форму, а меню Новая игра – запускает таймер движения авто.

В событии таймера опишем движения авто:

Private Sub tmrMove_Timer()

Dim i%

For i = 0 To 5

    OtherAuto(i).y = OtherAuto(i).y + 2

Next

For i = 6 To 11

    OtherAuto(i).y = OtherAuto(i).y - 2

Next

RedrawPic

End Sub

+ или – 2 – это смещение на 2 пиксела по вертикали, относительно нынешней координаты.

Учитывая, что перерисовка картинки у нас происходит достаточно часто, согласно таймеру, каждые 0.1 сек, то  в процедуре движения нашего авто, мы эту перерисовку можем опустить. Удалите из picRoad_KeyDown последнюю строку (RedrawPic).

Запустим и посмотрим: авто двинулись как на параде! Проехали за край и исчезли, а нам хотелось бы повторяемости. Поэтому добавим отслеживание координаты y каждого авто и при полном исчезновении за краем картинки дороги прорисовываем его на противоположной стороне:

Private Sub tmrMove_Timer()

Dim i%

For i = 0 To 5

    If OtherAuto(i).y >= picRoad.Height Then

        OtherAuto(i).y = -31

    ElseIf OtherAuto(i).y <= -31 Then

        OtherAuto(i).y = picRoad.Height

    End If

    OtherAuto(i).y = OtherAuto(i).y + 2

Next

For i = 6 To 11

    If OtherAuto(i).y >= picRoad.Height Then

        OtherAuto(i).y = -31

    ElseIf OtherAuto(i).y <= -31 Then

        OtherAuto(i).y = picRoad.Height

    End If

    OtherAuto(i).y = OtherAuto(i).y - 2

Next

RedrawPic

End Sub

Попробуем запустить еще раз. Авто "строем" доезжают до края дороги и появляются с противоположной стороны. Уже лучше! Но по прежнему неестественно. Некоторые, наверно замечали, что на магистрали автомобили, желающие ехать быстрее, располагаются ближе к осевой линии. Давайте и мы внесем эти изменения в движение нашего транспорта и смещение каждого авто будет не на 2 пиксела, а на 2 + порядковый номер авто.

Private Sub tmrMove_Timer()

Dim i%

For i = 0 To 5

    If OtherAuto(i).y >= picRoad.Height Then

        OtherAuto(i).y = -31

    ElseIf OtherAuto(i).y <= -31 Then

        OtherAuto(i).y = picRoad.Height

    End If

    OtherAuto(i).y = OtherAuto(i).y + 2 + i

Next

For i = 6 To 11

    If OtherAuto(i).y >= picRoad.Height Then

        OtherAuto(i).y = -31

    ElseIf OtherAuto(i).y <= -31 Then

        OtherAuto(i).y = picRoad.Height

    End If

    OtherAuto(i).y = OtherAuto(i).y - 2 - (13 - i)

Next

RedrawPic

End Sub

Запустим проект и понаблюдаем за движением авто.

Шаг 9. Движение стало более "осмысленным". Немного смущает стартовое положение авто (в одну линию). Давайте поправим это. Для генерации случайных чисел в VB существует функция Randomize. Вот ею мы и воспользуемся для определения "случайного" положения авто по вертикали при загрузке программы. Внесем изменения в процедуру Main, расположенную в модуле:

Public Sub Main()

Dim i%

With frmMain

    Auto.x = .picRoad.Width * 0.75

    Auto.y = .picRoad.Height * 0.5

    Randomize

    For i = 0 To 5

        OtherAuto(i).x = (i * 25) + (.picRoad.Width * 0.05)

        OtherAuto(i).y = Int((.picRoad.Height + 1) * Rnd)

    Next

    For i = 6 To 11

        OtherAuto(i).x = ((i - 6) * 25) + (.picRoad.Width * 0.5)

        OtherAuto(i).y = Int((.picRoad.Height + 1) * Rnd)

    Next

    .Show

    .RedrawPic

End With

End Sub

Теперь при открытии формы и встречные и попутные авто располагаются в случайном порядке.

Лирическое отступление 4. Функция Randomize в общем виде выглядит следующим образом:

Сначала производится генерация случайного числа от 0 до 1

Randomize

А затем, с учетом верхней и нижней границ для случайных чисел, производится вычисление:

Int((ВерхняяГраница - НижняяГраница + 1) * Rnd + НижняяГраница)

Шаг 10. Автомобили едут, согласно предписаний. И только наше авто может перемещаться вправо и влево с нулевой скоростью движения вперед. Пора-пора заняться скоростными качествами нашего авто.

Откроем форму и добавим два лейбла. Первый:

Name = lblCaption

Caption ="Скорость:"

Font.Bold = True

Font.Size = 10

ForeColor = &H00FF0000&

Height = 16

Index = 0

Left = 348

Top = 64

Width = 74

И второй:

Name = lblSpeed

AutoSize = True

Caption ="0"

Font.Bold = True

Font.Size = 10

ForeColor = &H00FF0000&

Height = 16

Left = 428

Top = 64

Добавим новую переменную в раздел деклараций модуля:

Public Speed As Integer

В форме в процедуру picRoad_KeyDown внесем дополнения для нажатий на клавиши вверх-вниз. Для клавиши "вниз" сразу же исключим отрицательные значения скоростей. Для клавиши "вверх" – ограничение максимальной скорости мы введем позже. После изменения скорости отобразим это в соответствующем лейбле, выбрав чисто эмпирически коэффициент для приближения к действительности (я решил, что таким коэффициентом может быть, например, 20)

Private Sub picRoad_KeyDown(KeyCode As Integer, Shift As Integer)

Select Case KeyCode

...

Case vbKeyUp

    Speed = Speed + 1

    lblSpeed.Caption = Speed * 20

Case vbKeyDown

    Speed = Speed - 1

    If Speed < 0 Then Speed = 0

    lblSpeed.Caption = Speed * 20

End Select

End Sub

Если сейчас запустить проект, то мы увидим изменения, происходящие в отображении скорости (т.е. в лейбле), но никак это не отобразится на самом движении автомобилей. Поэтому, заканчивая шаг 10, сделаем "видимым" наше изменение скорости. Т.е. попутные авто с увеличением скорости должны двигаться относительно нас медленнее, а встречные на ту же самую скорость – быстрее. Для этого добавим значение переменной Speed к скорости перемещения всех авто.

Private Sub tmrMove_Timer()

Dim i%

For i = 0 To 5

    If OtherAuto(i).y >= picRoad.Height Then

        OtherAuto(i).y = -31

    ElseIf OtherAuto(i).y <= -31 Then

        OtherAuto(i).y = picRoad.Height

    End If

    OtherAuto(i).y = OtherAuto(i).y + 2 + i + Speed

Next

For i = 6 To 11

    If OtherAuto(i).y >= picRoad.Height Then

        OtherAuto(i).y = -31

    ElseIf OtherAuto(i).y <= -31 Then

        OtherAuto(i).y = picRoad.Height

    End If

    OtherAuto(i).y = OtherAuto(i).y - 2 - (13 - i) + Speed

Next

RedrawPic

End Sub

Фух-х-х! Пол-дела сделано! :-)))

Шаг 11. Теперь ответственный момент – мы должны отреагировать на столкновение автомобилей. В нашей игрушке это должно произойти, когда любая точка нашего авто пересечется с любой точкой любого другого авто. Запишем эту процедуру:

Private Sub Crash()

Dim i%

For i = 0 To 11

'при совпадении координат

    'по вертикали

    If ((Auto.y + 31 >= OtherAuto(i).y) And _
        (Auto.y + 31 <= OtherAuto(i).y + 31)) Or _
        ((Auto.y >= OtherAuto(i).y) And _
        (Auto.y <= OtherAuto(i).y + 31)) Then

        '+ по горизонтали

        If ((Auto.x + 23 >= OtherAuto(i).x) And _
            (Auto.x + 23 <= OtherAuto(i).x + 23)) Or _
            ((Auto.x >= OtherAuto(i).x) And _
            (Auto.x <= OtherAuto(i).x + 23)) Then

            ' т.е. при столкновении - обнуляем скорость нашего авто

            Speed = 0

            lblSpeed.Caption = "0"

        End If

    End If

Next

End Sub

Данная процедура должна проверяться постоянно при движении всех авто – т.е. в процедуре таймера, непосредственно перед перерисовкой всех объектов:

Private Sub tmrMove_Timer()

...

    Crash

    RedrawPic

End Sub

Теперь при запуске проекта, в период столкновения скорость нашего авто сбрасывается до нуля.

 

Продолжение статьи

Hosted by uCoz