ПОНЯТНО О Visual Basic NET (том 3)

       

Движем ловца – вторая ступень проекта


Сейчас мы должны запрограммировать ловца полностью. Но сначала нужно очень точно продумать его поведение, определить до мелочей все, что он должен уметь делать. Выпишем все его умения:

А.

По приходе импульса от таймера он должен:

  • Проверить, не наткнулся ли он на бортик поля, и если да, то … Что? Мы не придумали еще. Надо придумать. Пуст он должен отскочить в исходное положение и остановиться.
  • В противном случае сдвинуться на некоторый шаг вверх, вниз, влево или вправо, подчиняясь соответствующим клавишам клавиатуры, или стоять на месте (клавиша Ctrl).
  • В.

    При нажатии кнопки Начинай сначала он должен возвращаться в исходное положение.

    Все. Вы спросите – а почему так мало, а как же умение ловить шары, ради которого ловец и создан? Я отвечу: В нашем случае проще запрограммировать «исчезалки», чем «ловилки». Не поняли? – Поясню. Наткнувшись на шар, ловец у нас не будет предпринимать никаких действий по его «поимке». Наоборот, шар, наткнувшись на ловца, потрудится добровольно исчезнуть. Со стороны – никакой разницы.

    Запрограммируем все действия, перечисленные в пункте А, в процедуре класса clsЛовец, которую назовем Действие. Запрограммируем все действия, перечисленные в пункте В, в процедуре Начальная_установка класса clsЛовец, которую мы уже частично написали.

    Таймер. Свои действия ловец должен производить по импульсам таймера. Но у класса нет ни одного элемента управления, значит и таймера тоже нет. Таймером нашего проекта будет таймер, принадлежащий форме. Поместите его на форму. Задайте ему интервал = 10. Щелкните по нему дважды – в окне кода формы появится заготовка процедуры. Напомню, что эта процедура выполняется на каждом импульсе таймера. Значит это и будет главная процедура нашего проекта, задающая ритм всем объектам и элементам управления.

    Перечислю действия, которые должна выполнять эта процедура, пока шаров в проекте нет:

    • Разбудить ловца и заставить его выполнить свою процедуру Действие, в частности вычислить свое положение (координаты) на форме и переместить свое изображение в вычисленное место.

    • Увеличить на 1 счетчик времени (импульсов таймера) на форме.


    • Перечислю действия, которые должны выполняться при нажатии на кнопку «Начинай сначала»:

      • Установить в 0 счетчик времени.




      • Заставить ловца выполнить свою процедуру Начальная_установка, то есть прыгнуть в точку старта ловца.


      • Программа. Вот как выглядит наш проект на второй ступени. На третьей ступени добавится класс шара, к стандартному модулю и коду формы добавятся строки, касающиеся шаров. А модуль clsЛовец я привожу целиком, в окончательном варианте.

        Стандартный модуль:  Остался неизменным.

        Модуль формы:

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

                Форма = Me

                KeyPreview = True                                        'Чтобы форма реагировала на клавиатуру

                Счетчик_времени.ReadOnly = True            'Чтобы нельзя было вручную менять показания счетчика

                Ловец = New clsЛовец                                  'Создаем объект Ловец класса clsЛовец

                Начальная_установка()

        End Sub

        Private Sub Начальная_установка()

                Счетчик_времени.Text = 0                    'Обнуляем счетчик времени

                Счетчик_времени.Focus()                      'Чтобы фокус ушел с кнопки

                Ловец.Начальная_установка()               'Ловец встает в исходную позицию

        End Sub

        Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick

                Ловец.Действие()

                Счетчик_времени.Text = Счетчик_времени.Text + 1    'Счетчик времени на форме увеличивается на 1

        End Sub

        Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)  Handles MyBase.KeyDown

                Ловец.Реакция_на_клавиатуру(e)

        End Sub

        Private Sub Начинай_сначала_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)  _

        Handles Начинай_сначала.Click

                Начальная_установка()

        End Sub

        Модуль класса clsЛовец (останется неизменным):

        Public Class clsЛовец



            Private x As Double         'Горизонтальная координата ловца на форме

            'Свойство - горизонтальная координата ловца на форме

            Public ReadOnly Property Xл() As Double

                Get

                    Return x

                End Get

            End Property

            Private y As Double        'Вертикальная координата ловца на форме

            'Свойство - вертикальная координата ловца на форме

            Public ReadOnly Property Yл() As Double

                Get

                    Return y

                End Get

            End Property

            Private Enum типРуль            ' Направление движения ловца или состояние неподвижности

                вверх

                влево

                вниз

                вправо

                стоп

            End Enum

            Private Руль As типРуль

            Public Sub New()

                Форма.pictЛовец.Width = Размер_ловца

                Форма.pictЛовец.Height = Размер_ловца

            End Sub

            Public Sub Начальная_установка()                         'Ловец встает в исходную позицию и останавливается

                Руль = типРуль.стоп

                x = Форма.Поле.Left + Форма.Поле.Width * 1 / 4

                y = Форма.Поле.Top + Форма.Поле.Height / 2

                Ставим_изображение_ловца_на_место()

            End Sub

            Private Sub Ставим_изображение_ловца_на_место()

                Форма.pictЛовец.Left = x

                Форма.pictЛовец.Top = y

            End Sub

            Public Sub Действие()       'Главная процедура ловца, выполняется один раз на каждом импульсе таймера

                'Если ловец врезается в бортик, то отскакивает в исходное положение и останавливается:

                If Ловец_у_бортика() Then Начальная_установка()

                Выбираем_куда_ехать_и_делаем_шаг()                     'Ловец слушается клавиатуру

            End Sub

            Private Function Ловец_у_бортика() As Boolean

                'ЕСЛИ ловец находится у потолка ИЛИ у пола, ИЛИ у левой стены ИЛИ у правой, ТО:

                If   y < Форма.Поле.Top   Or   y + Размер_ловца > Форма.Поле.Top + Форма.Поле.Height  _

        Or   x < Форма.Поле.Left   Or x + Размер_ловца > Форма.Поле.Left + Форма.Поле.Width   Then



                    Return True

                Else

                    Return False

                End If

            End Function

            Private Sub Выбираем_куда_ехать_и_делаем_шаг()

                Dim dx As Double = 1       'Шаг ловца по горизонтали и вертикали между двумя импульсами таймера

                Dim dy As Double = 1

                Select Case Руль

                    Case типРуль.вверх             : y = y - dy

                    Case типРуль.вниз        : y = y + dy

                    Case типРуль.влево            : x = x - dx

                    Case типРуль.вправо    : x = x + dx

                    Case типРуль.стоп                      'Поскольку никуда идти не надо, постольку ничего не делаем

                End Select

                Ставим_изображение_ловца_на_место()

            End Sub

            Public Sub Реакция_на_клавиатуру(ByVal e As System.Windows.Forms.KeyEventArgs)

                Select Case e.KeyCode

                    Case Keys.Left                     : Руль = типРуль.влево

                    Case Keys.Right                         : Руль = типРуль.вправо

                    Case Keys.Up                       : Руль = типРуль.вверх

                    Case Keys.Down                         : Руль = типРуль.вниз

                    Case Keys.ControlKey          : Руль = типРуль.стоп

                End Select

            End Sub

        End Class

        Запустите проект. Проверьте, правильно ли движется ловец. Для его движения не нужно держать палец на клавише, достаточно щелкнуть.

        А теперь пояснения.

        Начну с клавиатуры. Она почти полностью копирует работу клавиатуры в игре «Гонки» (тех, кто подзабыл работу с клавиатурой, отсылаю туда – 14.4.4). Для того, чтобы проект мог реагировать на клавиатуру, в коде формы появились три строки:

                KeyPreview = True                                        'Чтобы форма реагировала на клавиатуру

                Счетчик_времени.ReadOnly = True            'Чтобы нельзя было вручную менять показания счетчика

                Счетчик_времени.Focus()                      'Чтобы фокус ушел с кнопки

        Причины необходимости таких строк подробно объяснены в 14.4.4.

        Отличие от Гонок состоит в том, что теперь у нас появился объект и в соответствии с принципом инкапсуляции обработку нажатия на клавишу клавиатуры я перенес в него. Раньше вся обработка происходила бы в процедуре Form1_KeyDown формы, а теперь я там оставил только обращение к процедуре ловца Реакция_на_клавиатуру. Чтобы не плодить ловцу лишних полей, я снабдил эту процедуру параметром e, который несет в себе всю информацию про нажатие клавиши.



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

        Процедуры таймера Timer1_Tick и кнопки Начинай_сначала_Click предельно кратки и полностью соответствуют описанию их работы, приведенной чуть выше в этом подразделе.

        С пояснениями кода формы покончено. Перейдем к классу clsЛовец. Здесь добавлений много. В начальную установку ловца добавилась строка

                Руль = типРуль.стоп

        Она нужна потому, что начальная установка может застигнуть ловца в любой момент, чаще всего тогда, когда он находится в движении. Так вот, это нужно, чтобы он успокоился.

        Главная процедура ловца Действие в нашем случае сводится к выполнению двух дел:

        Проверке, не врезался ли ловец в бортик, а если врезался – к возврату его в исходное положение и остановке. Проверку осуществляет булевская функция Ловец_у_бортика, а возврат – уже рассмотренная нами процедура Начальная_установка.

        Опрашиванию переменной Руль и перемещению изображения ловца в соответствующем направлении. Всем этим занимается процедура Выбираем_куда_ехать_и_делаем_шаг. Она работает совершенно аналогично своей тезке из Гонок (см. 14.4.4).

        Получается, что нам осталось разобрать только булевскую функцию Ловец_у_бортика. Эта функция примет значение True, если ловец в своих путешествиях по полю врежется в бортик. Факт столкновения определяется сравнением координат ловца и координат каждого из 4 бортиков. В операторе If   эти 4 сравнения отделены друг от друга знаком логической функции Or.

        Координаты и шаг ловца и шаров я сделал дробного типа, а не целого. Причина в том, что скорости ловца и шаров вы можете захотеть сделать маленькими. Тогда может понадобиться шаг меньший 1.

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



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

        И вот импульс грянул. Заработала процедура Timer1_Tick и первым делом запустила процедуру Ловец.Действие. Давайте пока не будем в нее заглядывать, вообразим, что там ничего существенного не произошло, и пойдем дальше. Следующая строка увеличивает счетчик времени. Вместо 0 в счетчике на форме появляется 1. На этом процедура Timer1_Tick заканчивает свою работу. Все замирает до тех пор, пока через 10 тысячных долей секунды не придет следующий импульс от таймера.

        Предположим, вы за это время еще не успели прикоснуться к клавиатуре. Вот пришел новый импульс. Заработала процедура Timer1_Tick и запустила процедуру Ловец.Действие. Рассмотрим, как она работает. Ее тело состоит из двух строк, вызывающих процедуры и функции с интуитивно ясными именами. Первой вызывается функция Ловец_у_бортика. Очевидно, что поскольку ловец пока далеко от бортиков, то эта функция принимает значение False.

        Далее выполняется процедура Выбираем_куда_ехать_и_делаем_шаг. Руль у нас после начальной установки находится в положении стоп и ничто его оттуда не вывело, значит оператор Select Case не меняет ни x ни y. Следовательно процедура Ставим_изображение_ловца_на_место не сдвигает изображение ловца из начального положения.

        На этом вторая строка процедуры Ловец.Действие завершается, а с ней и вся процедура. VB возвращается в процедуру Timer1_Tick, которая увеличивает счетчик времени еще на 1.

        Ждем следующего импульса. Пусть до момента его прихода мы успели нажать на клавиатуре стрелку вправо, желая направить ловца направо. Немедленно сработала процедура Form1_KeyDown в модуле формы. Она вызвала процедуру ловца Реакция_на_клавиатуру, которая присвоила переменной Руль значение вправо. Импульса все нет.

         Вот пришел импульс. Опять процедура Timer1_Tick направляет нас в процедуру Ловец.Действие, та – в процедуру Выбираем_куда_ехать_и_делаем_шаг. Поскольку руль повернут направо, вычисляется новое значение x, которое на dx больше предыдущего. Согласно новому значению x процедура Ставим_изображение_ловца_на_место  смещает изображение ловца чуть вправо.



        Ждем следующего импульса и так далее. Вот и вся механика ловца.

         Теперь поговорим об инкапсуляции. В проекте я старался максимально придерживаться принципа инкапсуляции, все что можно я делал Private. Посмотрим по порядку.

        Объекты Форма, Ловец и константу Размер_ловца я объявил в стандартном модуле. Потому что все они будут нужны не в одном, а в нескольких модулях. Естественно, я сделал их Public.

        В форме все процедуры объявлены Private. Потому что в нашем проекте форма – это тот пульт, из которого осуществляется управление проектом. Ее процедуры предназначены, чтобы управлять, а не управляться. Чего не скажешь об элементах управления, которые видны снаружи формы и управляются из классов.

        Теперь поглядите повнимательнее в код ловца. Координаты ловца на форме x и y я сделал Private, но поскольку знаю, что они понадобятся шару (который должен знать их величину, чтобы вовремя исчезнуть при столкновении), я создал два соответствующих свойства только для чтения: Xл и Yл.

        Наводит на размышление тот факт, что методами класса стали именно те процедуры, которые должны вызываться из формы в ответ на какие-то события формы. Их просто нельзя было сделать Private. Это Начальная_установка (реагирует на загрузку формы и на кнопку Начинай сначала),  Действие (реагирует на таймер) и Реакция_на_клавиатуру (реагирует на клавиатуру). О конструкторе я не говорю. Если его сделать Private, то нельзя будет создать объект.

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

        Обратите внимание, что разные модули могут использовать одноименные компоненты. Например, процедура Начальная_установка. Проблемы в этом нет, так как если процедура задана, как Private, то из других модулей она просто не видна, а если даже Public, то перед своим именем она будет требовать имени хозяина.

        Вот и все о второй ступени проекта.


        Содержание раздела