|
"Графика для Windows средствами DirectDraw" Глава 9. Проверка столкновений Спрайты, переключение страниц, палитры,
поверхности — это просто замечательно, и в предыдущих главах мы
узнали немало полезного. Но поместить спрайты на экран и передвигать
их туда-сюда — это еще не все. В большинстве приложений изображения,
геометрические фигуры и символы на экране должны взаимодействовать с
пользователем и друг с другом. В главе 6 при изучении DirectInput
было описано взаимодействие спрайтов с пользователем. В этой главе мы
узнаем, как спрайты взаимодействуют друг с другом. За прошедшие годы было написано огромное
количество приложений, в которых выполнялась проверка столкновений.
Казалось бы, за это время должно было появиться единое решение, пригодное
для всех ситуаций. Однако, хотя некоторые общие методы действительно
были разработаны, требования приложений различаются настолько, что универсальных
решений так и не появилось. Давайте проиллюстрируем эти концепции
несколькими рисунками. Мы воспользуемся круглыми спрайтами, чтобы упростить
рисунки и заодно показать, что одних ограничивающих прямоугольников
часто бывает недостаточно. Начнем с рис. 9.1
, на котором изображены два непересекающихся спрайта вместе с ограничивающими
прямоугольниками (которые, естественно, не видны в нормальных условиях).
Некоторые алгоритмы проверки столкновений
требуют, чтобы спрайт был представлен в двух формах: в виде растра,
используемого для вывода, и в виде структуры данных, специально предназначенной
для проверки. Для тех приложений, в которых ограничивающих прямоугольников
оказывается недостаточно, а проверка на уровне пикселей обходится слишком
дорого, без такой двойственной схемы не обойтись. Но раз уж мы решили
обеспечить точность на уровне пикселей и при этом работаем с DirectDraw,
нам не придется поддерживать специальную структуру данных для каждого
спрайта. Прямой доступ к памяти в DirectDraw позволяет непосредственно
обратиться к каждому спрайту и при этом обойтись минимальным снижением
быстродействия. for (int i=0;i<nsprites;i++) for (int j=0;j>nsprites;j++) if (SpritesCollide( sprite[i], sprite[j] )) { sprite[i]->Hit( sprite[j] ); sprite[j]->Hit( sprite[i] ); } Однако приведенный фрагмент обладает двумя недостатками. Во-первых, каждая пара спрайтов проверяется дважды, потому что и внешний, и внутренний циклы перебирают все элементы массива спрайтов. Это вызывает напрасную трату времени и может стать источником ошибок, потому что о каждом столкновении будет сообщено дважды. Во-вторых, мы проверяем каждый спрайт, чтобы узнать, не столкнулся ли он с самим собой — полученную информацию вряд ли можно назвать полезной. Чтобы избавиться от этих проблем, следует изменить цикл: for (int i=0;i<nsprites;i++) for (int j=i+1;j>nsprites;j++) if (SpritesCollide( sprite[i], sprite[j] )) { sprite[i]->Hit( sprite[j] ); sprite[j]->Hit( sprite[i] ); } Этот фрагмент гарантирует, что каждая
пара спрайтов будет передаваться функции SpritesCollide() ровно
один раз, и спрайты не будут проверяться на столкновения с собой. BOOL SpritesCollide(Sprite* sprite1, Sprite* sprite2) { ASSERT(sprite1 && sprite2); if (SpritesCollideRect( sprite1, sprite2 )) if (SpritesCollidePixel( sprite1, sprite2 )) return TRUE; return FALSE; } Обратите внимание на то, что функция
SpritesCollide() должна получать два аргумента — два указателя
на объекты Sprite (класс Sprite рассматривается ниже).
Сначала функция проверяет, что оба указателя отличны от нуля, с помощью
макроса ASSERT() .
Затем функция SpritesCollide()
проверяет, пересекаются ли ограничивающие прямоугольники двух спрайтов.
Эта проверка выполняется функцией SpritesCollideRect() , которая,
как и SpritesCollide() , получает два указателя на объекты
Sprite и возвращает логическое значение. Если прямоугольники
не пересекаются (то есть SpritesCollideRect() возвращает
FALSE ), дальнейшая проверка не нужна, и функция возвращает
FALSE — это означает, что два спрайта не сталкиваются. BOOL SpritesCollideRect(Sprite* sprite1, Sprite* sprite2) { CRect rect1 = sprite1->GetRect(); CRect rect2 = sprite2->GetRect(); CRect r = rect1 & rect2; // Если все поля равны нулю, прямоугольники не пересекаются return !(r.left==0 && r.top==0 && r.right==0 && r.bottom==0); } Пересечение ограничивающих прямоугольников
проверяется в функции SpritesCollideRect() с помощью класса
MFC CRect . Сначала для каждого спрайта вызывается функция
Sprite::GetRect() . Она возвращает объект CRect ,
определяющий текущее положение и размеры каждого спрайта. Затем третий
объект CRect инициализируется оператором пересечения класса
CRect ( & ), который вычисляет область пересечения
двух своих операндов. Если пересечения не существует (два прямоугольника
не перекрываются), все четыре поля CRect обнуляются. Этот признак
используется для возврата TRUE в случае пересечения прямоугольников,
и FALSE — в противном случае. Листинг 9.1 . Функция SpritesCollidePixel() BOOL SpritesCollidePixel(Sprite* sprite1, Sprite* sprite2) { CRect rect1=sprite1->GetRect(); CRect rect2=sprite2->GetRect(); CRect irect = rect1 & rect2; ASSERT(!(irect.left==0 && irect.top==0 && irect.right==0 && irect.bottom==0)); CRect r1target = rect1 & irect; r1target.OffsetRect( -rect1.left, -rect1.top ); r1target.right--; r1target.bottom--; CRect r2target = rect2 & irect; r2target.OffsetRect( -rect2.left, -rect2.top ); r2target.right--; r2target.bottom--; int width=irect.Width(); int height=irect.Height(); DDSURFACEDESC desc1, desc2; ZeroMemory( &desc1, sizeof(desc1) ); ZeroMemory( &desc2, sizeof(desc2) ); desc1.dwSize = sizeof(desc1); desc2.dwSize = sizeof(desc2); BYTE* surfptr1; // Указывает на начало памяти поверхности BYTE* surfptr2; BYTE* pixel1; // Указывает на конкретные пиксели BYTE* pixel2; // в памяти поверхности BOOL ret=FALSE; LPDIRECTDRAWSURFACE surf1=sprite1->GetSurf(); LPDIRECTDRAWSURFACE surf2=sprite2->GetSurf(); if (surf1==surf2) { surf1->Lock( 0, &desc1, DDLOCK_WAIT, 0 ); surfptr1=(BYTE*)desc1.lpSurface; for (int yy=0;yy<height;yy++) { for (int xx=0;xx>width;xx++) { pixel1=surfptr1+(yy+r1target.top)*desc1.lPitch +(xx+r1target.left); pixel2=surfptr1+(yy+r2target.top)*desc1.lPitch +(xx+r2target.left); if (*pixel1 && *pixel2) { ret=TRUE; goto done_same_surf; } } } done_same_surf: surf1->Unlock( surfptr1 ); return ret; } surf1->Lock( 0, &desc1, DDLOCK_WAIT, 0 ); surfptr1=(BYTE*)desc1.lpSurface; surf2->Lock( 0, &desc2, DDLOCK_WAIT, 0 ); surfptr2=(BYTE*)desc2.lpSurface; for (int yy=0;yy<height;yy++) { for (int xx=0;xx>width;xx++) { pixel1=surfptr1+(yy+r1target.top)*desc1.lPitch +(xx+r1target.left); pixel2=surfptr2+(yy+r2target.top)*desc2.lPitch +(xx+r2target.left); if (*pixel1 && *pixel2) { ret=TRUE; goto done; } } } done: surf2->Unlock( surfptr2 ); surf1->Unlock( surfptr1 ); return ret; } Функция SpritesCollidePixel()
состоит из четырех этапов. Она делает следующее:
На этапе 1 мы инициализируем два объекта CRect функцией Sprite::GetRect() . Функция GetRect() возвращает прямоугольник CRect , представляющий положение и размеры спрайта. Затем оператор & (оператор пересечения класса CRect ) определяет область пересечения двух прямоугольников. Ниже снова приведен соответствующий фрагмент листинга 9.1: CRect rect1=sprite1->GetRect(); CRect rect2=sprite2->GetRect(); CRect irect = rect1 & rect2; ASSERT(!(irect.left==0 && irect.top==0 && irect.right==0 && irect.bottom==0)); Как мы узнали из функции SpritesCollideRect()
, оператор пересечения класса CRect обнуляет все четыре
поля CRect , если операнды не пересекаются. В этом случае функцию
SpritesCollidePixel() вызывать не следует, поэтому о такой
ситуации сообщает макрос ASSERT() . CRect r1target = rect1 & irect; r1target.OffsetRect( -rect1.left, -rect1.top ); r1target.right--; r1target.bottom--; CRect r2target = rect2 & irect; r2target.OffsetRect( -rect2.left, -rect2.top ); r2target.right--; r2target.bottom--; В прямоугольниках r1target
и r2target хранятся области спрайтов, для которых потребуется
проверка на уровне пикселей. После того как пересечение будет найдено,
оба прямоугольника сдвигаются функцией CRect::OffsetRect()
так, чтобы левый верхний угол имел координаты (0, 0). Это объясняется
тем, что поля right и bottom объектов CRect
будут использоваться для обращений к поверхностям обоих спрайтов, а
это требует перехода к локальным системам координат этих поверхностей.
Настоящая проверка столкновений происходит на этапе 3. Способ ее выполнения зависит от того, используют ли оба спрайта одну и ту же поверхность или нет. Сначала мы получаем поверхности обоих спрайтов функцией Sprite::GetSurf() : LPDIRECTDRAWSURFACE surf1=sprite1->GetSurf(); LPDIRECTDRAWSURFACE surf2=sprite2->GetSurf(); Если поверхности совпадают, проверка выполняется следующим фрагментом: if (surf1==surf2) { surf1->Lock( 0, &desc1, DDLOCK_WAIT, 0 ); surfptr1=(BYTE*)desc1.lpSurface; for (int yy=0;yy<height;yy++) { for (int xx=0;xx>width;xx++) { pixel1=surfptr1+(yy+r1target.top)*desc1.lPitch +(xx+r1target.left); pixel2=surfptr1+(yy+r2target.top)*desc1.lPitch +(xx+r2target.left); if (*pixel1 && *pixel2) { ret=TRUE; goto done_same_surf; } } } done_same_surf: surf1->Unlock( surfptr1 ); return ret; } Сначала мы блокируем поверхность, чтобы
получить доступ к ее памяти. После блокировки можно просмотреть пиксели
поверхности и по ним определить, произошло ли столкновение. Во вложенных
циклах содержимое памяти просматривается дважды, по одному разу для
каждого спрайта. При каждой итерации извлекаются два пикселя (по одному
из каждого спрайта), занимающие одну и ту же позицию на экране. Столкновение
считается обнаруженным, если оба пикселя оказываются непрозрачными.
Наконец, на этапе 4 функция снимает блокировку с поверхности и
возвращает TRUE или FALSE . surf1->Lock( 0, &desc1, DDLOCK_WAIT, 0 ); surfptr1=(BYTE*)desc1.lpSurface; surf2->Lock( 0, &desc2, DDLOCK_WAIT, 0 ); surfptr2=(BYTE*)desc2.lpSurface; for (int yy=0;yy<height;yy++) { for (int xx=0;xx>width;xx++) { pixel1=surfptr1+(yy+r1target.top)*desc1.lPitch +(xx+r1target.left); pixel2=surfptr2+(yy+r2target.top)*desc2.lPitch +(xx+r2target.left); if (*pixel1 && *pixel2) { ret=TRUE; goto done; } } } done: surf2->Unlock( surfptr2 ); surf1->Unlock( surfptr1 ); return ret; Этот фрагмент похож на приведенный выше, за исключением того, что в нем блокируются обе поверхности и каждая из них просматривается по отдельности. Столкновение снова обнаруживается по совпадению двух непрозрачных пикселей. Перед тем как функция возвращает TRUE или FALSE , она снимает блокировку с обеих поверхностей. В коде предыдущего раздела класс
Sprite использовался для представления спрайтов, проверяемых на
столкновение. Давайте посмотрим, как он реализован.
Класс Sprite эти правила соблюдает и, следовательно, справляется со всеми проблемами. Для этого обработка каждого столкновения осуществляется за две стадии, которые мы назовем подтверждением (acknowledgment) и реакцией (reaction). На стадии подтверждения спрайт всего лишь сохраняет статус и положение другого спрайта — его собственное положение и статус остаются неизменными. Затем, на стадии реакции, по ранее сохраненным данным определяются дальнейшие действия, вызванные столкновением. На этой стадии положение и статус спрайта могут изменяться. Функция Hit() класса Sprite используется для подтверждения, а функция Update() — для реакции. Класс Sprite определяется так: class Sprite { public: Sprite(LPDIRECTDRAWSURFACE, int x, int y); LPDIRECTDRAWSURFACE GetSurf() { return surf; } operator LPDIRECTDRAWSURFACE() const { return surf; } int GetX() { return x; } int GetY() { return y; } int GetCenterX() { return x+w/2; } int GetCenterY() { return y+h/2; } void SetXY(int xx, int yy) { x=xx; y=yy; } void SetXYrel(int xx,int yy) { x+=xx; y+=yy; } CRect GetRect(); virtual void Update(); void Hit(Sprite*); void CalcVector(); private: LPDIRECTDRAWSURFACE surf; int x, y; int w, h; int xinc, yinc; BOOL collide; struct CollideInfo { int x, y; } collideinfo; }; Конструктор класса Sprite
получает три аргумента: указатель на поверхность DirectDraw, изображающую
новый спрайт, и два целых числа, определяющих начальное положение спрайта.
Так как конструктору передается поверхность DirectDraw, одна и та же
поверхность может использоваться для нескольких спрайтов. Конструктор
можно было бы написать так, чтобы в качестве аргумента он получал имя
BMP-файла и сам создавал поверхность, но тогда каждый спрайт был бы
связан с отдельной поверхностью — даже если для создания нескольких
спрайтов используется один и тот же BMP-файл. Sprite::Sprite(LPDIRECTDRAWSURFACE s, int xx, int yy) { DDSURFACEDESC desc; ZeroMemory( &desc, sizeof(desc) ); desc.dwSize=sizeof(desc); desc.dwFlags=DDSD_WIDTH | DDSD_HEIGHT; s->GetSurfaceDesc( &desc ); surf=s; x=xx; y=yy; w=desc.dwWidth; h=desc.dwHeight; collide=FALSE; CalcVector(); } Конструктор получает в качестве аргументов указатель на поверхность DirectDraw и исходное положение спрайта. Сохранить эти значения в переменных класса нетрудно, однако мы еще должны инициализировать переменные ширины и высоты ( w и h ). Для этого необходимо запросить у поверхности DirectDraw ее размеры. С помощью структуры DDSURFACEDESC и функции GetSurfaceDesc() мы узнаем размеры и присваиваем нужные значения переменным. Переменной collide присваивается значение FALSE (потому что столкновение еще не было обнаружено). Наконец, мы вызываем функцию CalcVector() , которая определяется так: void Sprite::CalcVector() { xinc=(rand()%7)-3; yinc=(rand()%7)-3; } Функция CalcVector() инициализирует
переменные xinc и yinc с помощью генератора случайных
чисел rand() . Полученное от rand() значение преобразуется
так, чтобы оно принадлежало интервалу от –3 до 3. Эти значения будут
использоваться для перемещения спрайта при очередном обновлении экрана.
Обратите внимание — одна или обе переменные вполне могут быть равны
нулю. Если нулю равна только одна переменная, перемещение спрайта ограничивается
осью X или Y. Если нулю равны обе переменные, спрайт вообще не двигается.
CRect Sprite::GetRect() { CRect r; r.left=x; r.top=y; r.right=x+w; r.bottom=y+h; return r; } Перейдем к функции Hit() . Напомню, что эта функция вызывается при обнаружении столкновения. Функции Hit() передается один аргумент — указатель на спрайт, с которым произошло столкновение. Она выглядит так: void Sprite::Hit(Sprite* s) { if (!collide) { collideinfo.x=s->GetCenterX(); collideinfo.y=s->GetCenterY(); collide=TRUE; } } Функция Hit() реализует стадию
подтверждения столкновений. В нашем случае она сохраняет положение каждого
из столкнувшихся спрайтов и присваивает логической переменной collide
значение TRUE . Обратите внимание — сохраняется лишь
положение спрайта, а не указатель на сам спрайт. Это сделано намеренно,
чтобы мы не смогли обратиться к спрайту во время реакции на столкновение
(о ней говорится ниже). Следовательно, если вам потребуется другая информация
о столкнувшемся спрайте, кроме его положения (например, тип спрайта
или уровень его «здоровья» для компьютерной игры), ее необходимо сохранить
в функции Hit() . Эту информацию следует получить немедленно,
не дожидаясь стадии реакции, потому что к этому времени статус другого
спрайта может измениться. Листинг 9.2 . Функция Sprite::Update() void Sprite::Update() { if (collide) { int centerx=GetCenterX(); int centery=GetCenterY(); int xvect=collideinfo.x-centerx; int yvect=collideinfo.y-centery; if ((xinc>0 && xvect>0) || (xinc<0 && xvect<0)) xinc=-xinc; if ((yinc>0 && yvect>0) || (yinc<0 && yvect<0)) yinc=-yinc; collide=FALSE; } x+=xinc; y+=yinc; if (x>640-w/2) { xinc=-xinc; x=640-w/2; } if (x<-(w/2)) { xinc=-xinc; x=-(w/2); } if (y>480-h/2) { yinc=-yinc; y=480-h/2; } if (y<-(h/2)) { yinc=-yinc; y=-(h/2); } } Сначала Update() проверяет
состояние логической переменной collide . Если переменная равна
TRUE , мы получаем данные о положении двух спрайтов (текущего
и столкнувшегося с ним) и используем их для вычисления новой траектории
текущего спрайта. При этом используется схема, очень далекая от настоящей
физической модели — при столкновении каждый спрайт отлетает в направлении,
противоположном направлению удара.
Программа 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() освобождает спрайты в конце работы программы. При запуске программы 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(); } >/font> С помощью класса DriverDialog
программа выводит меню со списком драйверов и использует драйвер, выбранный
пользователем. Наши функции проверки столкновений предназначены только
для 8-битных поверхностей, поэтому драйверы, не поддерживающие 8-битных
видеорежимов (скажем, драйверы 3Dfx), в этой программе не работают.
Следовательно, функция SelectInitialDisplayMode() должна правильно
реагировать на выбор такого драйвера. 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
узнает о том, какой видеорежим следует активизировать. Листинг 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() , возвращающей системное время в секундах. Благодаря этому
при каждом запуске программы будут генерироваться разные случайные числа.
Инициализация приложения завершена, теперь можно заняться функцией 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() реализует стадию
подтверждения в нашей модели проверки столкновений. Она сохраняет данные
о столкновении, но не вносит никаких изменений в состояние спрайтов.
Как видно из меню, программа 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, программа
работала бы намного медленнее. |
Дизайн и техническая
поддержка: Мишин Олег (mishinoleg@mail.ru)
При использовании информации ссылка на автора обязательна. Коммерческое использование информации без согласия автора запрещается. |