Статьи

Как написать игрушку на VB
(продолжение)

 

Шаг 12. Продолжаем улучшать внешний вид нашей игры. Добавим еще 2 лейбла для отображения пройденного нашим автомобилем пути. Первый с параметрами:

Name = lblCaption

Caption ="Пройденный путь:"

Font.Bold = True

Font.Size = 10

ForeColor = &H000000FF&

Height = 16

Index = 1

Left = 348

Top = 84

Width = 136

И второй:

Name = lblPath

AutoSize = True

Caption ="0"

Font.Bold = True

Font.Size = 10

ForeColor = &H000000FF&

Height = 16

Left = 488

Top = 84

В событие таймера внесем изменения пройденного расстояния. Если верить учебнику математики за 3 класс, то расстояние высчитывается по формуле S = v * t. И скорость и интервал времени у нас известны.

Private Sub tmrMove_Timer()

...

    Crash

    lblPath.Caption = lblPath.Caption + (lblSpeed.Caption * 0.01)

    RedrawPic

End Sub

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

Шаг 13. Любая игра имеет свои ограничения. Чаще всего это бывает ограничением по времени. Не будем оригинальными и ограничим пользователя, допустим, 1 минутой. Отсчет времени должен видеть пользователь, поэтому на форме frmMain расположим еще 2 лейбла и новый таймер.

Первый лейбл:

Name = lblCaption

Caption ="Время игры:"

Font.Bold = True

Font.Size = 10

ForeColor = &H00000000&

Height = 16

Index = 2

Left = 348

Top = 104

Width = 90

Второй лейбл:

Name = lblTime

AutoSize = True

Caption ="0:01:00"

Font.Bold = True

Font.Size = 10

ForeColor = &H00000000&

Height = 16

Left = 444

Top = 104

Таймер:

Name = tmrTime

Enabled = False

Interval = 1000

Добавим в процедуру запуска новой игры инициализацию второго таймера.

Private Sub mnuNew_Click()

    tmrMove.Enabled = True

    tmrTime.Enabled = True

End Sub

А в процедуру работы самого таймера добавим обратный отсчет времени и остановку игры по его истечении, а также обнуление переменной Speed.

Private Sub tmrTime_Timer()

lblTime.Caption = CDate(CDate(lblTime.Caption) - CDate("0:00:01"))

If lblTime.Caption = "0:00:00" Then

    tmrMove.Enabled = False

    tmrTime.Enabled = False

    MsgBox "Пройденное расстояние - " & lblPath.Caption _
        & " км.", vbInformation + vbOKOnly, "Игра закончена"

    lblSpeed.Caption = "0"

    lblPath.Caption = "0"

    lblTime.Caption = "0:01:00"

    Speed = 0

End If

Итак, в черновике игра сделана. У нас есть автомобиль, которым мы управляем (скорость и направление); встречные и попутные машины, двигающиеся с различной скоростью, столкновение с которыми, ведет к остановке нашего автомобиля; мы можем отслеживать нашу скорость и пройденный путь, а так же зафиксировать остановку игры через определенный интервал времени. В таком случае напрашивается вопрос: "Чего будет недостаточно пользователю в данной игрушке?" Ответ – один: ощущения, что он сам может устанавливать правила игры. Давайте позволим ему тешиться этой мыслью и предоставим ему "свободу" в том объеме, в котором мы сами пожелаем. Итак ...

Шаг 14. Собираем форму "Настройки". Вызываем меню: Project/Add Form и выбираем обычную форму. Ее параметры:

Name = frmOption

BorderStyle = 1 – Fixed Single

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

Height = 1815

Icon – устанавливаем ту же, что и на основную форму

StartupPosition = 1 – CenterOwner

Width = 4860

NB! В данной форме мы не будем работать с графикой и API-функцией, поэтому нет смысла изменять  свойство ScaleMode. В этой форме мы будем работать с твипами.

Разместим на форме 4 лейбла для надписей:

Name = lblCaption

Index = 0; 1; 2; 3

Caption = "Максимальная скорость"; "Время"; "Количество попутных авто"; "Количество встречных авто"

Height = 195

Left = 120

Top = 120; 420; 720; 1020

Width = 2295

Добавим два текстовых поля:

Name = txtMaxSpeed

Height = 285

Left = 2520

MaxLength = 3

Text = "300"

Top = 60

Width = 735

----------------

Name = txtTime

Height = 285

Left = 2520

MaxLength = 7

Text = "0:01:00"

Top = 360

Width = 735

И два выпадающих списка:

Name = cboNumAuto

Index = 0; 1

Left = 2520

List = 0|1|2|3|4|5|6

Style = 2 – Dropdown List

Top = 660; 960

Width = 795

Ну и наконец две кнопки:

Name = cmdOKCancel

Index = 0; 1

Caption = "ОК"; "Отмена"

Height = 215

Left = 3480

Top = 540; 960

Width = 1155

В форме frmMain сделаем вызов новой формы

Private Sub mnuOptions_Click()

    frmOption.Show vbModal

End Sub

Теперь можно попробовать запустить проект.

Шаг 15. Где можно хранить настройки? Вариантов несколько, но наиболее часто встречаемые это либо в реестре, либо в отдельном файле (например *.ini). В нашем случае давайте будем сохранять в реестре. В VB для работы с реестром существует 4 встроенных функции: GetSetting, SaveSetting, DeleteSetting и GetAllSettings. У них существует один недостаток: они работают только с одной ветвью реестра – HKEY_CURRENT_USER\SOFTWARE\VB and VBA Program Settings. В принципе, мы не будем хранить никакой секретной информации типа пароля, серийного номера и т.п., поэтому нас устроят и эти функции (а вернее, первые две из них, которые считывают и записывают данные из реестра). Единственное что мы сделаем – это "обертку" для них, чтобы нам было удобнее пользоваться. Так как обращаться к реестру мы будем из обеих форм, то нашу функцию мы должны разместить, естественно, в модуле.

Для начала опишем две нумерованные константы в разделе деклараций модуля.

NB! Нумерованные константы могут принимать только целочисленные значения.

Public Enum constTypeOptions

    GetOption = 0

    SaveOption = 1

End Enum

Public Enum constKeyOptions

    MaxSpeed = 0

    Time = 1

    AutoUp = 2

    AutoDown = 3

End Enum

Ну а теперь и саму функцию-обертку:

Public Function Options(ByVal TypeOptions As constTypeOptions, _
    ByVal Key As constKeyOptions, Optional Setting As String) As String

Select Case TypeOptions

Case GetOption 'считывание

    Options = GetSetting(App.EXEName, "Options", Key)

Case SaveOption 'запись

    SaveSetting App.EXEName, "Options", Key, Setting

End Select

End Function

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

Public Sub Main()

Dim i%

If Options(GetOption, Time) = vbNullString Then

    Options SaveOption, AutoDown, 6

    Options SaveOption, AutoUp, 6

    Options SaveOption, MaxSpeed, 300

    Options SaveOption, Time, "0:01:00"

End If

...

End Sub

Запустим проект и сразу же закроем. Зайдем в реестр: кнопка "Пуск" – меню "Выполнить" – в текстовом поле наберем "regedit" и нажмем ОК. Откроем ветку HKEY_CURRENT_USER\SOFTWARE\VB and VBA Program Settings. Опустимся ниже в наш проект: AutoRoad\Options. Если все сделано правильно, то в правом окне Вы должны увидеть следующие записи:

(По умолчанию) – (Значение не присвоено)

0 – "300"

1 – "0:01:00"

2 – "6"

3 – "6"

Т.е. наши параметры по умолчанию были записаны.

Шаг 16. Перейдем в форму frmMain, и в событии загрузки формы запишем считывание в лейбл времени.

Private Sub Form_Load()

    lblTime.Caption = Options(GetOption, Time)

End Sub

Поставим ограничение максимальной скорости нашего авто:

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

Select Case KeyCode

...

Case vbKeyUp

    Speed = Speed + 1

    If Speed * 20 > Options(GetOption, MaxSpeed) Then _

        Speed = Options(GetOption, MaxSpeed) / 20

    lblSpeed.Caption = Speed * 20

...

End Select

End Sub

В событии таймера, следящего за временем игры, сделаем исправление для лейбла времени на окончание игры.

Private Sub tmrTime_Timer()

lblTime.Caption = CDate(CDate(lblTime.Caption) - CDate("0:00:01"))

If lblTime.Caption = "0:00:00" Then

...

    lblTime.Caption = Options(GetOption, Time)

    Speed = 0

End If

End Sub

Пока на этом закончим и перейдем к ...

Шаг 17.  ...форме frmOptions. Эта форма должна при открытии считать данные реестра и заполнить соответствующие поля, а при выходе сохранить изменения в реестре.

Private Sub Form_Load()

    txtTime.Text = Options(GetOption, Time)

    txtMaxSpeed.Text = Options(GetOption, MaxSpeed)

    cboNumAuto(0).ListIndex = Options(GetOption, AutoUp)

    cboNumAuto(1).ListIndex = Options(GetOption, AutoDown)

End Sub

Private Sub cmdOKCancel_Click(Index As Integer)

Select Case Index

Case 0 'если ОК - сохраняемся в реестре

    Options SaveOption, Time, txtTime.Text

    Options SaveOption, MaxSpeed, txtMaxSpeed.Text

    Options SaveOption, AutoUp, CStr(cboNumAuto(0).ListIndex)

    Options SaveOption, AutoDown, CStr(cboNumAuto(1).ListIndex)

End Select

'закрытие формы

Unload Me

End Sub

В случае, если нажимается кнопка Отмены, то происходит просто выгрузка формы без сохранения данных в реестр.

Теперь можно немного поиграть с окном настроек, выискивая недостатки.

Шаг 18. Их (недостатков) оказалось не так много, но они все-таки есть. Если в ComboBox'ах мы особенно-то развернуться пользователю не даем (можно только выбирать из предложенного), то в текстовых полях – пиши чего хочешь. На стадии рисования формы мы ограничили количество вводимых знаков в текстовые поля (свойство MaxLenght), теперь давайте займемся ограничением ввода тех знаков, которые мы хотим разрешить вводить пользователю. Очень удобным для этого является событие KeyPress, которое использует ASCII-коды каждого символа. Мы проверяем вводимый символ и если он допустим – ничего не делаем, а если нет – не выводим его и выдаем звуковой сигнал.

Private Sub txtMaxSpeed_KeyPress(KeyAscii As Integer)

Select Case KeyAscii

Case 8, 48 To 57 'backspace + цифры от 0 до 9

Case Else

    Beep

    KeyAscii = 0

End Select

End Sub

Private Sub txtTime_KeyPress(KeyAscii As Integer)

Select Case KeyAscii

Case 8, 48 To 57, 58 'backspace + цифры от 0 до 9 + двоеточие

Case Else

    Beep

    KeyAscii = 0

End Select

End Sub

Теперь проблем с вводом цифр нет и поле Максимальной скорости работает идеально, а вот с полем времени проблемы еще остались. Например, мы можем ввести такую запись ":::7755". Давайте ЗАСТАВИМ пользователя правильно ввести время:

Private Sub cmdOKCancel_Click(Index As Integer)

Select Case Index

Case 0 'если ОК - сохраняемся в реестре

    'проверка на правильность введения времени

    If Not IsDate(txtTime.Text) Then

        MsgBox "В поле не время!", vbExclamation, "Ошибка!"

        Exit Sub

    End If

    Options SaveOption, Time, txtTime.Text

    Options SaveOption, MaxSpeed, txtMaxSpeed.Text

    Options SaveOption, AutoUp, CStr(cboNumAuto(0).ListIndex)

    Options SaveOption, AutoDown, CStr(cboNumAuto(1).ListIndex)

End Select

'закрытие формы

Unload Me

End Sub

Осталось изменить параметры в форме frmMain при выгрузке формы frmOptions

Private Sub Form_Unload(Cancel As Integer)

'передача данных в основную форму

    frmMain.lblTime.Caption = Options(GetOption, Time)

End Sub

Количеством встречных и попутных авто мы займемся чуть-чуть позже.

Шаг 19. Итак это "чуть-чуть позже" наступило. Для начала выведем из процедуры Main в модуле часть кода, для определения случайного положения машин, в отдельную процедуру и назовем ее RndAuto. А циклы For ... Next привяжем к данным о количестве машин, хранящихся в реестре. В исправленном виде обе процедуры должны выглядеть так:

Public Sub Main()

If Options(GetOption, Time) = vbNullString Then

    Options SaveOption, AutoDown, 6

    Options SaveOption, AutoUp, 6

    Options SaveOption, MaxSpeed, 300

    Options SaveOption, Time, "0:01:00"

End If

With frmMain

    Auto.x = .picRoad.Width * 0.75

    RndAuto

    .Show

    .RedrawPic

End With

End Sub

Public Sub RndAuto()

Dim i%

With frmMain

    Randomize

    For i = 0 To Options(GetOption, AutoDown) - 1

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

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

    Next

    For i = 6 To Options(GetOption, AutoUp) + 5

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

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

    Next

End With

End Sub

В форме Настроек на выходе добавляем процедуру RndAuto и заставляем основную форму перерисоваться - frmMain.RedrawPic

Private Sub Form_Unload(Cancel As Integer)

    With frmMain

        .lblTime.Caption = Options(GetOption, Time)

        RndAuto

        .RedrawPic

    End With

End Sub

Осталось немного: сделать исправления в цикле RedrawPic, относительно настроек реестра

Public Sub RedrawPic()

Dim i%

    DrawRoad

    DrawAuto

    For i = 0 To Options(GetOption, AutoDown) - 1

        DrawOtherAuto i

    Next

    For i = 6 To Options(GetOption, AutoUp) + 5

        DrawOtherAuto i

    Next

End Sub

и тоже самое в циклах процедуры tmrMove_Timer:

Private Sub tmrMove_Timer()

Dim i%

For i = 0 To Options(GetOption, AutoDown) - 1

...

Next

For i = 6 To Options(GetOption, AutoUp) + 5

...

Next

...

End Sub

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

Шаг 20. При желании, можно заняться немного украшательством. Давайте создадим ActiveX Control, который будет эмулировать работу спидометра.

NB! ActiveX Control'ы могут существовать в 2-х видах: как отдельный файл (для многоразового использования в различных программах) и как составная часть конкретной программы.

Мы воспользуемся встроенным ActivX Control'ом. Меню Project/Add User Control – добавит его в Ваш проект. Установим некоторые его свойства:

Name = Speedometr

AutoRedraw = True

BorderStyle = 1 – Fixed Single

DrawStyle = 0 – Solid

FillColor = &H00FFFFC0&

FillStyle = 0 – Solid

ToolboxBitmap – вставьте любую картинку (bmp) 15 х 16 пиксел, какую хотите увидеть на панели инструментов.

Далее воспользуемся мастером создания ActivX Control'ов. Для этого выберите меню Add- Ins/Add-Inn Manager ... В открывшемся окне выберите VB6 ActiveX Ctrl Interface Wizard, а в CheckBox'е "Loaded/Unloaded" – поставьте галочку и нажмите ОК. Теперь через меню Add-Ins/ActiveX Control Interface Wizard вызовем непосредственно сам мастер и воспользуемся его пошаговыми услугами.

Step 1. В правом списке оставим только свойство BackColor.

Step 2. С помощью кнопки New добавим свои свойства: Min, Max, Value, ArrowColor, ConturColor, ArrowWidth

Step 3. Здесь мы сделаем привязку некоторых свойств к свойствам самого UserControl'a:

BackColor – UserControl.BackColor

ConturColor – UserControl.FillColor

ArrowWidth – UserControl.DrawWidth

Step 4. Для оставшихся свойств выберем значения (Public Name – Data Type – Default Value):

ArrowColor – OLE_COLOR – 0

Max – Long – 100

Min – Long – 0

Value – Long - 0

Нажмем кнопку ОК. Мастер вчерне сгенерировал нам код. Сделаем маленькие дополнения, чтобы наш контрол нормально изображал спидометр. Вначале очистим от предыдущей графики. Следующие две строки служат для украшательства: они рисуют светлую полоску сверху и полукруг внизу в центре. А вот последняя строка – основная – она рисует стрелку спидометра (прямая линия) с началом внизу в центре и концом вверху в точке, зависящей от значения Value (пересчет производится в единицы, относительно ширины контрола).

Private Sub Draw()

Cls

Line (0, 0)-(ScaleWidth, ScaleHeight * 0.1), ConturColor, BF

Circle (ScaleWidth * 0.5, ScaleHeight), ScaleHeight * 0.1, ConturColor

Line (ScaleWidth * 0.5, ScaleHeight)-(ScaleWidth * (m_Value – m_Min)/ _
    (m_Max - m_Min), 0), m_ArrowColor

End Sub

Теперь, чтобы эта процедура заработала ее необходимо вставить в каждое Property Let нашего контрола, примерно так:

Public Property Let Value(ByVal New_Value As Long)

    m_Value = New_Value

    PropertyChanged "Value"

    Draw

End Property

и добавить процедуру, инициализирующую графику при запуске контрола:

Private Sub UserControl_Show()

    Draw

End Sub

Контрол готов.

Лирическое отступление 5. Для тех, кто заинтересовался созданием ActivX Control'ов – отсылаю к циклу своих учебных статей

Шаг 21. Привязываем контрол к форме. В frmMain внесем изменения в события Form_Load, picRoad_KeyDown, Crash, tmrTime_Timer, чтобы привязать к изменениям, происходящих с переменной Speed и дублирующих изменения lblSpeed, только в графическом варианте:

Private Sub Form_Load()

    lblTime.Caption = Options(GetOption, Time)

    Speedometr1.Max = Options(GetOption, MaxSpeed)

End Sub

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

Select Case KeyCode

...

Case vbKeyUp

    Speed = Speed + 1

    If Speed * 20 > Options(GetOption, MaxSpeed) Then _
        Speed = Options(GetOption, MaxSpeed) / 20

    lblSpeed.Caption = Speed * 20

    Speedometr1.Value = Speed * 20

Case vbKeyDown

    Speed = Speed - 1

    If Speed < 0 Then Speed = 0

    lblSpeed.Caption = Speed * 20

    Speedometr1.Value = Speed * 20

End Select

End Sub

Private Sub Crash()

...

            Speed = 0

            lblSpeed.Caption = "0"

            Speedometr1.Value = 0

...

End Sub

Private Sub tmrTime_Timer()

...

    lblTime.Caption = Options(GetOption, Time)

    Speedometr1.Value = 0

    Speed = 0

End If

End Sub

В frmOption – в событие Form_Unload так же внесем изменения, которые характеризуют изменения в контроле, а именно его максимальное значение:

Private Sub Form_Unload(Cancel As Integer)

    With frmMain

        .lblTime.Caption = Options(GetOption, Time)

        .Speedometr1.Max = Options(GetOption, MaxSpeed)

        RndAuto

        .RedrawPic

    End With

End Sub

Заключение. За 21 шаг сделана вполне функциональная игрушка. У каждого, кто программирует , вполне возможно, появятся свои идеи, как улучшить данное приложение, чтобы оно стало еще интереснее для пользователя. Что это будет: улучшение графики или увеличение возможностей движения авто – решать Вам. Автор данной статьи будет благодарен за присланные нововведения в вариант этой игрушки.

Здесь можно взять полный листинг

2000 г.

Назад

Hosted by uCoz