Шаг 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 г.