Создаем шары Завершаем проект
Поместите на форму метку и назовите ее Счетчик_непойманных_шаров. В начале игры она будет показывать число 10. С каждым пойманным шаром это число будет убавляться на 1, пока не дойдет до 0, что и будет сигналом к окончанию игры.
Сейчас мы должны полностью запрограммировать шары. Но сначала нужно очень точно продумать поведение шара, определить до мелочей все, что он должен уметь делать. Выпишем все его умения:
А.
По приходе импульса от таймера шар должен:
- Прежде всего проверить, пойман он или нет, а если пойман, уменьшить счетчик непойманных шаров на 1 и выйти из игры.
- Проверить, не наткнулся ли он на бортик поля, и если да, то отскочить по законам отражения.
- Если не произошло ни того, ни другого, просто сдвинуться еще на один шаг в том же направлении, куда он двигался и раньше.
- Разбудить по очереди объекты Шар(1), Шар(2), … Шар(10) и заставить каждый выполнить свою работу, в частности вычислить свое положение (координаты) на форме и переместить свое изображение в вычисленное место.
- Если все шары пойманы, остановить игру.
- Установить в 10 переменную – счетчик непойманных шаров. Он нам понадобится, чтобы определить момент окончания игры – как только этот счетчик станет равен 0.
- Заставить каждый из шаров прыгнуть в точку старта шаров и подготовиться к старту.
- Начать игру.
В.
При нажатии кнопки Начинай сначала шар должен возвращаться в исходное положение.
Запрограммируем все действия, перечисленные в пункте А, в процедуре Действие класса clsШар. Запрограммируем все действия, перечисленные в пункте В, в процедуре Начальная_установка класса clsШар.
Перечислю действия, которые нужно добавить в процедуру таймера на третьей ступени:
Перечислю действия, которые нужно добавить в процедуру обработки нажатия на кнопку «Начинай сначала»:
Программа. Вот как выглядит наш проект окончательно. Модуль класса clsЛовец и стандартный модуль остались прежними. Приведу в окончательном виде все остальное, а именно: модуль формы и модуль класса clsШар.
Стандартный модуль: Остался неизменным.
Модуль формы:
Private Const Число_шаров As Integer = 10
Private Шар(Число_шаров) As clsШар 'Объявляем массив объектов Шар класса clsШар
Public pictШар(Число_шаров) As PictureBox 'Объявляем массив изображений шара
Public Число_непойманных_шаров As Integer
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Форма = Me
KeyPreview = True 'Чтобы форма реагировала на клавиатуру
Счетчик_времени.ReadOnly = True
Randomize() 'Шары должны разлетаться со случайной скоростью и в случайном направлении
'Порождаем объект Ловец и массив объектов-шаров:
Ловец = New clsЛовец
Dim i As Integer
For i = 1 To Число_шаров
Шар(i) = New clsШар
Next i
Начальная_установка()
Timer1.Enabled = False 'Чтобы шары не начали двигаться до нажатия на кнопку "Начинай сначала"
End Sub
Private Sub Начинай_сначала_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Начинай_сначала.Click
Начальная_установка()
Timer1.Enabled = True 'Шары стартуют, игра начинается
End Sub
Private Sub Начальная_установка()
Счетчик_времени.Text = 0 'Обнуляем счетчик времени
Число_непойманных_шаров = Число_шаров
Счетчик_непойманных_шаров.Text = Число_шаров
Счетчик_времени.Focus()
Ловец.Начальная_установка() 'Ловец встает в исходную позицию
Dim i As Integer
For i = 1 To Число_шаров
Шар(i).Начальная_установка() 'Все шары встают в исходную позицию и настраиваются на новую игру
Next i
End Sub
'Главная процедура игры, выполняется один раз на каждом импульсе таймера:
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Ловец.Действие() 'Действует ловец
Dim i As Integer
For i = 1 To Число_шаров
Шар(i).Действие() 'Действуют все шары
Next i
' Действует счетчик времени на форме, увеличивая свои показания на 1:
Счетчик_времени.Text = Счетчик_времени.Text + 1
If Число_непойманных_шаров = 0 Then Timer1.Enabled = False 'Конец игры
End Sub
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) _
Handles MyBase.KeyDown
Ловец.Реакция_на_клавиатуру(e)
End Sub
Модуль класса clsЛовец Остался прежним.
Модуль класса clsШар:
Public Class clsШар
Private Номер_шара As Integer
Private Shared Число_созданных_шаров As Integer = 0
Private Const Размер_шара As Double = 12
Private x, y As Double 'Координаты шара
Private dx, dy As Double 'Шаг шара по горизонтали и вертикали между двумя импульсами таймера
Public Sub New()
Число_созданных_шаров = Число_созданных_шаров + 1
Номер_шара = Число_созданных_шаров
'Порождаем элемент управления - изображение шара:
Форма.pictШар(Номер_шара) = New PictureBox
'Настраиваем размеры изображения шара:
Форма.pictШар(Номер_шара).Width = Размер_шара
Форма.pictШар(Номер_шара).Height = Размер_шара
Форма.pictШар(Номер_шара).SizeMode = PictureBoxSizeMode.StretchImage
'Чтобы белый фон PictureBox не был заметен на белом Поле:
Форма.pictШар(Номер_шара).BackColor = Color.White
Форма.pictШар(Номер_шара).Image = Image.FromFile("MOON06.ICO")
Форма.Controls.Add(Форма.pictШар(Номер_шара)) 'Добавляем новый шарик в коллекцию формы
Форма.pictШар(Номер_шара).BringToFront() 'Чтобы Поле не заслоняло шарик
End Sub
Public Sub Начальная_установка()
Const Макс_шаг As Double = 1.8 'Максимально возможное значение шага шара
'Ставим шар на исходную позицию:
x = Форма.Поле.Left + Форма.Поле.Width * 3 / 4
y = Форма.Поле.Top + Форма.Поле.Height / 2
Ставим_изображение_шара_на_место()
'Вычисление шага:
dx = Макс_шаг * (1 - 2 * Rnd()) 'Шаг по горизонтали случаен и не превосходит Макс_шаг
dy = Макс_шаг * (1 - 2 * Rnd()) 'Шаг по вертикали случаен и не превосходит Макс_шаг
End Sub
Private Sub Ставим_изображение_шара_на_место()
Форма.pictШар(Номер_шара).Left = x
Форма.pictШар(Номер_шара).Top = y
End Sub
Public Sub Действие() ' Главная процедура шара, выполняется один раз на каждом импульсе таймера
If Поймали() Then Выход_шара_из_игры() 'Сначала шар определяет, не поймали ли его,
Отскок() 'затем, если бортик рядом, настраивается отскочить от него
Шаг() 'и, наконец, делает шаг
Ставим_изображение_шара_на_место()
End Sub
Private Sub Шаг()
x = x + dx
y = y + dy
End Sub
Private Sub Отскок() 'Реакция на бортик
If Шар_у_горизонтального_бортика() Then dy = -dy 'Отскок от пола или потолка
If Шар_у_вертикального_бортика() Then dx = -dx 'Отскок от стен
End Sub
Private Function Шар_у_горизонтального_бортика() As Boolean
If y < Форма.Поле.Top Or y + Размер_шара > Форма.Поле.Top + Форма.Поле.Height Then
Return True
Else
Return False
End If
End Function
Private Function Шар_у_вертикального_бортика() As Boolean
If x < Форма.Поле.Left Or x + Размер_шара > Форма.Поле.Left + Форма.Поле.Width Then
Return True
Else
Return False
End If
End Function
Private Function Поймали() As Boolean
Const Дальность As Double = 10 ' Это расстояние, на котором ловец достает шар
'ЕСЛИ расстояние по горизонтали между центрами шара и ловца меньше Дальности
'И если расстояние по вертикали между центрами шара и ловца меньше Дальности, ТО поймали:
If Math.Abs(x - Ловец.Xл - ((Размер_ловца - Размер_шара) / 2)) < Дальность _
And Math.Abs(y - Ловец.Yл - ((Размер_ловца - Размер_шара) / 2)) < Дальность Then
Return True
Else
Return False
End If
End Function
Private Sub Выход_шара_из_игры()
x = -10000 : y = -10000 'Убрать шар подальше с глаз долой
dx = 0 : dy = 0 ' и чтоб не двигался
Форма.Число_непойманных_шаров = Форма.Число_непойманных_шаров - 1
Форма.Счетчик_непойманных_шаров.Text = Форма.Число_непойманных_шаров
End Sub
End Class
Запустите проект. Проверьте, правильно ли он работает. Поиграйте значениями шагов ловца и шара, их размерами, числом шаров, интервалом таймера и другими величинами, подберите наиболее удобные для себя.
Пояснения. Обратите внимание, что в обоих классах много одноименных переменных и процедур. Как я уже говорил чуть выше, никакой путаницы здесь произойти не может. Иметь одинаковые имена для компонентов одинакового смысла удобно и правильно.
Код формы. Я уже говорил, что для изображений шаров нужно создать программным путем массив элементов управления PictureBox. Созданием займемся в классе clsШар, а в модуле формы я массив объявил:
Public pictШар(Число_шаров) As PictureBox 'Объявляем массив изображений шара
Стартом и окончанием процесса игры управляют помещенные в подходящие места операторы:
Timer1.Enabled = True
который запускает таймер и значит игру, и
Timer1.Enabled = False
который останавливает таймер и значит игру.
Остальные дополнения, сделанные в коде формы, очевидны. Разберитесь в них самостоятельно. Я думаю, что комментариев достаточно.
Класс шара. Поговорим подробнее о классе шара. Если вы хорошо разобрались в классе ловца, то в нем вам будет разбираться гораздо легче, так как многие переменные и процедуры этих двух классов похожи.
Сначала о главном. Совершенно аналогично модулю ловца здесь имеется два метода – Начальная_установка и Действие.
Метод Действие определяет, что должен делать шар в каждое мгновение своего полета. Он должен знать, поймали его или нет, и пора ли отскакивать от бортика. Этому и посвящены первые две из четырех строк процедуры Действие. Третья строка – Шаг – вычисляет в соответствии со значениями dx и dy координаты x и y, а четвертая строка ставит в вычисленное место изображение шара. Все, больше ничего во время движения шара делать не нужно.
Теперь посмотрим, что происходит при нажатии на кнопку «Начинай сначала». Выполняется процедура формы Начинай_сначала_Click и после пары прыжков по процедурам VB передает управление процедуре Начальная_установка каждого шара. Здесь трудности у вас может вызвать вычисление dx и dy. Поскольку значение Rnd есть случайное число в диапазоне от 0 до 1, то легко видеть, что как dx, так и dy будут случайными числами в диапазоне от -1.8 до 1.8. Этого достаточно, чтобы шар полетел с небольшой случайной скоростью в случайном направлении. Если скорость шаров вам кажется маловатой, увеличьте Макс_шаг.
Вычисление координат x и y исходной позиции шара совершенно аналогично вычислению таких же координат ловца.
Функции Шар_у_горизонтального_бортика и Шар_у_вертикального_бортика совершенно аналогичны «половинкам» функции ловца Ловец_у_бортика.
Теперь насчет процедуры Отскок. Возможно, тем, кто не очень силен в математике и физике, покажется удивительным, что для отскока от горизонтальной преграды достаточно выполнить единственный оператор dy = -dy, то есть поменять вертикальную составляющую шага на ее противоположное значение. «Но это действительно так!» Аналогично, достаточно выполнить оператор dx = -dx для отскока от вертикальной преграды. Чтобы лучше понять этот факт, запустите проект при Макс_шаг = 30 и Число_шаров = 1 в режиме прерывания, установив точки прерывания на процедурах Отскок и Шаг. При этом проследите внимательно за dx и dy, x и y.
Поговорим о конструкторе. Чтобы объект Шар передвигал по форме именно свое изображение, а не чужое, он должен знать свой номер в массиве шаров. Идея сообщения ему этого номера та же, что и у номера садового участка в 22.6. Организуется статическая переменная Число_созданных_шаров, увеличивающая свое значение на 1 с каждым созданным шаром, и переменной Номер_шара присваивается ее значение.
Кроме этого конструктор занимается тем, что создает для объекта Шар элемент управления PictureBox с изображением шара. Механика такого создания разобрана в 15.8. Здесь она аналогична. Отличие в том, что здесь мне не пришлось организовывать цикл, так как каждый объект Шар создает себе только одно изображение. Строка
Форма.pictШар(Номер_шара).SizeMode = PictureBoxSizeMode.StretchImage
понадобилась для того, чтобы картинка шара на элементе управления PictureBox подстраивала свой размер под указанный размер элемента управления PictureBox.
Об инкапсуляции. Обратите внимание, что шар у нас получился на загляденье – весь «бронированный». Вся механика – в себе. Своим изображением на форме он не только управляет, но даже его порождает. Все переменные – Private. Методов всего два – Начальная_установка и Действие. В общем, безопасность соблюдена.
С формой дела обстоят тоже неплохо. Снаружи видны только одна переменная и массив изображений шара. Но здесь уже некуда было деваться. Методов – вообще ни одного. Правда, элементы управления формы по умолчанию видны снаружи, но их тоже можно сделать недоступными.
Испытания. Задайте побольше шаров, скажем, 100 или 1000. Запустите проект. Почему так медленно движутся шары и редко сменяются цифры в счетчике времени? Причина – в «холостых» импульсах таймера (см. 13.2).
Теперь, когда вы разобрались в проекте, снова поиграйте значениями шагов ловца и шара, их размерами, числом шаров, интервалом таймера и другими величинами, каждый раз пытаясь объяснить изменения в поведении проекта.