Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 

КАК Я РАЗРАБАТЫВАЛ ИГРУ "СТРЕЛОК 50"

 

Половый Александр

 

 

ПРОЛОГ

 

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

Поэтому меня можно отнести скорее к программистам-любителям, у которых есть E‑mail только на бесплатном сервере Polovy@mail.ru. Но, честно говоря, мне кажется, не этот признак должен определять любитель ты или профессионал.

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

Часто бывает, что я берусь за программу для того, чтобы доказать (скорее самому себе), что на VB можно сделать все и даже больше. Я вижу чью-то разработку и говорю себе "я это сделаю на VB!". Например, так я поступил с "Виртуальной линейкой" (Ruler.exe), ее я сделал после того, как увидел программу Cool Ruler (www.fabsoft.com). Если Вам интересно, сравните их. У еще одной     моей программы RulerR.exe вообще аналогов нет, ведь она вращается!

Так вот игру "Стрелок 50" я задумал после того, как поиграл в одну из "стрелялок".

"Это просто" сказал я себе и принялся за работу.

 

 

"СТРЕЛЯЛКА" ПО ПРОИЗВОЛЬНО ДВИЖУЩИМСЯ ЦЕЛЯМ

 

Итак, что нам нужно? Прежде всего я определился, что моя программа работает в двух режимах:

1) Режим "ИГРА" FGamePlay = True, когда я как бешенный должен носиться за мишенями по всему окну и уничтожить как можно больше мишеней.

2) Режим "СТАНДАРТНАЯ ПРОГРАММА" FGamePlay = False, в котором моя игра выполняет функции любой другой программы: открывается,  закрывается, меняет свои размеры, может быть поверх остальных окон, включает/выключает звуки и сохраняет эти параметры в INI-файле.

 

Сначала поговорим о режиме "ИГРА".

 

 

СТРЕЛЬБА

 

Стрельба должна быть похожа на настоящую, для этого курсор мыши превращаем в перекрестие:

…

Me.MousePointer = 99

Me.MouseIcon = Image2.Picture

…

В Image2.Picture я поместил файл курсора в виде перекрестия. Дефицита в подобных файлах сейчас нет, а кое-что можно и самому нарисовать, например в ImageEdit.exe.

Выстрел должен звучать:

…

Dim PW As Integer

…

If MnuGameSound.Checked = True Then

PW = sndPlaySound(SW(1), SND_ASYNC Or SND_NODEFAULT)

End If

…

Флаг SND_ASYNC – прерывает предыдущий звук и проигрывает текущий

Флаг SND_NODEFAULT – не позволяет системе проигрывать звук по умолчанию, если не найден текущий звук

Путь к файлу со звуком выстрела 1.wav хранится в переменной SW(1):

…

SW(1) = App.Path & "\1.WAV"

…

Не забываем в Module1.bas указать функцию sndPlaySound:

…

Public Declare Function sndPlaySound Lib "winmm.dll" Alias "sndPlaySoundA" (ByVal lpszSoundName As String, ByVal uFlags As Long) As Long

Global Const SND_ASYNC = 1

Global Const SND_SYNC = 0

Global Const SND_NODEFAULT = &H2       

…

Если прицел наведен и выстрел сделан, то обязательно появится пулевое отверстие:

…

If Button = 1 Then

…

IShoot = IShoot + 1

…

Load Image4(IShoot)

Image4(IShoot).Picture = Image4(0).Picture

Image4(IShoot).Left = X - Image4(IShoot).Width / 2

Image4(IShoot).Top = Y - Image4(IShoot).Height / 2

Image4(IShoot).Visible = True

…

End If

…

Реагируем только на левую кнопку мыши Button = 1. С каждым выстрелом количество отверстий IShoot увеличивается на 1. Получается массив объектов Image4, "клонированных" из Image4(0).Picture.

Процессы создания отверстий помещаем в события MouseDown всех объктов, находящихся на форме – это сама форма, метки на ней, мишени да и сами картинки с пулевыми отверстиями.

Если событие произошло на мишени Image1 – то попадание:

…

Private Sub Image1_MouseDown(Index As Integer, Button As Integer, Shift As Integer, X As Single, Y As Single)

…

CurNShoots = CurNShoots + 1 'общее количество выстрелов

…

CurNTargetsTerm = CurNTargetsTerm + 1 'мишень унитожена

RatCurNTargetsTerm = RatCurNTargetsTerm + 1 * ScaleKoef 'стоимость мишени

FlagTargetTerm(Index) = True ' цель  уничтожена

…

End Sub

…

Флаг FlagTargetTerm показывает: уничтожена мишень или нет.

На остальных объектах – промах, например на форме:

…

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)

…

CurNShoots = CurNShoots + 1 'общее количество выстрелов

…

CurNMiss = CurNMiss + 1 ' число промахов

RatCurNMiss = RatCurNMiss + 1 * ScaleKoef ' стоимость промаха

…

End Sub

…

Со временем на форме остается слишком много пулевых отверстий, поэтому периодически, скажем 1 раз в 3 секунды, проводим их "зачистку":

…

'------------------------------------------------------

'удалить пулевые отверстия

'------------------------------------------------------

Private Sub Timer4_Timer()

If FGamePlay = True Then

ClearHoles 'очищаем отверстия от пуль

Else ' игра окончена

Timer4.Enabled = False

End If

End Sub

…

Private Sub ClearHoles()

Dim I As Integer

For I = Image4.Count - 1 To 1 Step -1

Unload Image4(I)

Next I

IShoot = 0

End Sub

'------------------------------------------------------

'удалить пулевые отверстия

'------------------------------------------------------

…

 

 

МИШЕНИ

 

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

Чем любая игра похожа на жизнь? Непредсказуемостью.

За непредсказуемость у нас отвечает генератор случайных чисел в интервале от Nmin до Nmax:

…

Private Function NRnd(Nmin As Integer, Nmax As Integer) As Integer

Randomize

NRnd = Int((Nmax - Nmin + 1) * Rnd) + Nmin 'произвольный номер

End Function

…

и генератор случайного направления (знака "+" или "–") :

…

Private Function NSgnRnd() As Integer

Randomize

If Int(2 * Rnd) = 0 Then NSgnRnd = 1 Else NSgnRnd = -1

End Function

…

Вот этими базовыми функциями я и напичкал процессы создания и движения мишеней. Процессы эти сложные, так что без таймеров не обойтись Timer1 отвечает за "движение", а Timer2 за "рождение" мишени.

 

 

РОЖДЕНИЕ МИШЕНИ

 

Все мишени я отсортировал по типу движения TypeTarget:

…

Select Case TypeTarget(I) ' тип движения цели

Case 0 ' не подвижен

…

Case 1 'горизонтальное без смены направления

…

Case 2  'горизонтальное со сменой направления

…

Case 3 'вертикальное без смены направления

…

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

…

Case 5, 6  ' под любым углом без смены направления

…

Case 7, 8 ' под любым углом со сменой направления

…

Case 9 To 12 ' по окружности

…

End Select

…

Тип движения цели генерируется так:

…

TypeTarget(ITarget) = NRnd(0, 12) ' случайный тип движения цели

…

Чтобы управлять долей вероятности создания того или иного типа мишени, я на каждый тип отвел разное количество допустимых значений ITarget. Так для неподвижной мишени ITarget=0, а для двигающейся по окружности ITarget=9, 10, 11, 12, что означает вероятность возникновения последней мишени в 4 раза выше, чем первой, а этого мне и хотелось.

У каждой мишени есть свои характеристики: внешний вид, скорость (точнее шаг движения по горизонтали и вертикали, направление и т.п. (изменять размер мишеней я не захотел). Все эти характеристики присваиваются мишени в момент ее рождения:

…

Private Sub Timer2_Timer()

…

ITarget = Image1.Count 'новая цель

…

Load Image1(ITarget) ' загружаем цель

…

ReDim Preserve TypeTarget(1 To ITarget) 'создаем тип двжения

ReDim Preserve FlagTargetTerm(1 To ITarget)

…

ReDim Preserve LeftStepTarget(1 To ITarget) 'шаг по горизонтали

ReDim Preserve TopStepTarget(1 To ITarget) 'шаг по виртекали

…

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

ReDim Preserve AlfaTargetR(1 To ITarget)

ReDim Preserve AlfaStepTargetR(1 To ITarget)

ReDim Preserve RStepTargetR(1 To ITarget)

ReDim Preserve XrStepTargetR(1 To ITarget)

ReDim Preserve YrStepTargetR(1 To ITarget)

…

End Sub

…

Оператор ReDim изменяет размер массива, а параметр Preserve заставляет сохранять информацию, хранящуюся в массиве перед изменением.

Массивы задаются на уровне формы без задания размерности:

…

Dim TypeTarget() As Integer ' тип движения цели

Dim ITarget  As Integer ' количество целей

Dim FlagTargetTerm() As Boolean ' цель уничтожена?

…

'прямолинейное движение

Dim LeftStepTarget() As Single ' шаг перемещения

Dim TopStepTarget() As Single ' шаг перемещения

…

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

Dim AlfaTargetR() As Single ' угол перемещения

Dim AlfaStepTargetR() As Single ' шаг угла перемещения

Dim RStepTargetR() As Single ' шаг перемещения

Dim XrStepTargetR() As Single ' шаг перемещения

Dim YrStepTargetR() As Single ' шаг перемещения

…

Ну и разумеется, значения присваиваем этим характеристикам с максимальной непредсказуемостью (в пределах разумного конечно):

…

AlfaTargetR(ITarget) = NRnd(0, 360) / 60

AlfaStepTargetR(ITarget) = NRnd(5, 15) / 100 * NSgnRnd

RStepTargetR(ITarget) = (Me.ScaleWidth + Me.ScaleHeight) / 6 * NRnd(1, 100) / 100

XrStepTargetR(ITarget) = Me.ScaleWidth * NRnd(1, 100) / 100

YrStepTargetR(ITarget) = Me.ScaleHeight * NRnd(1, 100) / 100

…

'случайный шаг и направление

LeftStepTarget(ITarget) = NRnd(50, 150) * NSgnRnd

TopStepTarget(ITarget) = NRnd(50, 150) * NSgnRnd

…

FlagTargetTerm(ITarget) = False ' цель не уничтожена

…

'случайная картинка цели

I = NRnd(0, 9)

Image1(ITarget).Picture = Image3(I).Picture

…

'размеры

Image1(ITarget).Width = 32 * Screen.TwipsPerPixelX

Image1(ITarget).Height = 32 * Screen.TwipsPerPixelY

…

В качестве картинок я выбрал иконки старого доброго Windows (иногда так охота "расстрелять" его!).

Чтобы мишень была выше других объектов на форме и в нее можно было стрельнуть, ее надо "поднять":

…

Image1(ITarget).Visible = True

Image1(ITarget).ZOrder

…

Ну и чтобы было веселее, сделаем звук "рождения " мишени:

…

PW = sndPlaySound(SW(2), SND_ASYNC Or SND_NODEFAULT)

…

Для разнообразия управляем интервалами между созданиями мишеней, чтобы это было неравномерно:

…

'меняем интервал создания новой мишени от 0.1 до 1 сек

Timer2.Interval = NRnd(100, 1000)

…

Чтобы в игре одновременно существовало не более 10 мишеней я поставил условие:

…

If Image1.Count - 1 < 10 Then 'больше 10 одновременно нельзя

…

'Создание новой мишени

…

End If

…

Уничтоженные мишени на самом деле просто делаются невидимыми Visible=False (см. ниже), поэтому как только появляется такая "вакансия", старая убитая невидимая мишень становится новой видимой:

…

'используем удаленную мишень для новой

For I = 1 To Image1.Count - 1 ' обрабатываем все картинки

If Image1(I).Visible = False Then ' цель не видна

ITarget = I

Exit For

End If

Next I

…

Вот все это хозяйство оформляем в событии Timer2_Timer.

 

 

ДВИЖЕНИЕ МИШЕНИ

 

Я поставил интервал Timer1, как мне кажется, на самый короткий промежуток времени Timer1.Interval=50. Выше - медленно, ниже - система не успевает отработать.

В движении мишени ничего особенного нет, ну разве что движение по окружности прикольное. Если хотите добавьте сюда "тряску" мишени вокруг основной траектории с помощью того же Rnd, но я посчитал, что это слишком.

…

Private Sub Timer1_Timer()

…

If FGamePlay = True Then 'игра идет

…

For I = 1 To Image1.Count - 1 ' обрабатываем все картинки

…

If FlagTargetTerm(I) = False Then ' цель не поражена

…

Select Case TypeTarget(I) ' тип движения цели

Case 0 ' не подвижен

…

Case 1 'горизонтальное без смены направления

Image1(I).Left = Image1(I).Left + LeftStepTarget(I)

If Image1(I).Left > Me.ScaleWidth Then Image1(I).Left = 0

If Image1(I).Left < -Image1(I).Width Then Image1(I).Left = Me.ScaleWidth

If Image1(I).Top >= Me.ScaleHeight - Image1(I).Height Then Image1(I).Top = Me.ScaleHeight - Image1(I).Height

…

Case 7, 8 ' под любым углом со сменой направления

Image1(I).Left = Image1(I).Left + LeftStepTarget(I)

If Image1(I).Left > Me.ScaleWidth Then LeftStepTarget(I) = -LeftStepTarget(I)

If Image1(I).Left < -Image1(I).Width Then LeftStepTarget(I) = -LeftStepTarget(I)

Image1(I).Top = Image1(I).Top + TopStepTarget(I)

If Image1(I).Top > Me.ScaleHeight Then TopStepTarget(I) = -TopStepTarget(I)

If Image1(I).Top < -Image1(I).Height Then TopStepTarget(I) = -TopStepTarget(I)

Case 9 To 12 ' по окружности

AlfaTargetR(I) = AlfaTargetR(I) + AlfaStepTargetR(I)

Image1(I).Left = XrStepTargetR(I) + RStepTargetR(I) * Cos(AlfaTargetR(I))

Image1(I).Top = YrStepTargetR(I) + RStepTargetR(I) * Sin(AlfaTargetR(I))

End Select

…

End If

'-----------------------------------------------------

Next I

'-----------------------------------------------------

Else ' игра окончена

Timer1.Enabled = False

End If

End Sub

…

Это если цель не поражена, иначе делаем "гибель цели", например, сначала уменьшаем ее, а затем делаем невидимой:

…

Else 'цель поражена

If Image1(I).Visible = True Then

ImageWidth = Image1(I).Width - 5 * Screen.TwipsPerPixelX

ImageHeight = Image1(I).Height - 5 * Screen.TwipsPerPixelY

If ImageWidth < 0 Or ImageHeight < 0 Then

ImageWidth = 0

ImageHeight = 0

Image1(I).Visible = False

End If

Image1(I).Width = ImageWidth

Image1(I).Height = ImageHeight

End If

End If

…

Вот собственно и все: мишени летают, а мы по ним стреляем.

Осталось за немногим:

 

 

ПРАВИЛА ИГРЫ

 

Для разнообразия я ввел три варианта завершения игры FlagGameType (именно поэтому не просто "Стрелок", а "Стрелок  50"):

0) после совершения игроком 50 выстрелов с момента начала игры (не важно были попадания или промахи);

1) после уничтожения 50 мишеней (целей).

2) по истечении 50 секунд с момента начала игры;

За выстрелами следят все объекты, имеющиеся на форме во время игры, для формы выглядит это так:

…

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)

…

'окончание игры

If FlagGameType = 0 And CurNShoots = MaxNShoots Then

MnuCancel1_Click

End If

…

End Sub

…

По аналогии не забываем про:

Image1_MouseDown – выстрел по мишени

Image4_MouseDown – выстрел по пулевому отверстию

Label1_MouseDown, Label2_MouseDown – выстрел по меткам с информацией

Из всех выстрелов попаданиями считаюся только Image1_MouseDown. Когда вы сгенерируете это событие 50 раз, игра закончится по сценарию 1.

…

'---------------------------------------------------

'окончание игры

If FlagGameType = 1 And CurNTargetsTerm = MaxNTargetsTerm … Then

MnuCancel1_Click

End If

End If

End If

'-----------------------------------------------------

…

За временем следит Timer3:

…

Private Sub Timer3_Timer()

If FGamePlay = True Then

CurNTimes = CurNTimes + 1

ITimes = ITimes + 1

…

If FlagGameType = 2 And CurNTimes = MaxNTimes Then

MnuCancel1_Click

Exit Sub

End If

…

Else ' игра окончена

Timer3.Enabled = False

End If

End Sub

…

Во всех трех случаях запускается некая MnuCancel1_Click, где и описывается завершение текущей игры:

…

Private Sub MnuCancel1_Click()

'-----------------------------------------------------

Dim PW As Integer 'звучание звукового файла

'-----------------------------------------------------

FGamePlay = False

Me.MousePointer = vbDefault

…

End Sub

…

Как только FGamePlay = False , все таймеры выключаются, а объекты не реагируют на щелчки мыши.

 

 

РЕЙТИНГ

 

Человек всегда сравнивает себя с другими и с самим собой.

Формулы для расчета очков могут быть какими угодно – это право автора. Немного подумав, я изобрел формулу рейтинга игрока, учитывающую, как мне кажется, все основные факторы игры:

…

Rating = (RatCurNTargetsTerm - RatCurNMiss) / CurNTimes * 1000

…

Итак, рейтинг равен разности между стоимостями попаданий и промахов поделенной на пройденное время (последующее умножение рейтинга на 1000 введено мною для получения "удобочитаемых" чисел Integer).

Для всех трех сценариев игры формула в целом справедлива.

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

…

RatCurNTargetsTerm = RatCurNTargetsTerm+1 * ScaleKoef 'стоимость попадания

…

аналогично с промахом:

…

RatCurNMiss = RatCurNMiss  + 1 *  ScaleKoef 'стоимость попадания

…

Непосредственно масштабный коэффициент я рассчитал после нескольких десятков "битв" на формах с разными размерами. Его определение помещено в событии Form_Resize:

…

ScaleKoef = ((Me.Width ^ 2 + Me.Height ^ 2) ^ 0.5 / (MinMeWidth ^ 2 + MinMeHeight ^ 2) ^ 0.5) ^ (1 / 4)

…

В каждом событии, которое приводит к изменению рейтинга, запускается обновление информации для игрока:

…

Private Sub UpdateLabel()

If CurNTimes > 0 Then Rating = (RatCurNTargetsTerm - RatCurNMiss) / CurNTimes * ScaleKoef * 1000

Label2(0).Caption = CurNShoots

Label2(1).Caption = CurNTargetsTerm

Label2(2).Caption = CurNMiss

Label2(3).Caption = CurNTimes

Label2(4).Caption = Rating

End Sub

…

По завершении каждой игры обновляется таблица лидеров:

…

Private Sub UpdateLiders()

…

Dim I As Integer

Dim OldLiderRat As Integer

Dim OldLiderName As String

'---------------------------------------------------

For I = 10 To 1 Step -1

'---------------------------------------------------

If Rating > LiderRat(I) Then 'если рез больше следующего

CurPosition = I

OldLiderName = LiderName(I)

OldLiderRat = LiderRat(I)

LiderName(I) = CurName

LiderRat(I) = Rating

'---------------------------------------------------

If I < 10 Then

LiderName(I + 1) = OldLiderName

LiderRat(I + 1) = OldLiderRat

End If

'---------------------------------------------------

Else 'если результат меньше следующего

Exit For

End If

'---------------------------------------------------

Next I

'---------------------------------------------------

For I = 1 To 10

Text1(I).Text = LiderName(I)

Label4(I).Caption = LiderRat(I)

'---------------------------------------------------

If I = CurPosition Then 'красим в цвета

Text1(I).BackColor = vbGreen

Label4(I).BackColor = vbGreen

Label3(I).BackColor = vbGreen

Text1(I).Locked = False

Else

Text1(I).BackColor = vbWhite

Label4(I).BackColor = vbWhite

Label3(I).BackColor = vbWhite

Text1(I).Locked = True

End If

'---------------------------------------------------

Next I

'---------------------------------------------------

End Sub

…

Таким образом мы рассмотрели основные функции программы в режиме "Игра".

 

СТАНДАРТНАЯ ПРОГРАММА

 

Функции в этом режиме FGamePlay = False присущи практически всем программам.

Обратите внимание на возможность паузы во время игры. Чтобы вызватьь ее при нажатии кнопки Esc я использовал кнопку Command1, пряча ее при загрузке формы за ее пределы. Также пауза срабатывает при сворачивании формы Form_Resize, через 10 мин бездействия Timer3.timer и по правой кнопке мыши:

…

If Button = 2 Then 'правая кнопка

If FGamePlay = False Then

Me.PopupMenu mnuGame 'всплывающее меню

Else

Command1_Click 'пауза

End If

End If

…

Два слова о работе с INI-файлом.

Конечно, приведенный в программе вариант далеко не лучший, но он простой понятный и работает, к тому же я стараюсь минимально привлекать в программу dll‑библиотеки, но это, как говорится, мои проблемы.

Основная идея: читаем праметры через OpenINI, если файла нет запускаем параметры по умолчанию DefaultINI, приводим их в действие в LoadINI, сохраняем параметры в SaveINI перед выходом из программы.

Я использую такой вариант:

…

Private Sub Form_Unload(Cancel As Integer)

…

SaveINI

…

For I = Forms.Count - 1 To 0 Step -1

Unload Forms(I) 'выгружаются все формы программы

Next I

End Sub

…

С недавних пор в свои программы я всегда включаю процедуру:

…

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)

Unload Me

End Sub

…

Она позволяет корректно завершить работу программы (удаляются временные файлы и т.п.) при жестком закрытии программы, например, при выходе из текущего сеанса Windows.

 

Мне очень нравится решение одного из программистов (стыдно - не помню имени) по контролю минимально допустимой ширины и высоты формы при изменении ее размеров:

…

Private Sub Form_Resize()

…

If Me.Width < MinMeWidth Then

Me.Enabled = False

Me.Width = MinMeWidth

Me.Enabled = True

End If

If Me.Height < MinMeHeight Then

Me.Enabled = False

Me.Height = MinMeHeight

Me.Enabled = True

End If

…

End Sub

…

 

Функция "Поверх остальных окон" стала стандартной для современных программ.

…

'-----------------------------------------------------

'поверх остальных окон

'-----------------------------------------------------

Private Sub MnuGameForvard_Click()

If MnuGameForvard.Checked = False Then

MnuGameForvard.Checked = True

Else

MnuGameForvard.Checked = False

End If

MeForvard

End Sub

Private Sub MeForvard()

If MnuGameForvard.Checked = True Then

SetWindowPos Me.hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE

Else

SetWindowPos Me.hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE

End If

End Sub

'-----------------------------------------------------

'поверх остальных окон

'-----------------------------------------------------

…

И не забудте в Module1.bas:

…

'------------------------------------------

'всегда впереди

'------------------------------------------

Public Const HWND_TOPMOST = -1

Public Const HWND_NOTOPMOST = -2

Public Const SWP_NOACTIVATE = &H10

Public Const SWP_SHOWWINDOW = &H40

Public Const SWP_NOMOVE = &H2

Public Const SWP_NOSIZE = &H1

Declare Sub SetWindowPos Lib "user32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long)

'------------------------------------------

'всегда впереди

'------------------------------------------

…

Практически у всех моих программ используется одна и та же форма "О программе"  Form7. Так сказать "на поток" поставил.

Не забудьте и для этого окна менять режим "Поверх остальных окон", а то оно появится сзади основной формы и тяжело Вам будет до нее добраться.

 

Мир VB огромен и необъятен, поэтому закругляюсь.

 

 

ЭПИЛОГ

 

Неделя, в течение которой я разрабатывал эту игру, пролетела как одно мгновение. Это не передаваемое состояние творчества, когда ты просыпаешься в 3 часа ночи и скорее запускаешь компьютер, чтобы воплотить в программу мелькнувшую в голове мысль.

Совершенствуйте себя и свои программы!

Именно для этого я написал эту статью.

 

 

С уважением,

                          Половый Александр

Декабрь 2002 г.

 
     

   
   
     
  VBNet рекомендует