Книга: Графика для Windows средствами DirectDraw

Программа Bumper 

Программа Bumper 

Для проверки алгоритма мы напишем демонстрационную программу. Программа Bumper выполняет отображение и анимацию восьми спрайтов. Как я упоминал, при столкновении спрайты разлетаются в противоположных направлениях. Программа Bumper изображена на рис. 9.4.


Рис. 9.4. Программа Bumper

Восемь спрайтов, показанных на рисунке, представлены четырьмя разными поверхностями — по каждой поверхности создаются два спрайта. Исходные векторы направления, по которым перемещаются спрайты, определяются случайным образом. В начале своей работы программа «раскручивает» генератор случайных чисел, чтобы результаты ее работы не были всегда одинаковыми. При нажатии клавиши пробела векторы направления пересчитываются заново. Код программы Bumper рассматривается в следующих разделах. 

Класс BumperWin 

Программа Bumper, как и все остальные программы в этой книге, построена на основе базового класса DirectDrawWin. Производный от него класс BumperWin определяется так:

class BumperWin : public DirectDrawWin {
public:
 BumperWin();
protected:
 //{{AFX_MSG(BumperWin)
 afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
 afx_msg void OnDestroy();
 //}}
 AFX_MSG DECLARE_MESSAGE_MAP()
private:
 int SelectDriver();
 int SelectInitialDisplayMode();
 BOOL CreateCustomSurfaces();
 void DrawScene();
 void RestoreSurfaces();
 BOOL SpritesCollide(Sprite* s1, Sprite* s2);
 BOOL SpritesCollideRect(Sprite* s1, Sprite* s2);
 BOOL SpritesCollidePixel(Sprite* s1, Sprite* s2);
private:
 Sprite* sprite[MAX_SPRITES];
 int nsprites;
 LPDIRECTDRAWSURFACE text;
};

В нем объявляются два обработчика сообщений. Функция OnKeyDown() обрабатывает нажатия клавиш, а функция OnDestroy() освобождает спрайты в конце работы программы.

Функции SelectDriver(), SelectInitialDisplayMode(), CreateCustomSurfaces(), DrawScene() и RestoreSurfaces() наследуются от класса DirectDrawWin. Вскоре мы подробно рассмотрим каждую из этих функций. Функции SpritesCollide(), SpritesCollideRect() и SpritesCollidePixel() совпадают с одноименными функциями, описанными выше, однако на этот раз они принадлежат классу BumperWin. Поскольку эти функции уже рассматривались, мы не будем обсуждать их снова.

В классе объявлены три переменные: массив указателей на объекты Sprite, целая переменная для хранения общего количества спрайтов и указатель text на интерфейс DirectDrawSurface. Первые две переменные предназначены для хранения спрайтов и последующих обращений к ним. Указатель text используется для отображения меню, находящегося в левом нижнем углу экрана. 

Инициализация приложения 

При запуске программы Bumper прежде всего вызывается функция SelectDriver(). Чтобы добиться максимальной гибкости, при наличии нескольких драйверов DirectDraw программа Bumper выводит меню. Функция SelectDriver() выглядит так:

int BumperWin::SelectDriver() {
 int numdrivers=GetNumDrivers();
 if (numdrivers==1) return 0;
 CArray<CString, CString> drivers;
 for (int i=0;i<numdrivers;i++) {
  LPSTR desc, name;
  GetDriverInfo(i, 0, &desc, &name);
  drivers.Add(desc);
 }
 DriverDialog dialog;
 dialog.SetContents(&drivers);
 if (dialog.DoModal()!=IDOK) return -1;
 return dialog.GetSelection();
}

С помощью класса DriverDialog программа выводит меню со списком драйверов и использует драйвер, выбранный пользователем. Наши функции проверки столкновений предназначены только для 8-битных поверхностей, поэтому драйверы, не поддерживающие 8-битных видеорежимов (скажем, драйверы 3Dfx), в этой программе не работают. Следовательно, функция SelectInitialDisplayMode() должна правильно реагировать на выбор такого драйвера.

Функция SelectInitialDisplayMode() вызывается после функции SelectDriver(), но перед созданием поверхностей. Функция выглядит так:

int BumperWin::SelectInitialDisplayMode() {
 DWORD curdepth=GetDisplayDepth();
 int i, nummodes=GetNumDisplayModes();
 DWORD w,h,d;
 if (curdepth!=desireddepth) ddraw2->SetDisplayMode(640, 480, curdepth, 0, 0);
 for (i=0;i<nummodes;i++) {
  GetDisplayModeDimensions(i, w, h, d);
  if (w==desiredwidth && h==desiredheight && d==desireddepth) return i;
 }
 ddraw2->RestoreDisplayMode();
 ddraw2->Release(), ddraw2=0;
 AfxMessageBox("Can't find 8-bit mode on this device");
 return -1;
}

Функция SelectInitialDisplayMode() ищет конкретный видеорежим 640x480x8. Если этот режим не найден, она выводит сообщение и возвращает –1, говоря тем самым классу DirectDrawWin о том, что приложение следует завершить. Если режим будет найден, функция возвращает его индекс. По этому индексу класс DirectDrawWin узнает о том, какой видеорежим следует активизировать.

Если функция SelectInitialDisplayMode() находит нужный видеорежим, класс DirectDrawWin вызывает функцию CreateCustomSurfaces(). Она создает поверхности наших восьми спрайтов, а также поверхность меню. Функция CreateCustomSurfaces() приведена в листинге 9.3.

Листинг 9.3. Функция CreateCustomSurfaces()

BOOL BumperWin::CreateCustomSurfaces() {
 DDCOLORKEY ddck;
 ddck.dwColorSpaceLowValue = 0;
 ddck.dwColorSpaceHighValue = 0;
 LPDIRECTDRAWSURFACE surf;
 srand(time(0));
 CString msg="Can't find ";
 surf=CreateSurface("diamond.bmp", TRUE);
 if (surf==0) {
  msg+="diamond.bmp";
  Fatal(msg);
 }
 surf->SetColorKey(DDCKEY_SRCBLT, &ddck);
 sprite[nsprites++]=new Sprite(surf, 0, 0);
 sprite[nsprites++]=new Sprite(surf, 150, 0);
 surf=CreateSurface("triangle.bmp");
 if (surf==0) {
  msg+="triangle.bmp";
  Fatal(msg);
 }
 surf->SetColorKey(DDCKEY_SRCBLT, &ddck);
 sprite[nsprites++]=new Sprite(surf, 0, 150);
 sprite[nsprites++]=new Sprite(surf, 150, 150);
 surf=CreateSurface("rect.bmp");
 if (surf==0) {
  msg+="rect.bmp";
  Fatal(msg);
 }
 surf->SetColorKey(DDCKEY_SRCBLT, &ddck);
 sprite[nsprites++]=new Sprite(surf, 0, 300);
 sprite[nsprites++]=new Sprite(surf, 150, 300);
 surf=CreateSurface("oval.bmp");
 if (surf==0) {
  msg+="oval.bmp";
  Fatal(msg);
 }
 surf->SetColorKey(DDCKEY_SRCBLT, &ddck);
 sprite[nsprites++]=new Sprite(surf, 300, 0);
 sprite[nsprites++]=new Sprite(surf, 300, 150);
 text=CreateSurface("text.bmp");
 if (text==0) {
  msg+="text.bmp";
  Fatal(msg);
 }
 text->SetColorKey(DDCKEY_SRCBLT, &ddck);
 return TRUE;
}

Функция CreateCustomSurfaces() «раскручивает» генератор случайных чисел с помощью функции time(), возвращающей системное время в секундах. Благодаря этому при каждом запуске программы будут генерироваться разные случайные числа.

Затем для каждой создаваемой поверхности готовится структура DDCOLORKEY. Для всех поверхностей этого приложения прозрачным является черный цвет (то есть нулевое значение).

Функция создает четыре поверхности, и по каждой поверхности — два спрайта. Если хотя бы один из BMP-файлов, по которым создаются поверхности, не будет найден, функция Fatal() выводит сообщение и завершает программу. Для успешно созданных поверхностей с помощью функции SetColorKey() интерфейса DirectDrawSurface активизируются цветовые ключи.

Наконец, поверхность меню text инициализируется содержимым файла TEXT.BMP. Функция SetColorKey(), как и в случае спрайтовых поверхностей, определяет прозрачный цвет. Код возврата TRUE является признаком успешного завершения. 

Функция DrawScene() 

Инициализация приложения завершена, теперь можно заняться функцией DrawScene(). Эта функция выполняет проверку столкновений, строит кадр во вторичном буфере и переключает страницы. В программе Bumper() функция DrawScene() выглядит так:

void BumperWin::DrawScene() {
 ASSERT(nsprites>0);
 ASSERT(text);
 for (int s1=0;s1<nsprites;s1++) for (int s2=s1+1;s2>nsprites;s2++) if (SpritesCollide(sprite[s1], sprite[s2])) {
  sprite[s1]->Hit(sprite[s2]);
  sprite[s2]->Hit(sprite[s1]);
 }
 for (int i=0;i<nsprites;i++) sprite[i]->Update();
 ClearSurface(backsurf, 0);
 for (i=0;i<nsprites;i++) {
  Sprite* s=sprite[i];
  BltSurface(backsurf, *s, s->GetX(), s->GetY(), TRUE);
 }
 BltSurface(backsurf, text, 0, 448, TRUE);
 primsurf->Flip(0, DDFLIP_WAIT);
}

Проверка столкновений осуществляется во вложенном цикле. Для каждой пары спрайтов вызывается функция SpritesCollide(), а при обнаруженном столкновении вызывается функция Hit(), которой в качестве аргументов передаются оба столкнувшихся спрайта. Напомню, что функция Sprite::Hit() реализует стадию подтверждения в нашей модели проверки столкновений. Она сохраняет данные о столкновении, но не вносит никаких изменений в состояние спрайтов.

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

После того как все столкновения будут обнаружены и обработаны, мы стираем вторичный буфер функцией DirectDrawWin::ClearSurface() и выводим каждый спрайт функцией BltSurface(). Обратите внимание на то, что вторым аргументом BltSurface() является указатель на сам объект Sprite. В данном случае оператор LPDIRECTDRAWSURFACE() преобразует объект Sprite в указатель на поверхность, соответствующую данному спрайту. Также стоит заметить, что координаты спрайтов определяются функциями GetX() и GetY(). После прорисовки всех спрайтов в левом нижнем углу вторичного буфера выводится поверхность меню. Функция Flip() переключает страницы и отображает кадр на экране. 

Функция OnKeyDown() 

Как видно из меню, программа Bumper реагирует на две клавиши: пробел и Escape. Нажатие пробела приводит к тому, что векторы направлений каждого спрайта пересчитываются заново, а Escape завершает работу программы. Функция OnKeyDown() выглядит так:

void BumperWin::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
 switch (nChar) {
 case VK_ESCAPE:
  PostMessage(WM_CLOSE);
  break;
 case VK_SPACE:
 case VK_RETURN:
  for (int i=0;i<nsprites;i++) sprite[i]->CalcVector();
  break;
 }
 DirectDrawWin::OnKeyDown(nChar, nRepCnt, nFlags);
}

Восстановление потерянных поверхностей 

Прежде чем расставаться с программой Bumper, мы должны посмотреть, как происходит восстановление потерянных поверхностей. Как обычно, для этого служит функция RestoreSurfaces():

void BumperWin::RestoreSurfaces() {
 for (int i=0;i<nsprites;i++) sprite[i]->GetSurf()->Restore();
 LoadSurface(*sprite[0], "diamond.bmp");
 LoadSurface(*sprite[1], "diamond.bmp");
 LoadSurface(*sprite[2], "triangle.bmp");
 LoadSurface(*sprite[3], "triangle.bmp");
 LoadSurface(*sprite[4], "rect.bmp");
 LoadSurface(*sprite[5], "rect.bmp");
 LoadSurface(*sprite[6], "oval.bmp");
 LoadSurface(*sprite[7], "oval.bmp");
 text->Restore();
 LoadSurface(text, "text.bmp");
}

Сначала область памяти каждой поверхности восстанавливается функцией Restore() (если поверхность не была потеряна, вызов Restore() игнорируется). Затем функция LoadSurface() восстанавливает содержимое поверхности. Обратите внимание — здесь, как и в функции DrawScene(), используется оператор LPDIRECTDRAWSURFACE(), позволяющий передавать объекты Sprite вместо указателей на поверхности. Работа функции завершается восстановлением поверхности меню (text).

Заключение 

Если запустить программу Bumper (даже на относительно медленном компьютере), становится очевидно, что наши функции проверки столкновений работают достаточно эффективно. Даже когда спрайты сближаются на близкое расстояние и активизируется проверка на уровне пикселей, замедления работы не ощущается. Отчасти это объясняется оптимизацией, а отчасти — тем обстоятельством, что мы непосредственно обращаемся к памяти поверхности. Конечно, если бы обращение к каждому пикселю осуществлялось через специальную функцию DirectDraw, программа работала бы намного медленнее.

Эта глава была последней — мы рассмотрели все программы. Тем не менее остались некоторые интересные темы, которые не обсуждались в книге. Мы поговорим о них в приложении А.

Оглавление книги


Генерация: 1.169. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз