InfoCity
InfoCity - виртуальный город компьютерной документации
Реклама на сайте







Размещение сквозной ссылки

 

Часть третья: Творим!


Да уж давно бы пора! - подумают некоторые. Ну ладно, не злитесь, в этой главе мы разработаем и претворим в жизнь самую настоящую DirectX'овую игруху!

Не будем очень оригинальны и сделаем PingPong; ну знаете, такая штука, где ездят две ракетки и пинают мячик стараясь чтобы противник по нему не попал. Так вот, мы постараемся сделать полностью (ну или почти) работающий ПингПонг для одного игрока против компьютера. Как подключить второго игрока я надеюсь вам будет абсолютно понятно - это останется в качестве упражнения. Кроме того, для упрощения задачи я пока не буду рассматривать вывод текстовых сообщений - это в раздел эффектов в следующую часть. А для усложнения задачи - выработаем такой алгоритм, в котором поле у нас будет представлено в виде матрицы в которой есть элементы 0 - свободно и 1 - блок. От блоков мячик соответственно отскакивает. Это сделано для того, чтобы мяч мог передвигаться независимо от расположения блоков, вам надо будет только поменять ссответствующее значение в матрице. Это уже будет нечто действительно похожее на движок и вы можете делать различные игровые поля.

Итак, приступаем

Что сначала?


Ну с самого начала, еще даже перед тем как начать рисовать графику, надо решить один такой маленький вопросик: а какой у нас будет ПингПонг: вертикальный или горизонтальный? Я выбрал вертикальный; не спрашивайте почему, вы всегда сможете сделать другой.

Разрешение экрана будет 640x480x16, а размер матрицы - 12*16, то есть одна клетка - 40*40 пикселей.

Ну вот, а теперь можно рисовать графику.

Процесс этот утомительный только по одной причине: кропотливость. Сначала надо выбрать размерность каждого объекта: бита будет размером 80*20, мяч - 20*20, а блок - 40*40, чтобы занимал одну клетку. Размеры мяча и биты абсолютно произвольные, но если вы захотите взять другие, учтите, что придется исправлять все нижеследующие математические расчеты.

Итак, зайдите куда-нибудь типа FreeHand8 и сотворите шедевр в стиле Малевича! Каждый объект рисуйте по отдельности и сохраняйте в 24-разрядном файле BMP. Не забывайте, что потребуется две разных биты. Нарисовали - вперед в Painbrush! Выбирайте пункт меню "Вставить из файла" и скомпонуйте все спрайты на одном рисунке. Затем подгоните размер рисунка под границы спрайтов (Пожалуйста, не делайте рисунок больше, чем главная поверхность. Это поддерживают не все видеокарты). Да! И не забудьте, что нам потребуется "прозрачный" цвет. Я взял для этого черный (RGB(0,0,0)), поэтому контуры на рисунках спрайтов у меня вроде как черные, а на самом деле нет (RGB(12,12,12)).

Я нарисовал вот такую картину и далее подразумевается, что координаты расположения спрайтов будут как у меня.

Примечание: как всегда я где-то напортачил, поэтому размерности спрайтов у меня с погрешностями +/- 1 пиксел.

 

Первые аккорды кода


Давным давно, еще в школе, нам преподавали бейсик (простой!). Поэтому я еще некоторое время то и дело боролся с искушением свалить все в одну кучу. Это не только непрактично, но и просто как-то неэстетично и плохо выглядит, поэтому давайте обсудим структуру программы.

Заместо того, чтобы вводить кучу переменных, для описания объектов используем типы BallInf и BetInf, описывающие соответственно мячик и биту. Создадим экземпляр мячика Ball и два экземпляра биты Bet1 и Bet2 "As BetInf".

Модуль mdlDirectDraw7 у нас уже есть (ведь есть, правда?!, поэтому помимо формы frmBall код будет размещаться в модуле mdlBall, в котором будут функции и процедуры, выполняющие основные расчеты и размещение объектов на экране. В моих принципах освобождать модули кода форм от лишних процедур, не относящихся непосредственно к обработке их событий поэтому я жестоко и беспощадно БУДУ убирать все служебные процедуры в другие модули. Можете теперь в меня чем-нибудь кинуть.

Программу будем писать постепенно, создавая сначала костяк, а потом наращивая все более и более завороченные фичи (не баги!).

Для начала, создаем новый проект, добавляем в него нужные библиотеки (как всегда - dx7vb и win32). Дадим имя проекту - VBPong, переименуем главную форму в frmBall, добавим все вышеперечисленные модули и сохраним все хозяйство в одну новую папку. В нее же надо перекопировать графический файл, который мы предусмотрительно приготовили заранее.

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

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

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
Case vbKeyEscape 'Выйти из программы
mdlDirectX.Running=False
End Select
End Sub

'При выходе надо уничтожать все объекты DirectX
Private Sub Form_Unload(Cancel As Integer)
mdlDirectDraw7.DestroyDD
End Sub

Private Sub Form_Load()
mdlDirectDraw7.Running = True
'Инициализация DirectDraw
mdlDirectDraw7.CreateDDFullscreen Me.hWnd, 640, 480, 16
Set ddsPic = mdlDirectDraw7.CreateDDSFromFile(App.Path & "\tball.bmp") 'Создаем буфер с нашей графикой
'Прозрачный цвет - черный, буфер объявляется чуть позже

'Следующие строки назовем "Регион 1". Сюда будут помещаться начальные определения игры
'------- Пока пусто --------
'------- Конец региона 1 --------

'Теперь, создаем главный цикл прорисовки экрана
While mdlDirectDraw7.Running = True
DoEvents 'Будем жалостливы к системе
Call mdlDirectDraw7.ClearBuffer(ddsBack) 'Чистим полотно

' Это место назовем "Регион 2". Здесь надо разместить последовательность рисования всех
' объектов на очередном витке цикла
'------- Пока пусто --------
' ------- Конец региона 2 ---------

'Теперь, завершающие действия витка
Call ddsPrimary.Flip(ddsBack, DDFLIP_WAIT) ' Переводим задний буфер на передний
Wend
Unload Me
End Sub

И на этом код формы пока заканчивается. Внимательно присмотрясь к тому что будет делать программа, вы увидите, что после инициализации, она войдет в цикл, в котором будет находиться до самого своего конца. Идея такова, чтобы в этом цикле заставить программу отображать все нужные нам объекты каждый раз на заданных местах. Места же эти каждый раз перевычисляются на очередном витке цикла. Таким образом, вычисления сводятся к определению новых координат объекта, заданию структуры RECT, о которой говорилось в прошлой главе, и применению метода BltFast. Все эти действия помещаются в "Регионе 2" и будут занимать все дальнейшее время нашей работы в этой части. Теперь я думаю вам стало ясно, почему я был категорически против размещения всей программы в модуле кода формы. Почти все действия, относящиеся к DirectX мы уже сделали и теперь приступаем к математике.

 

Добавим функциональности


Переходим в модуль mdlBall. Сперва, как я уже говорил, определим структуры BallInf и BetInf, а также создадим нужные переменные.

Типы выглядят так:

Type BetInf
X As Integer 'координата на оси X
SpeedX As Integer 'Приращение следующего шага на оси X
End Type

Type BallInf
X As Integer 'Координаты на осях
Y As Integer
SpeedX As Integer 'Приращения на осях
SpeedY As Integer
End Type

'Две биты и мяч
Public Bet1 As BetInf
Private Bet2 As BetInf
Private Ball As BallInf

'Игровое поле
Private Map(11, 15)

'Буфер для спрайтов
Public ddsPic As DirectDrawSurface7

Инициализация закончена. Текущее положение ракетки зависит от координаты X. Y у обеих ракеток постоянная - у нижней - 460, у верхней - 0. Чтобы посчитать новое положение ракетки исходя из старого, надо прибавить к координате X приращение SpeedX. Если приращение отрицательное, то бита двигается влево и наоборот. Bet1 управляется игроком, поэтому приращение задается нажатием клавиши "влево" или "вправо". Если клавиша отпускается, то приращение равно нулю. Приращение Bet2 вычисляется компьютером.

Положение мяча задается координатами X и Y. У него также существуют два осевых приращения.

Карта размера 16*12 (16 - по-горизонтали, 12 - по-вертикали). Учтите, что массив начинается с нуля и сначала идут строки.

Давайте напишем функцию, которая сбрасывает положения мяча и бит до начальных.

Public Sub Reset()
Bet1.X = 320
Bet1.SpeedX = 0

Bet2.X = 320
Bet2.SpeedX = 0

Ball.X = 320
Ball.Y = 240
Ball.SpeedY = -5
Ball.SpeedX = GetRandomSpeedX
End Sub

Здесь используется дополнительная функция GetRandomSpeedX. Она возвращает случайное приращение по оси X, колеблющееся от -5 до 5. Вот как она описывается.

Private Function GetRandomSpeedX() As Integer
Randomize
GetRandomSpeedX = Int(Rnd * 10) - 5
End Function

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

Public Sub LoadMap(ByVal Level As Integer)
Dim i As Integer
Select Case Level
Case 1 'Всего лишь две вертикальные стенки по бокам
For i = 0 To 11
Map(i, 0) = 1
Map(i, 15) = 1
Next i
End Select
End Sub

Мы всего-лишь задали матрицу. Нам еще понадобится процедура, которая будет рисовать карту каждый виток цикла по этой матрице.

Public Sub DrawMap()
Dim i As Integer, j As Integer
For i = 0 To 11
For j = 0 To 15
If Map(i, j) = 1 Then
' Если встретили в матрице значение 1, переводим на это место на экране красный блок
rc.Top = 0
rc.Left = 102
rc.Right = rc.Left + 40
rc.Bottom = 40
Call ddsBack.BltFast(40 * j, 40 * i, ddsPic, rc, DDBLTFAST_WAIT Or DDBLTFAST_SRCCOLORKEY)
End If
Next j
Next i
End Sub

Теперь, процедура, которая будет рисовать биты. В параметре ей указывается какая бита будет рисоваться, 1-нижняя, 2-верхняя

Public Sub DrawBet(ByVal Num As Integer)
Dim BY As Integer, BX As Integer

If Num = 1 Then
rc.Left = 0
Bet1.X = Bet1.X + Bet1.SpeedX 'Новая координата X
If Bet1.X + 80 > 600 Then Bet1.X = 600 - 80 'Проверяем на боковые стенки, которые д. б. всегда
If Bet1.X < 40 Then Bet1.X = 40
BX = Bet1.X
BY = 460
Else
' Поставьте здесь ремарку пока не напишете функцию DoBetAI
DoBetAI 'Вычисляем приращение по X биты компьютера
rc.Left = 142
Bet2.X = Bet2.X + Bet2.SpeedX
If Bet2.X + 80 > 600 Then Bet2.X = 600 - 80
If Bet2.X < 40 Then Bet2.X = 40
BX = Bet2.X
BY = 0
End If

'Заполняем недостающие элементы структуры RECT и рисуем биту на заднем буфере
rc.Top = 0
rc.Bottom = rc.Top + 20
rc.Right = rc.Left + 80
Call ddsBack.BltFast(BX, BY, ddsPic, rc, DDBLTFAST_WAIT Or DDBLTFAST_SRCCOLORKEY)
End Sub

Отлично, теперь "приделаем" нашей нижней бите управление. Доведите процедуру обработки события формы frmBall_KeyDown до следующего состояния, а затем допишите обработку события frmBall_KeyUp:

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
Case vbKeyEscape 'Выход
mdlDirectDraw7.Running = False
Case vbKeyLeft 'Управление первого игрока
Bet1.SpeedX = -5
Case vbKeyRight
Bet1.SpeedX = 5
End Select
End Sub

Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
Bet1.SpeedX = 0
End Sub

Добавим в регион 1 следующее:

mdlBall.Reset
mdlBall.LoadMap 1

А в регион 2 это:

mdlBall.DrawMap
mdlBall.DrawBet 1

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

Свободный полет


Осталось самое сложное - запрограммировать мячик, чтобы он летал и отскакивал от тех мест, которые в массиве помечены единичкой. Я сделал это вот каким нехитрым образом. Итак, представьте себе двухмерный мячик. В процессе полета он может находиться целиком в одном секторе (каждый сектор - 40*40, если помните, а мяч - 20*20), в двух секторах одновременно, в трех не может, а вот в четырех - запросто. Теперь представьте, что у мячика есть крайние точки, так называемые - "контрольные точки". Можно сказать, что если одна из этих точек находится в каком-то секторе, то некая часть мячика тоже находится в этом секторе. Таким образом, если контрольная точка находится в секторе, который помечен 0, то все нормально, однако, если контрольная точка попадает в сектор с 1, то он уже занят блоком и мячик должен отскочить. В какую сторону? Посмотрите на такой рисунок:

Вы видите на нем четыре контрольных точки, отмеченных красным, а также их координаты в системе координат мяча. Если мяч попал в занятый сектор точкой номер 1, то очевидно, что он стукнулся о преграду, находящуюся сверху него и должен отсочить вниз. Это легко задать формулой SpeedY=-SpeedY. Если "просигналила" точка номер два, то мяч отскакивает влево и т. д. При этом, мяч нужно "отпозиционировать" то-есть поставить просигналившую точку ровно на границу с занятым сектором, чтобы мяч не "залипал" там.

Конечно, для правильного отображения четырех точек явно мало, мяч может уже быть в занятом секторе и ни одна точка не просигналит, поэтому в идеале надо рассматривать каждую точку окружности мяча, лезть в дебри тригонометрии... бррр... Я вас еще не убедил?

Хорош этот способ или нет, решайте сами. По крайней мере он работает.

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

Private Function CheckPoint() As Integer
Dim Ret As Integer
Ret = 0

'Проверяем точки
If Map(Int((Ball.Y + 10) / 40), Int(Ball.X / 40)) <> 0 Then Ret = 4
If Map(Int((Ball.Y + 20) / 40), Int((Ball.X + 10) / 40)) <> 0 Then Ret = 3
If Map(Int((Ball.Y + 10) / 40), Int((Ball.X + 20) / 40)) <> 0 Then Ret = 2
If Map(Int(Ball.Y / 40), Int((Ball.X + 10) / 40)) <> 0 Then Ret = 1

CheckPoint = Ret
End Function

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

Настал момент рисования мяча.

Public Sub DrawBall()
MoveBall 'Вычисляем новые координаты мяча
rc.Top = 0
rc.Left = 81
rc.Bottom = 21
rc.Right = 102
' Рисуем его
Call ddsBack.BltFast(Ball.X, Ball.Y, ddsPic, rc, DDBLTFAST_WAIT Or DDBLTFAST_SRCCOLORKEY)
End Sub

Private Sub MoveBall()
Dim Pt As Integer
'Делем следующий шаг
Ball.X = Ball.X + Ball.SpeedX
Ball.Y = Ball.Y + Ball.SpeedY
Pt = CheckPoint 'И проеряем, где после этого оказался мяч
'Проверяем попадание мяча одной из контрольных точек в занятый сектор
If Pt > 0 Then Select Case Pt
Case 1 'Верх
Ball.SpeedY = -Ball.SpeedY 'Изменение осевой скорости
Ball.Y = Int(Ball.Y / 40) * 40 + 40 'Нормализация осевой координаты
Case 2 'Право
Ball.SpeedX = -Ball.SpeedX
Ball.X = Int((Ball.X + 20) / 40) * 40 - 20
Case 3 'Низ
Ball.SpeedY = -Ball.SpeedY
Ball.Y = Int((Ball.Y + 20) / 40) * 40 - 20
Case 4 'Лево
Ball.SpeedX = -Ball.SpeedX
Ball.X = Int(Ball.X / 40) * 40 + 40
End Select
'Дополнительных точек пока нет
Else
'Нижняя ракетка
If Ball.Y + 20 > 460 Then
If (Ball.X + 15) >= Bet1.X And (Ball.X + 5) <= (Bet1.X + 80) Then 'Отбили мяч
Ball.SpeedY = -Ball.SpeedY
Ball.SpeedX = GetDirectSpeedX
Ball.Y = 460 - 20
Else 'Мяч упал
Reset
End If
End If
'Верхняя ракетка
If Ball.Y < 20 Then
If (Ball.X + 15) >= Bet2.X And (Ball.X + 5) <= (Bet2.X + 80) Then
Ball.SpeedY = -Ball.SpeedY
Ball.SpeedX = GetRandomSpeedX
Ball.Y = 20
Else
Reset
End If
End If
End If
End Sub

Private Function GetDirectSpeedX() As Integer
'Направление мяча по X в зависимости от того, каким местом биты отбил его игрок
GetDirectSpeedX = Int(((Ball.X + 10) - (Bet1.X + 40)) / 8)
End Function

Наконец, последняя функция - вычисление приращения по X ракетки компьютера в зависимости от позиции мяча. Все очень просто. Компьютер всегда старается, чтобы левый и правый край мяча попадал в поле его ракетки.

Private Sub DoBetAI()
If Ball.X <= Bet2.X + 10 Then Bet2.SpeedX = -5
If Ball.X + 20 >= Bet2.X + 80 Then Bet2.SpeedX = 5
If Ball.X > Bet2.X And Ball.X + 20 < Bet2.X + 80 Then Bet2.SpeedX = 0
End Sub

Громкие апплодисменты! Написание модуля завершено. Теперь добавьте в регион две cтроки:

mdlBall.DrawBet 2
mdlBall.DrawBall

И запустите программу (Не забудьте снять ремарку с вызова функции DoBetAI). Работает? Ура!

Надеюсь, теперь вам немного стало ясно, как делаются игрушки на DirectX. Если нет - то это нормально. Просто надо упражняться, упражняться и еще раз упражняться... Не помню кто сказал...

На этом части три пришел конец. В следующей части поговорим об эффектах.

[Назад][Вперед]


Реклама на InfoCity

Яндекс цитирования



Финансы: форекс для тебя








1999-2009 © InfoCity.kiev.ua