Инициализация приложения
Наше знакомство с программой Switch начинается с конструктора switchWin, внутри которого происходит первоначальная инициализация переменных класса. Не следует путать эту инициализацию с той, что выполняется функцией CreateCustomSurfaces(), потому что в отличие конструктора CreateCustomSurfaces() вызывается при каждой смене видеорежима. Конструктор выглядит так:
SwitchWin::SwitchWin() { bmpsurf=0; x=y=0; xinc=8; yinc=1; menusurf=0; fpssurf=0; vlargefont = CreateFont( 28, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH, "Arial" ); smallfont = CreateFont( 14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH, "Arial" ); } |
В основном конструктор просто обнуляет переменные. Два логических номера шрифтов инициализируются функцией Win32 CreateFont(). В программе используются два разных размера одного и того же шрифта: крупным шрифтом выводится заголовок на поверхности меню видеорежимов, а мелким — описания видеорежимов и текст со значением FPS.
После того как объект SwitchWin будет создан, DirectDrawWin вызывает функции SelectDriver() и SelectInitialDisplayMode(). Поскольку в программе Switch обе функции ведут себя стандартным образом (как описано в главе 3), мы не будем их рассматривать.
Затем класс DirectDrawWin вызывает функцию SwitchWin::CreateCustomSurfaces(), в которой подготавливает три поверхности, используемые программой Switch:
BOOL SwitchWin::CreateCustomSurfaces() { int displaydepth=GetCurDisplayDepth(); CString filename; if (displaydepth==8) filename="tri08.bmp"; else filename="tri24.bmp"; bmpsurf=CreateSurface( filename, TRUE ); if (bmpsurf==0) { TRACE("surface creation failed\n"); return FALSE; } selectmode=GetCurDisplayMode(); CreateMenuSurface(); UpdateMenuSurface(); CreateFPSSurface(); return TRUE; } |
Содержимое одной из этих трех поверхностей определяется BMP-файлом. Функция CreateCustomSurfaces() по текущей глубине пикселей определяет, какой из двух BMP-файлов нужно использовать. Затем указатель на поверхность (bmpsurf) инициализируется функцией DirectDrawWin::CreateSurface(). В случае 8-битного видеорежима содержимое палитры DirectDraw определяется палитрой из BMP-файла.
Затем происходит инициализация самой поверхности и переменных, связанных с видеорежимом. Переменной selectmode присваивается значение, зависящее от текущего видеорежима. Это значение используется для выделения активного видеорежима в меню. Указатель на поверхность меню видеорежимов (menusurf) инициализируется вызовами функций CreateMenuSurface() и UpdateMenuSurface().
Наконец, переменные поверхности FPS инициализируются функцией Create FPSSurface(). Мы рассмотрим ее позднее, после функций CreateMenuSurface() и UpdateMenuSurface().
Функция CreateMenuSurface() выглядит так:
BOOL SwitchWin::CreateMenuSurface() { if (menusurf) menusurf->Release(), menusurf=0; menusurf=CreateSurface( menuwidth, menuheight ); if (menusurf==0) Fatal("SwitchWin::CreateMenuSurface() failed\n"); DDCOLORKEY ddck; ddck.dwColorSpaceLowValue = 0; ddck.dwColorSpaceHighValue = 0; menusurf->SetColorKey( DDCKEY_SRCBLT, &ddck ); return TRUE; } |
Прежде всего CreateMenuSurface() освобождает любые поверхности, созданные ранее. Новая поверхность создается функцией CreateSurface(). Доступ к ней осуществляется через переменную menusurf. Затем мы назначаем новой поверхности цветовой ключ с помощью структуры DDCOLORKEY и функции SetColorKey() интерфейса DirectDrawSurface.
Если вы не знаете, для чего нужны цветовые ключи, попробуйте запустить программу Switch и понаблюдать за поведением меню видеорежимов. Обратите внимание — когда перемещающийся растр оказывается в верхней части экрана, он проходит как бы позади меню, но при этом остается видимым. Текст меню непрозрачен, однако те части меню, в которых текста нет, прозрачны. Дело в том, что пиксели пустых участков меню не выводятся DirectDraw и потому не заслоняют растр. Цветовой ключ определяет, какие именно пиксели поверхности не будут выводиться.
Мы назначаем цветовой ключ поверхности меню с помощью структуры DDCOLORKEY и функции SetColorKey(). Оба поля DDCOLORKEY обнуляются (некоторые видеокарты позволяют задавать интервалы цветовых ключей, но в нашем случае используется всего один цвет). Это означает, что пиксели поверхности, равные нулю, не будут копироваться при блит-операциях с активным цветовым ключом.
После того как поверхность меню будет создана функцией CreateMenuSurface(), она заполняется с помощью функции UpdateMenuSurface(). Внутри последней для вывода текста на поверхность используются функция GetDC() интерфейса DirectDrawSurface и текстовые функции Win32. Функция UpdateMenuSurface() приведена в листинге 4.3.
Листинг 4.3. Функция SwitchWin::UpdateMenuSurface()
BOOL SwitchWin::UpdateMenuSurface() { char buf[40]; int len; int hdrlen=strlen( headertext ); ClearSurface( menusurf, 0 ); HDC hdc; menusurf->GetDC( &hdc ); SelectObject( hdc, largefont ); SetBkMode( hdc, TRANSPARENT ); SetTextColor( hdc, textshadow ); TextOut( hdc, 1, 1, headertext, hdrlen ); SetTextColor( hdc, textcolor ); TextOut( hdc, 0, 0, headertext, hdrlen ); SelectObject( hdc, smallfont ); int nmodes=GetNumDisplayModes(); if (nmodes>maxmodes) nmodes=maxmodes; int rows=nmodes/menucols; if (nmodes%menucols) rows++; for (int i=0; i<nmodes; i++) { int x=(i/rows)*colwidth+2; int y=(i%rows)*rowheight+reservedspace; DWORD w,h,d; GetDisplayModeDimensions( i, w, h, d ); len=sprintf( buf, "%dx%dx%d", w, h, d ); SetTextColor( hdc, textshadow ); TextOut( hdc, x+1, y+1, buf, len ); if (i==selectmode) SetTextColor( hdc, brighttextcolor ); else SetTextColor( hdc, textcolor ); TextOut( hdc, x, y, buf, len ); } len=sprintf( buf, "[Arrows] [Enter] [Escape]" ); SetTextColor( hdc, textshadow ); TextOut( hdc, 3, 186, buf, len ); SetTextColor( hdc, textcolor ); TextOut( hdc, 2, 185, buf, len ); menusurf->ReleaseDC( hdc ); return TRUE; } |
Функция UpdateMenuSurface() вызывает ClearSurface() и передает ей в качестве аргументов указатель menusurf и 0. В результате все пиксели поверхности обнуляются. Так как ноль является цветовым ключом для данной поверхности, вся поверхность становится прозрачной.
Теперь все готово к выводу текста. Обратите внимание на функцию SetBkMode(), которая указывает, что текст должен выводиться в прозрачном режиме. Это значит, что функция TextOut() будет выводить только сам текст, без фона, благодаря чему наш прозрачный фон останется в неприкосновенности. Цвет текста задается функцией Win32 SetTextColor(). В этой программе используются три цвета: первый — для обычного текста, второй — для затененного текста, и третий — для текста, выделенного подсветкой. Каждая текстовая строка выводится дважды — сначала затемненным, а потом обычным цветом; затененный текст смещен на один пиксель по отношению к обычному. После завершения вывода текста вызывается функция ReleaseDC() интерфейса DirectDrawSurface.
Инициализация приложения завершается вызовом функции CreateFPSSurface(), которая создает поверхность для вывода FPS. Она выглядит так:
BOOL SwitchWin::CreateFPSSurface() { static const char dummystr[]="000 FPS"; HDC hdc = ::GetDC( 0 ); SelectObject( hdc, smallfont ); SIZE size; GetTextExtentPoint( hdc, dummystr, strlen(dummystr), &size ); ::ReleaseDC( 0, hdc ); fpsrect.left=0; fpsrect.top=0; fpsrect.right=size.cx+1; fpsrect.bottom=size.cy+1; fpssurf=CreateSurface( fpsrect.right, fpsrect.bottom ); DDCOLORKEY ddck; ddck.dwColorSpaceLowValue = 0; ddck.dwColorSpaceHighValue = 0; fpssurf->SetColorKey( DDCKEY_SRCBLT, &ddck ); framecount=0; displayfps=FALSE; return TRUE; } |
Работа CreateFPSSurface() начинается с определения размера поверхности функцией GetTextExtentPoint(). Фиктивная строка (с текстом, который занимает максимальную возможную площадь) передается в качестве аргумента функции GetTextExtentPoint(), вычисляющей размеры области (в пикселях) для вывода заданного текста. По размерам, полученным от GetTextExtentPoint(), мы определяем размеры поверхности, добавляя один пиксель для смещения тени. Такой подход отличается от использованного в функции CreateMenuSurface(), потому что этот код автоматически регулирует размеры поверхности при изменении размера шрифта. Поверхность меню, напротив, обладает фиксированными размерами, не зависящими от размера шрифта.
По аналогии с menusurf мы обеспечиваем прозрачность, назначая поверхности нулевой цветовой ключ (с помощью функции SetColorKey() интерфейса DirectDrawSurface). Наконец, переменная framecount (предназначенная для подсчета кадров за текущий интервал хронометража) обнуляется, а логической переменной displayfps присваивается значение FALSE, согласно которому поверхность FPS пока не должна отображаться на экране.
Хотя мы создали поверхность fpssurf, она осталась неинициализированной. В отличие от поверхности menusurf, инициализируемой функцией UpdateMenuSurface(), мы пока не можем инициализировать поверхность FPS, потому что у нас еще нет выводимого значения. Приложение только что было запущено (или только что перешло в другой видеорежим), так что вывод любого значения FPS был бы необоснованным.
К этому моменту инициализация программы Switch завершается. Наши поверхности (три вспомогательные, плюс первичная поверхность со вторичным буфером) были созданы и подготовлены к работе. Давайте посмотрим, как они отображаются на экране.
Как упоминалось выше, перед инициализацией DirectDraw программа SuperSwitch выводит в функции SuperSwitchWin::OnCreate() диалоговое окно. После вывода диалогового окна функция вызывает версию OnCreate() класса DirectDrawWin. Код функции SuperSwitchWin::OnCreate() выглядит так:
int SuperSwitchWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { IntroDialog introdialog; if (introdialog.DoModal()!=IDOK) return -1; include_refresh=introdialog.include_refresh; if (DirectDrawWin::OnCreate(lpCreateStruct)==-1) return -1; if (include_refresh) ddraw2->EnumDisplayModes( DDEDM_REFRESHRATES, 0, this, StoreModeInfo ); return 0; } |
Сначала мы создаем объект класса IntroDialog — этот класс-оболочка был сгенерирован ClassWizard. Диалоговое окно отображается функцией CDialog::DoModal(), которая возвращает код IDOK в случае нажатия пользователем кнопки OK. Если пользователь закрывает диалоговое окно другим способом (например, нажимая кнопку Cancel), функция OnCreate() возвращает код –1, что для MFC является признаком завершения приложения. Если была нажата кнопка OK, переменной include_refresh присваивается значение в зависимости от состояния флажка в диалоговом окне.
Теперь мы вызываем версию OnCreate() класса DirectDrawWin, где и происходит инициализация DirectDraw. Функция составляет список видеорежимов, активизирует исходный режим и создает поверхности приложения. Если вызов функции OnCreate() завершается неудачей, мы завершаем приложение, возвращая код –1.
Следующий шаг - повторное составление списка видеорежимов. На этот раз при вызове функции EnumDisplayModes() в первом аргументе передается флаг DDEDM_REFRESHRATES, согласно которому каждый видеорежим должен быть включен в список по одному разу для каждой поддерживаемой частоты. В результате мы сможем построить список частот для каждого видеорежима. Четвертый аргумент EnumDisplayModes() — функция косвенного вызова StoreModeInfo(), которая выглядит так:
HRESULT WINAPI SuperSwitchWin::StoreModeInfo( LPDDSURFACEDESC desc, LPVOID p) { DWORD w=desc->dwWidth; DWORD h=desc->dwHeight; DWORD d=desc->ddpfPixelFormat.dwRGBBitCount; DWORD r=desc->dwRefreshRate; SuperSwitchWin* win=(SuperSwitchWin*)p; int index=win->GetDisplayModeIndex( w, h, d ); win->refresh_rates[index].Add(r); return DDENUMRET_OK; } |
Функции StoreModeInfo()> передается указатель на структуру DDSURFACEDESC с описанием очередного видеорежима. В описание входит частота смены кадров (поле dwRefreshRate), а также размеры, по которым определяется индекс режима. Затем этот индекс используется для сохранения частоты видеорежима в массиве.
После выхода из функции OnCreate() класс DirectDrawWin вызывает функцию CreateCustomSurfaces(). По сравнению с программой Switch эта функция не изменилась; она по-прежнему создает три поверхности, потому что новая поверхность (ratemenusurface) создается только в случае необходимости.
Перед тем как инициализировать DirectDraw, класс DirectDrawWin вызывает функцию SelectDriver(), чтобы производные классы могли выбрать драйвер DirectDraw при наличии нескольких вариантов. В программе BmpView мы отказываемся от этой возможности и позволяем выбрать первичный драйвер по умолчанию. Это сделано потому, что для вывода диалоговых окон используется механизм GDI, а GDI может выводить только на первичное видеоустройство (которому соответствует первичный драйвер DirectDraw).
Следующим этапом инициализации приложения является вызов функции SelectInitialDisplayMode(), которую мы обязаны переопределить. Наша версия SelectInitialDisplayMode() выбирает видеорежим с параметрами 640x480x16. Исходный видеорежим не так уж важен, потому что он, скорее всего, будет переопределен пользователем при выборе BMP-файла. Однако функция SelectInitialDisplayMode() (см. листинг 5.6) выполняет две дополнительные задачи.
Листинг 5.6. Функция BmpViewWin::SelectInitialDisplayMode()
int BmpViewWin::SelectInitialDisplayMode() { DisplayModeDescription desc; int i, nummodes=GetNumDisplayModes(); DWORD w,h,d; for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); desc.w=w; desc.h=h; desc.d=d; desc.desc.Format("%dx%dx%d", w, h, d ); if ( d==8 ) palettemode.Add( desc ); else nonpalettemode.Add( desc ); } DWORD curdepth=GetDisplayDepth(); for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); if (w==640 && h==480 && d==curdepth) return i; } for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); if (d==curdepth) return i; } for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); if (w==640 && h==480) return i; } GetSystemPalette(); return 0; } |
Помимо выбора исходного видеорежима функция SelectInitialDisplayMode() используется для подготовки двух массивов: в первом хранятся сведения о палитровых (palettemode), а во втором — о беспалитровых (nonpalettemode) видеорежимах. Мы воспользуемся этими массивами позднее, при отображении диалогового окна. Когда пользователь выбирает файл с палитровым изображением, в список включаются только палитровые режимы; для беспалитровых режимов дело обстоит аналогично. Обратите внимание — в подготовленные массивы (коллекции структур DisplayModeDescription) включены строки, отображаемые в диалоговом окне.
Функция SelectInitialDisplayMode() также используется для вызова функции GetSystemPalette(), создающей палитру DirectDraw на базе системной палитры. Функция GetSystemPalette() выглядит так:
void BmpViewWin::GetSystemPalette() { PALETTEENTRY pe[256]; HDC dc = ::GetDC( 0 ); if (GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE) { GetSystemPaletteEntries( dc, 0, 256, pe ); ddraw2->CreatePalette( DDPCAPS_8BIT, pe, &syspal, 0 ); } ::ReleaseDC( 0, dc ); } |
С помощью функции Win32 GetSystemPaletteEntries() мы получаем содержимое текущей палитры Windows и создаем по ее образцу палитру DirectDraw функцией CreatePalette() интерфейса DirectDraw. Указатель на созданную палитру syspal позднее будет применяться для восстановления системной палитры; это обеспечивает правильное отображение диалоговых окон Windows в 8-битных видеорежимах.
Следующий шаг инициализации приложения, заслуживающий нашего внимания, - функция OnCreate(). В функции OnCreate(), переопределенной классом BmpViewWin(), происходит создание и отображение диалогового окна:
int BmpViewWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (DirectDrawWin::OnCreate(lpCreateStruct) == -1) return -1; ShowDialog(); return 0; } |
Наше знакомство с программой Cursor начинается с функции OnCreate(), которая отвечает за инициализацию DirectDraw, DirectInput и потока ввода. Функция OnCreate() приведена в листинге 7.2.
Листинг 7.2. Функция CursorWin::OnCreate()
int CursorWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { HRESULT r=DirectInputCreate( AfxGetInstanceHandle(), DIRECTINPUT_VERSION, &dinput, 0 ); if (r!=DI_OK) { AfxMessageBox("DirectInputCreate() failed"); return -1; } if (InitMouse()==FALSE) return -1; if (InitKeyboard()==FALSE) return -1; if (DirectDrawWin::OnCreate(lpCreateStruct) == -1) return -1; mousethread->ResumeThread(); return 0; } |
Сначала OnCreate() инициализирует DirectInput функцией DirectInputCreate(). Затем мышь и клавиатура инициализируются функциями InitMouse() и InitKeyboard(), после чего вызывается функция DirectDrawWin::OnCreate(). Функция InitMouse(), которую мы рассмотрим чуть ниже, создает поток ввода, доступ к которому осуществляется через указатель mousepointer. Однако поток ввода создается в приостановленном состоянии, чтобы он не пытался преждевременно обращаться к первичной поверхности. Поток будет запущен лишь после инициализации DirectDraw. Приостановленный поток активизируется функцией CWinThread::ResumeThread().
Давайте рассмотрим функцию InitMouse(), чтобы получить общее представление об инициализации мыши и создании потока ввода. Функция InitMouse() приведена в листинге 7.3.
Листинг 7.3. Функция InitMouse()
BOOL CursorWin::InitMouse() { HRESULT r; r = dinput->CreateDevice( GUID_SysMouse, &mouse, 0 ); if (r!=DI_OK) { TRACE("CreateDevice(mouse) failed\n"); return FALSE; } r = mouse->SetDataFormat( &c_dfDIMouse ); if (r!=DI_OK) { TRACE("mouse->SetDataFormat() failed\n"); return FALSE; } r = mouse->SetCooperativeLevel( GetSafeHwnd(), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND ); if (r!=DI_OK) { TRACE("mouse->SetCooperativeLevel() failed\n"); return FALSE; } DIPROPDWORD property; property.diph.dwSize=sizeof(DIPROPDWORD); property.diph.dwHeaderSize=sizeof(DIPROPHEADER); property.diph.dwObj=0; property.diph.dwHow=DIPH_DEVICE; property.dwData=64; r = mouse->SetProperty( DIPROP_BUFFERSIZE, &property.diph ); if (r!=DI_OK) { TRACE("mouse->SetProperty() failed (buffersize)\n"); return FALSE; } mouse_event[mouse_event_index]=new CEvent; mouse_event[quit_event_index]=new CEvent; r = mouse->SetEventNotification( *mouse_event[mouse_event_index] ); if (r!=DI_OK) { TRACE("mouse->SetEventNotification() failed\n"); return FALSE; } mousethread=AfxBeginThread( (AFX_THREADPROC)MouseThread, this, THREAD_PRIORITY_TIME_CRITICAL, 0, CREATE_SUSPENDED ); return TRUE; } |
Функция InitMouse() состоит из семи этапов:
- Инициализация устройства DirectInput, которое представляет мышь.
- Выбор формата данных, получаемых от мыши.
- Установка уровня кооперации для мыши.
- Инициализация буфера данных мыши.
- Создание двух объектов CEvent.
- Инициализация механизма оповещений DirectInput.
- Создание потока ввода.
На этапе 5 создаются два динамических объекта CEvent, а полученные указатели сохраняются в маленьком массиве. Положение этих указателей в массиве определяется константами mouse_event_index и quit_event_index (которые равны 0 и 1 соответственно). Первое событие блокирует или активизирует поток ввода в зависимости от того, поступили ли от мыши новые данные. Второе событие сообщает потоку мыши о завершении приложения. Как мы вскоре увидим, указатели сохраняются в массиве для того, чтобы мы могли заблокировать поток мыши по двум событиям одновременно.
На этапе 6 функция SetEventNotification() интерфейса DirectInputDevice приказывает DirectInput устанавливать событие мыши при появлении новых данных. Функция SetEventNotification() получает один аргумент типа HANDLE, однако наш объект CEvent наследует оператор преобразования типа от класса CSyncObject, благодаря чему мы можем использовать объект CEvent так, словно он имеет тип HANDLE (тип HANDLE, в частности, используется потоковым API Win32 для представления событий).
На этапе 7 создается поток ввода от мыши. Я снова приведу соответствующий фрагмент листинга 7.2:
mousethread=AfxBeginThread( (AFX_THREADPROC)MouseThread, this, THREAD_PRIORITY_TIME_CRITICAL, 0, CREATE_SUSPENDED ); |
Существуют и другие способы создания потоков, но функция AfxBeginThread() является самым простым вариантом. Она получает шесть аргументов, однако последние четыре имеют значения по умолчанию, так что обязательными являются лишь два аргумента. В нашем случае передается пять аргументов.
Первый аргумент AfxBeginThread — указатель на функцию, выполняемую новым потоком; в нашем случае используется функция MouseThread(). Второй аргумент — значение, которое передается функции потока при вызове. Мы передаем указатель this, чтобы функция MouseThread() могла обращаться к членам нашего класса.
Третий аргумент — приоритет потока. По умолчанию для потока устанавливается нормальный приоритет (флаг THREAD_PRIORITY_NORMAL), но мы переопределяем его и задаем флаг THREAD_PRIORITY_TIME_CRITICAL, чтобы добиться наискорейшего отклика курсора.
Четвертый аргумент — размер стека для нового потока. Ноль означает, что размер стека выбирается по умолчанию. Пятый и последний аргумент определяет исходное состояние потока. Если он равен нулю, создается активный поток; в нашем случае использован флаг CREATE_SUSPENDED, чтобы создавался приостановленный поток.
На создании потока ввода работа функции InitMouse() заканчивается. Благодаря флагу CREATE_SUSPENDED поток ввода приостанавливается до момента, когда основной поток завершит инициализацию DirectDraw. Затем, перед возвратом из функции OnCreate(), поток ввода активизируется функцией ResumeThread() (см. листинг 7.2).
При запуске программы 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 является признаком успешного завершения.