DirectX5.0最新遊戲編程指南

 一 配置DirectX SDK 2
1.1 配置Microsoft Developer Studio 2
1.2 配製NMAKE路徑 3
1.3 爲Borland C++5.0配置DirectX SDK 3
二 第一個DirectDraw實例 3
2.1 首先初始化DirectDraw對象 4
2.2 創建DirectDraw 對象 5
2.3 設置顯示模式 5
2.4 改變顯示模式 6
2.5 創建可翻轉表面(Flippable Surface) 6
2.6 定義表面要求 6
2.7 創建表面 7
2.8 着色表面 8
2.9 寫表面及翻轉表面 8

2.10 釋放DirectDraw 對象 10
2.11 將位圖調入表面 10
2.12 設置調色板,將位圖調入後臺緩衝區 11
2.13 從屏外表面位塊傳輸 12
2.14 將位圖文件調入屏外表面 13
2.15 將屏外表面位位塊傳輸到後臺緩衝區 13
三、創建動畫 14
3.1 Color Key和位圖動畫 14
3.2 DDEX4中的動畫 15
3.3 動態改變調色板 15
3.4 替換調色板 15
四 使用覆蓋表面 16
4.1 創建一個主表面 16
4.2 檢測硬件對覆蓋的支持 17
4.3 創建一個覆蓋表面 17
4.4 顯示覆蓋表面 19
4.5 更新覆蓋的顯示位置 22
4.6 隱藏覆蓋表面 23
五 DirectDraw中其它的DirectDraw範例 23

 

DirectDraw教程篇

DirectX是爲Visual C++的用戶準備的,因此要編制DirectDraw遊戲程序,最好對VC要有一定的瞭解。不願意使用VC的用戶也可以利用Arakelian Soft公司開發的專門針對Visual Basic5.0用戶的ActiveX控件DirectStudio98或Tegosoft公司的TegoSoft ActiveX for Visual Basic。不過,如果想充分發揮DirectX的性能,並且希望保持程序的兼容,那麼最好還是使用Visual C++。
爲了敘述方便,假定已經安裝了DirectX5.0 SDK 和Visual C++ 5.0,其目錄分別是C:\DX5SDK和C:\Program Files\DevStudio。如果你使用了另一種編譯器或安裝到了其它目錄下,必須將下面的例子做適當的修改才能運行。有人安裝了DirectX SDK後卻不知怎樣使用,因爲它是基於Visual C++的,卻沒有一個界面友好的集成開發環境,因此必須對Visual C++進行適當的配製。

一 配置DirectX SDK
1.1 配置Microsoft Developer Studio
  爲了編譯DirectX SDK提供的例子,需要打開一個新的project workspace,插入適當的文件,設置環境變量使得編譯器能夠找到需要的鏈接庫和包含文件,下面描述了設置的全部過程。啓動Microsoft Developer Studio,安裝下述步驟創建工程:
.在File菜單,選擇New;
.在New對話框中選擇Project中的Win32 Application,在Project Name輸入DDEX1
.在Location文本框輸入放置工程文件的位置,點OK按鈕
.一個新的DDEX1 Classes文件夾就出現在workspace窗口的左邊了。
創建了工程後,需要使用如下步驟向工程插入適當的文件:
.在Project菜單選擇Add toProject|Files
.瀏覽到C:\DX5SDK\SDK\SAMPLES\DDEX1 目錄,選擇所有的文件
.選擇OK,該目錄下的DDEX1.CPP、DDEX1.RC、RESOURCE.H就加入到工程了。
然後設置包含文件的路徑:
.在Tools菜單,選擇Options,就彈出Options對話框
.選擇Directories ,在Show Directories For列表框選擇Include files
.在Directories:列表框雙擊列表底部的空白行,輸入C:\DX5SDK\SDK\INC.
.同樣再加入另一個路徑C:\DX5SDK\SDK\SAMPLES\MISC
.選擇OK按鈕,
設置鏈接庫目錄:
.在Show Directories For列表框選擇Library files
.在Directories:列表框雙擊底部空白行,輸入C:\DX5SDK\SDK\LIB.
.單擊OK按鈕。
最後設置建立應用程序時鏈接的模塊:
.在Project菜單單擊Settings.
.選擇Link
.在Category下拉框選擇General.
.在Object/Library模塊列表框加入Ddraw.lib和Winmm.lib.
.單擊OK.
1.2 配製NMAKE路徑
  有時候命令行的方式比集成環境更加方便,所以許多有經驗的程序員更願意用命令行的方式來建立應用程序。下面是包含文件和鏈接庫模塊的路徑:
@echo off
set PATH=C:\Program Files\DevStudio\SharedIDE\Bin;
  C:\Program Files\DevStudio\Vc\Bin;%PATH%
set INCLUDE=C:\Program Files\DevStudio\Vc\include;
  C:\Program Files\DevStudio\Vc\Mfc\include;C:\DX5SDK\SDK\INC;%INCLUDE%
set LIB= C:\Program Files\DevStudio\SharedIDE\Vc\lib;
  C:\Program Files\DevStudio\Vc\Mfc\lib; C:\DX5SDK\SDK\LIB;%LIB%
set INIT= C:\Program Files\DevStudio;%INIT%
將上述內容加入Autoexec.bat。在例子的目錄下輸入
NMAKE
將會在當前目錄下創建一個DEBUG目錄,並將生成的可執行文件放在該目錄下。
爲了在學習的過程中熟悉DirectX SDK,我們將按照DirectX SDK提供的範例程序的順序由淺入深,循序漸進。
1.3 爲Borland C++5.0配置DirectX SDK
儘管DirectX 5 SDK是主要爲Visual C++用戶準備的,但Microsoft並未忘記衆多的Borland
C++用戶,所以在DirectX SDK中也提供了DirectX的Borland C++庫。不過,可能出於競爭的緣故(猜測而已),安裝後的DirectX SDK中並沒有Borland C++庫。這就需要用戶自己來處理這一惱人的問題了。我們知道,DirectX 5 SDK是以一個IDX5SDK.EXE發佈的,運行IDX5SDK後,它先將壓縮的文件全部解壓到某個目錄下(如D:\DX5SDK),然後再運行該目錄下的SETUP.EXE安裝DirectX SDK(假設目錄爲C:\DX5SDK)。實際上,在解壓後的目錄中包含了一個D:\DX5SDK\SDK\LIB\BORLANDC目錄,該目錄下就是Borland C++的鏈接庫文件。但在SETUP安裝時,安裝程序並沒有把該目錄複製到安裝目錄中。解決方法很簡單,即SETUP安裝完成後,再建立一個C:\DX5SDK\SDK\LIB\Borland,將目錄D:\DX5SDK\SDK\LIB\Borland下的所有文件都複製到C:\DX5SDK\SDK\LIB\Borland目錄下。然後在Borland C++5.0的集成環境中如同配置Visual C++5.0那樣配置工程文件。

二 第一個DirectDraw實例
要使用DirectDraw,首先必須創建DirectDraw對象的一個實例來表徵計算機上的顯示適配卡,然後使用接口方法來處理對象。另外還需要創建一個或多個DirectDrawSurface對象的實例來顯示遊戲。DDEX1首先創建一個DirectDraw對象,再創建一個主表面(primary
surface)和一個後臺緩衝區(back buffer),然後在表面之間轉換。DDEXx例子都是用C++寫成的,如果你使用的是C編譯器,必須將代碼做適當改動,至少要加入虛表和指向接口方法的指針。
2.1 首先初始化DirectDraw對象
DDEX1 程序在doInit函數包含了DirectDraw 的初始化代碼:
// 創建主DirectDraw 對象
ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
if( ddrval == DD_OK )
{
// 獲取獨佔模式
ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE| DDSCL_FULLSCREEN );
if(ddrval == DD_OK )
{
ddrval = lpDD->SetDisplayMode(640, 480, 8 );
if( ddrval == DD_OK )
{
//創建帶有一個後臺緩衝區的主表面
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |DDSCAPS_FLIP |DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
if( ddrval == DD_OK )
{
//獲取後臺緩衝區的指針
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval=lpDDSPrimary->GetAttachedSurface(&ddscaps,&lpDDSBack);
if( ddrval == DD_OK )
{
// 畫出一些文本
if (lpDDSPrimary->GetDC(&hdc) == DD_OK)
{
SetBkColor( hdc, RGB( 0, 0, 255 ) );
SetTextColor( hdc, RGB( 255, 255, 0 ) );
TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) );
lpDDSPrimary->ReleaseDC(hdc);
}
if (lpDDSBack->GetDC(&hdc) == DD_OK)
{
SetBkColor( hdc, RGB( 0, 0, 255 ) );
SetTextColor( hdc, RGB( 255, 255, 0 ) );
TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) );
lpDDSBack->ReleaseDC(hdc);
}
// 創建翻轉頁面的計時器
if( SetTimer( hwnd, TIMER_ID, TIMER_RATE, NULL ) )
{
return TRUE;
}
}
}
}
}
}
wsprintf(buf, "Direct Draw Init Failed (%08lx)\n", ddrval );
............
下面詳細說明初始化DirectDraw 對象的創建和準備表面的每一步驟。
2.2 創建DirectDraw 對象
創建DirectDraw 對象的一個實例,應該用DirectDrawCreate API 函數,也可以用COM中的CoCreateInstance函數。DirectDrawCreate用一個全局統一標誌符GUID(Globally
Unique IDentifier)來表徵顯示設備,在大多數情況下GUID爲NULL(使用系統的缺省顯示設備,既“空設備”);指針指向DirectDraw對象的地址;第三個參數總是NULL(供將來擴展使用)。下述代碼表明瞭如何創建一個DirectDraw對象,並且檢驗是否成功。
ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
if( ddrval == DD_OK )
{
// lpDD is是合法的DirectDraw 對象
}
else
{
// DirectDraw對象不能被創建
}
2.3 設置顯示模式
設置DirectDraw 應用程序的顯示模式需要兩步:首先調用IDirectDraw::SetCooperativeLevel方法來設定該模式下的要求,一旦確定了要求,再用IDirectDraw::SetDisplayMode方法來選擇顯示分辨率。
在改變顯示分辨率之前,還必須通過IDirectDraw::SetCooperativeLevel方法來指定DDSCL_EXCLUSIVE和DDSCL_FULLSCREEN標誌。
  這樣能使遊戲程序完全控制顯示設備,其它的應用程序不能同時共享顯示設備。DDSCL_FULLSCREEN標誌表示將程序設爲全屏模式。下面的代碼顯示瞭如何使用IDirectDraw::SetCooperativeLevel方法:
HRESULT ddrval;
LPDIRECTDRAW lpDD; // already created by DirectDrawCreate
ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
if( ddrval == DD_OK )
{
// 全屏獨佔方式設置成功
}
else {
// 調用不成功,但程序仍然能繼續運行
}
如果IDirectDraw::SetCooperativeLevel不返回DD_OK,你仍然可以運行該程序,但不是全屏模式,有時可能產生一些無法預料的錯誤。因此你應該顯示一條錯誤信息,讓用戶知道發生了什麼事,由用戶來決定是否繼續運行遊戲。
使用IDirectDraw::SetCooperativeLevel時,必須向窗口 (HWND)傳送一個句柄,讓窗口決定何時非正常地終止應用程序。例如,若發生了GP錯誤或GDI被翻轉(flip)到了後臺緩衝區,用戶就無法訪問當前屏幕。爲了避免這種情況,DirectDraw有一個後臺等待進程,它俘獲所有發往該窗口的消息,用這些消息來確定應用程序何時終止。如果創建了新的窗口,必須確定該窗口爲活動的,否則,就會有一系列的事件無法繼續工作。
2.4 改變顯示模式
一旦選擇了應用程序的工作模式,就可以使用IDirectDraw::SetDisplayMode方法來改變顯示模式,下面的代碼將顯示模式設置成640x480x256:
HRESULT ddrval;
LPDIRECTDRAW lpDD; // already created
ddrval = lpDD->SetDisplayMode( 640, 480, 8 );
if( ddrval == DD_OK )
{
// 改變模式成功
}
else
{
// 顯示模式不能改變
// 系統可能不支持該模式
}
當設定顯示模式時,應該確保如果用戶的設備不支持更高的分辨率,應用程序應該返回系統支持的標準模式。如果顯示示配卡不支持設計的分辨率,IDirectDraw::SetDisplayMode返回一個DDERR_INVALIDMODE錯誤值。因此,在設置分辨率時,應該先用IDirectDraw::EnumDisplayMode方法檢測用戶的顯示設備的性能。
2.5 創建可翻轉表面(Flippable Surface)
設定了顯示模式後,必須創建放置應用程序的表面。在DDEX1例中,我們使用IDirectDraw::SetCooperativeLevel方法將程序設成獨佔全屏模式,然後可以創建翻轉表面。如果使用IDirectDraw::SetCooperativeLevel設成DDSCL_NORMAL模式,就只能創建塊寫方式的表面了。
2.6 定義表面要求
創建可翻轉表面的的第一步是在DDSURFACEDESC結構中定義表面的要求。下面的代碼描述了結構的定義及創建可翻轉表面所需要的標誌:
// 創建帶有一個後臺緩衝區的主表面
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
例中,成員變量dwSize被設爲DDSURFACEDESC結構的大小。dwFlags標誌指定DDSURFACEDESC結構中哪些域可存放有效信息。
在DDEX1例中,dwFlags指出了需要使用DDSCAPS結構(DDSD_CAPS)並創建一個後臺緩衝區
(DDSD_BACKBUFFERCOUNT)。dwCaps指出 DDSCAPS結構會用到的標誌,本例中它指定了一個主表面(DDSCAPS_PRIMARYSURFACE),一個翻轉表面(DDSCAPS_FLIP)和一個複雜表面(DDSCAPS_COMPLEX)。
  最後,程序指定了一個後臺緩衝區。後臺緩衝區是背景圖像和人物將寫入的位置,它可以轉化爲主表面。
本例中,後臺緩衝區的數目爲1,事實上,只要有足夠的顯示內存,可以創建任意多個後臺緩衝區,一般每1M的顯示內存只能用來創建一個後臺緩衝區。表面的內存既可以是顯示內存,也可以是系統內存。
DirectDraw在使用完了顯示內存時(例如在僅有1M的顯示內存創建了2個後臺緩衝區)會自動使用系統內存。你可以通過將DDSCAPS結構中的dwCaps設定爲DDSCAPS_SYSTEMMEMORY或DDSCAPS_VIDEOMEMORY來指定只使用系統內存或只使用顯示內存。如果指定了DDSCAPS_VIDEOMEMORY又沒有足夠的顯示內存來創建表面,IDirectDraw::CreateSurface
將返回一個DDERR_OUTOFVIDEOMEMORY錯誤。
2.7 創建表面
填完了DDSURFACEDESC結構,就可以使用該結構和lpDD了,lpDD是用DirectDrawCreate
方法創建的DirectDraw 對象的指針,下面的代碼顯示了這一過程:
ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL);
if( ddrval == DD_OK )
{
// lpDDSPrimary points to new surface
}
else
{
// surface was not created
return FALSE;
}
如果調用成功,IDirectDraw::CreateSurface函數就返回指向主表面的指針lpDDSPrimary。若主表面指針可用,就可以調用IDirectDrawSurface::GetAttachedSurface 方法取得後臺緩衝區的指針,如下所示:
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = lpDDSPrimary->GetAttachedSurface( &ddcaps, &lpDDSBack);
if( ddrval == DD_OK )
{
// lpDDSBack points to the back buffer
}
else
{
return FALSE;
}
如果IDirectDrawSurface::GetAttachedSurface調用成功,通過提供主表面的地址和設置DDSCAPS_BACKBUFFER標誌,lpDDSBack 參數就指向後臺緩衝區。
2.8 着色表面
創建了主表面和後臺緩衝區後,DDEX1使用標準的WindowsGDI 函數將一些文本提交到主表面和後臺緩衝區,代碼如下:
if (lpDDSPrimary->GetDC(&hdc) == DD_OK)
{
SetBkColor( hdc, RGB( 0, 0, 255 ) );
SetTextColor( hdc, RGB( 255, 255, 0 ) );
TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg));
lpDDSPrimary->ReleaseDC(hdc);
}
if (lpDDSBack->GetDC(&hdc) == DD_OK)
{
SetBkColor( hdc, RGB( 0, 0, 255 ) );
SetTextColor( hdc, RGB( 255, 255, 0 ) );
TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg));
lpDDSBack->ReleaseDC(hdc);
}
例中使用了IDirectDrawSurface::GetDC方法來設備上下文的句柄且鎖定該表面。如果不想使用要求句柄的Windows函數,還可以使用IDirectDrawSurface::Lock和IDirectDrawSurface::Unlock方法來鎖定和解鎖後臺緩衝區。鎖定表面的內存(不管是整個表面還是其中的一部分)能確保你的應用程序和系統不會同時訪問這塊內存。另外,除非給內存解鎖,程序不能翻轉表面。本例在鎖定表面後使用Windows GDI 函數SetBkColor來設置背景顏色,使用SetTextColor來設置文本顏色,然後使用TextOut將文本輸出到表面。當文本寫入緩衝區後,例中使用了IDirectDrawSurface::ReleaseDC方法來解鎖表面並釋放句柄。良好的習慣是,向後臺緩衝區寫數據完成後,馬上調用IDirectDrawSurface::ReleaseDC或IDirectDrawSurface::Unlock。
一般來講,當向表面寫數據時,該表面就是後臺緩衝區,然後將緩衝區翻轉成主表面顯示出來。在DDEX1中,第一次翻轉表面之前有一個重要的延遲。於是DDEX1就將數據寫入主緩衝區,避免開始顯示時有太長的時間間隔。後面將會講到,DDEX1只在WM_TIMER期間向後臺寫數據。初始化函數或標題頭可能會寫入主緩衝區。應該注意的是,一旦使用IDirectDrawSurface::Unlock對錶面解鎖,指向表面的指針就變成無效,必須再次使用IDirectDrawSurface::Lock方法才能獲取該表面內存的有效指針。
2.9 寫表面及翻轉表面
完成了初始化後,DDEX1開始處理消息循環。在消息循環的過程中,完成鎖定後臺緩衝區——寫入新的文本——解鎖後臺緩衝區——翻轉表面的過程。WM_TIMER包含了寫數據和翻轉表面的大部分代碼。
WM_TIMER消息的前半部分用於向後臺緩衝區寫數據,“phase”變量決定是寫主緩衝區消息還是寫後臺緩衝區消息。如果phase爲1,表示寫主緩衝區的消息,然後將phase改變爲0;若爲0,表示寫後臺緩衝區的消息,然後將phase改變爲1。注意,兩種情況中的消息都是寫向後臺緩衝區。後臺緩衝區寫入了消息後,使用IDirectDrawSurface::ReleaseDC方法解鎖。下面的代碼實現了這一點:
case WM_TIMER:
// Flip surfaces
if( bActive )
{
if (lpDDSBack->GetDC(&hdc)== DD_OK)
{
SetBkColor( hdc, RGB( 0, 0, 255 ) );
SetTextColor( hdc, RGB( 255, 255, 0 ) );
if( phase )
{
TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) );
phase = 0;
}
else
{
TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) );
phase = 1;
}
lpDDSBack->ReleaseDC(hdc);
}
表面內存解鎖後,使用IDirectDrawSurface::Flip方法將後臺緩衝區翻轉成主表面,代碼如下:
while( 1 )
{
HRESULT ddrval;
ddrval = lpDDSPrimary->Flip( NULL, 0 );
if( ddrval == DD_OK )
{
break;
}
if( ddrval == DDERR_SURFACELOST )
{
if(ddrval = lpDDSPrimary-&g>val != DD_OK )
{
break;
}
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
break;
}
}
例中,lpDDSPrimary指明瞭主表面及其後臺緩衝區。調用IDirectDrawSurface::Flip方法後,主表面和後表面交換。調用成功後,返回DD_OK,程序終止While循環;如果返回DDERR_SURFACELOST,表明可能是表面丟失,需要用IDirectDrawSurface::Restore方法恢復該表面,若恢復成功,就再一次調用IDirectDrawSurface::Flip方法;如果失敗,程序終止While循環並返回一個錯誤值。另外,如前所述,即使調用IDirectDrawSurface::Flip成功,交換也不是立即完成,它將等到系統中在此之前的表面交換都完成後才進行。例如,前一次的表面翻轉還未發生時,IDirectDrawSurface::Flip就返回DERR_WASSTILLDRAWING。本例中,IDirectDrawSurface::Flip繼續循環直到返回DD_OK.。
2.10 釋放DirectDraw 對象
當按了F12後,DDEX1程序在退出之前先處理WM_DESTROY消息,該消息調用了finiObjects函數,而finiObjects函數包含了所有的Iunknown Release的調用,代碼如下:
static void finiObjects( void )
{
if( lpDD != NULL )
{
if( lpDDSPrimary != NULL)
{
lpDDSPrimary->Release();
lpDDSPrimary = NULL;
}
lpDD->Release();
lpDD = NULL;
}
} /* finiObjects */
程序檢測DirectDraw對象的指針(lpDD)和DirectDrawSurface對象的指針(lpDDSPrimary)
是否等於NULL,本例中顯然不爲NULL。然後DDEX1調用 IDirectDrawSurface::Release方法將DirectDrawSurface對象的參考值減1,這將會使得其參考值變爲0,DirectDrawSurface 對象就被釋放了,DirectDrawSurface的指針被設爲NULL值,然後撤消。程序又調用IDirectDraw::Release就DirectDraw對象的參考值減1變爲0,釋放DirectDraw對象及其指針。
上述的DDEX1是DirectDraw最基本的應用,它首先創建DirectDraw對象和DirectDrawSurface對象,創建一個主表面及其後臺緩衝區,將文本輸出到後臺緩衝區,然後轉化表面。第二個例子DDEX2擴展了DDEX1的功能,它可以將一個位圖文件調入後臺緩衝區。第三個例子DDEX3則更進一步,除了一個主表面及後臺緩衝區外,還創建了兩個屏外表面,將位圖調入每一個屏外表面,然後使用IDirectDrawSurface::BltFast方法將一個屏外表面的內容位塊傳輸到後臺緩衝區,然後翻轉表面並將另一個屏外表面的內容位塊傳輸到後臺緩衝區。下面將詳細討論這些功能。
2.11 將位圖調入表面
如DDEX1中一樣,doInit函數是DDEX2的初始化函數,兩者的實質一樣,一直到下面的代碼:
lpDDPal = DDLoadPalette(lpDD, szBackground);
if (lpDDPal == NULL)
goto error;
ddrval = lpDDSPrimary->SetPalette(lpDDPal);
if( ddrval != DD_OK )
goto error;
// load a bitmap into the back buffer.
ddrval = DDReLoadBitmap(lpDDSBack, szBackground);
if( ddrval != DD_OK )
goto error;
代碼的第一行從函數DDLoadPalette返回一個值,該函數在C:\DX5SDK\SDK\SAMPLES\MISC中的Ddutil.cpp文件中,因此編譯DDEX2時需要將Ddutil.cpp和Ddutil.h加入過程。大部分的DirectDraw程序都需要該文件。在DDEX2中,DDLoadPalette函數從Back.bmp文件中創建一個DirectDrawPalette對象。DDLoadPalette函數首先檢查用於創建調色板的文件或資源十分存在,如果不存在,就創建一個缺省調色板。在DDEX2中,它從位圖文件中抽取調色板信息並存儲在由ape指向的結構,然後如下創建DirectDrawPalette對象:
pdd->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL);
return ddpal;
當IDirectDraw::CreatePalette方法返回時,ddpal就指向該DirectDrawPalette對象。ape是指向一個結構的指針,該結構能包含2/4/16/256個線性的實體,實體的數目由IDirectDraw::CreatePalette調用的dwFlags參數決定。本例中,dwFlags設定爲DDPCAPS_8BIT,這表示該結構中有256個實體,每個實體有四個字節(紅色、綠色、藍色和標誌字節)。
2.12 設置調色板,將位圖調入後臺緩衝區
創建了調色板之後,可以通過調用IDirectDrawSurface::SetPalette方法將DirectDrawPalette 對象的指針ddpal傳送給主表面,代碼如下:
ddrval = lpDDSPrimary->SetPalette(lpDDPal);
if( ddrval != DD_OK )
// SetPalette failed
調用了IDirectDrawSurface::SetPalette方法之後,DirectDrawPalette對象就和DirectDrawSurface對象掛(hook)在一起了,需要改變調色板時,只需要創建一個新的調色板對其進行設置就可以了。
  DirectDrawPalette對象同DirectDrawSurface對象掛在一起後,DDEX2使用以下代碼將Back.bmp文件裝入後臺緩衝區:
// load a bitmap into the back buffer.
ddrval = DDReLoadBitmap(lpDDSBack, szBackground);
if( ddrval != DD_OK )
// Load failed
DDReLoadBitmap是Ddutil.cpp中的另一個函數,它將位圖從文件或資源中調入已經存在的DirectDraw表面。在本例中,它將szBackground指向的Back.bmp裝入lpDDSBack指向的後臺緩衝區。DDReLoadBitmap調用DDCopyBitmap函數將文件拷貝到後臺緩衝區並延展爲適當的尺寸。DDCopyBitmap函數將位圖拷入內存,使用GetObject函數獲取位圖的大小,然後用下述代碼獲取將要放置位圖的後臺緩衝區的大小:
// get size of surface.
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH;
pdds->GetSurfaceDesc(&ddsd);
  ddsd 是指向DDSURFACEDESC結構的指針,該結構儲存了DirectDraw表面的當前描述。DDSURFACEDESC結構的成員描述了由DDSD_HEIGHT 和DDSD_WIDTH指定了表面的高和寬。IDirectDrawSurface::GetSurfaceDesc方法使用合適的值調入結構,例中高爲480,寬爲640。DDCopyBitmap函數鎖定表面並將位圖拷貝到後臺緩衝區,然後用StretchBlt函數對位圖進行拉伸或壓縮,代碼如下:
if ((hr = pdds->GetDC(&hdc)) == DD_OK)
{
StretchBlt(hdc, 0, 0, ddsd.dwWidth,ddsd.dwHeight, hdcImage, x, y, dx, dy, SRCCOPY);
pdds->ReleaseDC(hdc);
}
2.13 從屏外表面位塊傳輸
DDEX2同DDEX1基本相同。DDEX2打開一個位圖文件並將它送往後臺緩衝區,然後翻轉後臺緩衝區和主表面。但這對顯示位圖並不特別理想,DDEX3擴展了DDEX2的功能,它加入了兩個屏外緩衝區,每個緩衝區都存儲一個位圖。下面是DDEX3中的doInit函數的一部分,功能是創建兩個屏外緩衝區:
// Create an offscreen bitmap.
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwHeight = 480;
ddsd.dwWidth = 640;
ddrval = lpDD->CreateSurface( &ddsd, &lpDDSOne, NULL);
if( ddrval != DD_OK )
{
return initFail(hwnd);
}
// Create another offscreen bitmap.
ddrval = lpDD->CreateSurface( &ddsd, &lpDDSTwo, NULL);
if( ddrval != DD_OK )
{
return initFail(hwnd);
}
從代碼中可以看到,dwFlags指明瞭程序使用DDSCAPS結構,並設置緩衝區的高和寬。由DDSCAPS結構中的DDSCAPS_OFFSCREEN標誌指定該表面是屏外緩衝區,在DDSURFACEDESC結構中將高和寬設爲480和640,然後使用IDirectDraw::CreateSurface方法來創建表面。因爲兩個屏外表面的大小一樣,創建第二個緩衝區只需要再運行一次IDirectDraw::CreateSurface即可(當然要用不同的指針)。你還可以在DDSCAPS中設置DDSCAPS_SYSTEMMEMORY或DDSCAPS_VIDEOMEMORY 來指定屏外緩衝區放在顯示內存還是系統內存。將位圖存放在顯示內存可以加快後臺緩衝區與屏外表面之間的數據傳輸速度,這在位圖動畫中非常重要。但是,如果你爲屏外緩衝區指定了DDSCAPS_VIDEOMEMORY又沒有足夠的顯示內存調入整個位圖,當創建該表面時,程序就會返回一個DDERR_OUTOFVIDEOMEMORY的錯誤值。
2.14 將位圖文件調入屏外表面
創建了兩個屏外表面後,DDEX3使用了InitSurfaces函數將位圖從Frntback.bmp文件裝入到兩個表面。InitSurfaces函數使用了DDCopyBitmap函數調入兩個位圖,代碼如下:
// Load our bitmap resource.
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap,
IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
if (hbm == NULL)
return FALSE;
DDCopyBitmap(lpDDSOne, hbm, 0, 0, 640, 480);
DDCopyBitmap(lpDDSTwo, hbm, 0, 480, 640, 480);
DeleteObject(hbm);
return TRUE;
   Frntback.bmp文件由兩部分組成,一半在上,一半在下。DDCopyBitmap函數將上半部分調入第一個屏外表面lpDDSOne,下半部分調入第二個表面lpDDSTwo。
2.15 將屏外表面位位塊傳輸到後臺緩衝區
WM_TIMER包含了寫表面和翻轉表面的代碼。在DDEX3中,它選擇適當的屏外表面,並將其位塊傳輸到後臺緩衝區,代碼如下:
rcRect.left = 0;
rcRect.top = 0;
rcRect.right = 640;
rcRect.bottom = 480;
if(phase)
{
pdds = lpDDSTwo;
phase = 0;
}
else {
pdds = lpDDSOne;
phase = 1;
}
while( 1 ) {
ddrval = lpDDSBack->BltFast(0, 0, pdds, &rcRect, FALSE );
if( ddrval ==DD_OK )
{
break;
}
“phase”決定了準備將哪一個屏外表面位塊傳輸到後臺緩衝區,然後調用IDirectDrawSurface::BltFast方法將選定的屏外表面位塊傳輸到後臺緩衝區,從左上角的位置0,0開始。rcRect指向定義了屏外表面的左上角和右下角的RECT結構。最後一個參數設爲FALSE或0,指明不使用特殊的傳輸標誌。一旦屏外表面被傳送到後臺緩衝區,就可以利用前面的方法將後臺緩衝區和主表面相互翻轉了。

三、創建動畫
上面的例子都只是將數據寫入後臺緩衝區,然後將後臺緩衝區與主表面翻轉,其速度並不太快。下面的例子DDEX4和DDEX5優化了實時功能,使看起來更象一個真正的遊戲。DDEX4顯示了怎樣爲表面設置Color key,怎樣使用IDirectDrawSurface::BltFast方法將屏外表面各部分拷貝到後臺緩衝區以產生動畫。DDEX5加入了讀取調色板並在動畫運行時改變調色板的功能。
3.1 Color Key和位圖動畫
在DDEX3例中描述了將位圖放入屏外緩衝區的一種主要方式。DDEX4則使用了將背景和一系列的精靈(sprite,本例中精靈是圓環)裝入屏外表面的技術,然後使用,IDirectDrawSurface::BltFast方法將屏外表面的各部分拷貝到後臺緩衝區。doInit函數除了具有前面例子中的功能外,還包括了爲精靈設置Color key的代碼。Color key是用於設置透明度的顏色值。當使用硬件塊寫方式時,矩型區域內除了設爲color key的像素,其它的像素都被塊寫,由此在表面上產生非矩型的精靈。設置color key的代碼如下:
// Set the color key for this bitmap (black)
// NOTE this bitmap has black as entry 255 in the color table.
// ddck.dwColorSpaceLowValue = 0xff;
// ddck.dwColorSpaceHighValue = 0xff;
// lpDDSOne->SetColorKey( DDCKEY_SRCBLT, &ddck);
// if we did not want to hard code the palette index (0xff)
// we can also set the color key like so...
DDSetColorKey(lpDDSOne, RGB(0,0,0));
return TRUE;
例中給出了設置color key的兩種不同方法。第一種方法是註釋內的3行,先設定DDCOLORKEY
結構中color key的範圍,再調用IDirectDrawSurface::SetColorKey方法將color key 設置成黑色(假定位圖在顏色表中以黑色作爲調色板索引項255)。第二種方法是調用DDSetColorKey函數設置顏色的RGB值來選擇color key,黑色就是RGB(0,0,0)。DDSetColorKey 函數調用了DDColorMatch函數,DDColorMatch存儲放置於lpDDSOne表面的位圖的0,0位置像素的顏色值,然後用提供的RGB值賦給0,0位置的像素,並將該顏色值屏蔽。
  完成了這一步驟後,原來的顏色就可重新放回0,0處並用正確的Color Key調用DDSetColorKey函數,調用成功後,color key就放入DDCOLORKEY 結構中的成員變量dwColorSpaceLowValue ,同時也拷貝到dwColorSpaceHighValue成員,然後再調用IDirectDrawSurface::SetColorKey設置Color key。 CLR_INVALID是DDSetColorKey 和DDColorMatch函數中另一個有用的變量。如果在DDSetColorKey中以該值作爲color key,位圖左上角的像素就會作爲color key使用。要想實現這一功能,需要調入位圖文件All.bmp,將0,0處的像素值該爲黑色,保存更改,然後如下改變對DDSetColorKey的調用: DDSetColorKey(lpDDSOne, CLR_INVALID);重新編譯DDEX4,DDEX4就會使用0,0處的像素值作爲color key了。
3.2 DDEX4中的動畫
DDEX4利用All.bmp中的紅色圓環調用updateFrame 函數來創建一個簡單的動畫。動畫由圓環的3個位置組成。例子通過比較Win32中的GetTickCount和上次該函數開始運行的時間來判斷是否重畫哪個圓環,然後使用IDirectDrawSurface::BltFast方法將背景從屏外表面lpDDSOne位塊傳輸到後臺緩衝區,然後再使用已經設定好的color key將圓環塊寫入後臺緩衝區。在所有的圓環都塊寫到後臺緩衝區後,調用IDirectDrawSurface::Flip方法翻轉後臺緩衝區和主表面。
3.3 動態改變調色板
DDEX5描述了任何在程序運行時動態地改變調色板,儘管在遊戲中這並不總是用到。DirectDraw確實能很好地控制調色板。DDEX5中的下述代碼將All.bmp文件的下半部分中的調色板裝入:
// First, set all colors as unused
for(i=0; i<256; i++)
{
torusColors[i] = 0;
}
// lock the surface and scan the lower part (the torus area)
// and remember all the index's we find.
ddsd.dwSize = sizeof(ddsd);
while (lpDDSOne->Lock(NULL, &ddsd, 0, NULL) == DDERR_WASSTILLDRAWING);
// Now search through the torus frames and mark used colors
for( y=480; y<480+384; y++ ){
for( x=0; x<640; x++ )
{
torusColors[((BYTE*)ddsd.lpSurface)[y*ddsd.lPitch+x]] = 1;
}
}
lpDDSOne->Unlock(NULL);
  數組torusColors用於指定All.bmp中的下半部分調色板的索引值,數組在使用之前都初始化爲0。然後鎖定屏外表面來檢測某顏色索引值是否已用。數組torusColors開始於位圖的第0行第480列,數組中的顏色索引值由位圖表面放置於內存的位置的一個字節決定,該位置由DDSURFACEDESC 結構中的lpSurface成員變量來決定,lpSurface指向對應於位圖(0,480)處的內存地址(y*lPitch+x)。數組中設定的顏色索引值用來檢測調色板中哪些顏色被替換。因爲背景和紅色圓環之間沒有公用的顏色,所以只有那些同圓環聯在一起的顏色值纔會被替換。
3.4 替換調色板
DDEX5中的updateFrame函數同DDEX4中的基本相同,先將背景塊寫入後臺緩衝區,再將3個紅色圓環塊寫到前景。但在翻轉表面之前,updateFrame用doInit函數創建的調色板索引值來改變主表面的調色板,代碼如下:
// Change the palette
if(lpDDPal->GetEntries( 0, 0, 256, pe ) != DD_OK)
{
return;
}
for(i=1; i<256; i++)
{
if(!torusColors[i])
{
continue;
}
pe[i].peRed = (pe[i].peRed+2) % 256;
pe[i].peGreen = (pe[i].peGreen+1) %256;
pe[i].peBlue = (pe[i].peBlue+3) % 256;
}
if(lpDDPal->SetEntries( 0, 0, 256, pe) != DD_OK)
{
return;
} IDirectDrawPalette::GetEntries方法在DirectDrawPalette對象中查詢調色板的值,因爲pe指向的調色板實體的值有效,方法就返回DD_OK,程序繼續運行。然後循環檢測torusColors在初始化中是否被設爲1,如果索引值被設爲1,由pe指向的調色板的紅色、綠色、藍色的值就被替換。在所有的被標記的調色板實體替換完畢後,再調用IDirectDrawPalette::SetEntries方法來真正改變DirectDrawPalette中的實體。如果該調色板已經設給主表面,上面的改變就會立即完成。完成了這一工作,剩下的就是同前面一樣的翻轉表面了。

四 使用覆蓋表面
  本例將使用DirectX SDK包含的Mosquito範例程序一步一步地說明怎樣在程序中使用DirectDraw和硬件支持的覆蓋表面。Mosquito使用覆蓋表面的翻轉鏈而沒有位塊傳輸到主表面將運動位圖顯示在桌面上。Mosquito程序調整覆蓋表面的特徵以適應硬件的限制。
4.1 創建一個主表面
要使用覆蓋表面,必須先要初始化一個主表面,覆蓋表面將顯示在該主表面上。Mosquito用如下代碼創建了一個主表面:
// Zero-out the structure and set the dwSize member.
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
// Set flags and create a primary surface.
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
ddrval = g_lpdd->CreateSurface(&ddsd, &g_lpddsPrimary,NULL );

程序先初始化將要使用的DDSURFACEDESC結構,然後設定適當的標誌調用IDirectDraw2::CreateSurface方法創建主表面。在對該方法的調用中,第一個參數是描述將要創建的表面的DDSURFACEDESC結構的指針;第二個參數是一個變量的指針,如果調用成功,該變量將接收IDirectDrawSurface接口的指針;第三個參數設爲NULL表明沒有COM集合。
4.2 檢測硬件對覆蓋的支持
初始化DirectDraw後,需要檢測設備是否支持覆蓋表面。因爲DirectDraw不能仿真覆蓋,所以如果硬件不支持覆蓋,就不能繼續下面的工作。你可以用IDirectDraw2::GetCaps方法獲取硬件設備驅動程序的能力檢測覆蓋支持。在調用該方法之後,查看DDCAPS結構中的dwFlags成員是否包含有DDCAPS_OVERLAY標誌。若有就表明支持覆蓋,否則就不支持。
下面的代碼是Mosquito程序中的一部分,它表明了怎樣檢測硬件的覆蓋支持能力:
BOOL AreOverlaysSupported()
{
DDCAPS capsDrv;
HRESULT ddrval;
// Get driver capabilities to determine Overlay support.
ZeroMemory(&capsDrv, sizeof(capsDrv));
capsDrv.dwSize = sizeof(capsDrv);
ddrval = g_lpdd->GetCaps(&capsDrv, NULL);
if (FAILED(ddrval))
return FALSE;
// Does the driver support overlays in the current mode?
// (Currently the DirectDraw emulation layer does not support overlays.
// Overlay related APIs will fail without hardware support).
if (!(capsDrv.dwCaps & DDCAPS_OVERLAY))
return FALSE;
return TRUE;
}
程序首先調用IDirectDraw2::GetCaps方法獲取設備驅動程序的能力。第一個參數是DDCAPS結構的地址指針;因爲程序不需要關仿真的信息,所以第二個參數就設爲NULL。獲取驅動程序的能力後,程序使用了邏輯“與”來檢查dwFlags成員是否包含有DDCAPS_OVERLAY標誌。若否,程序返回FALSE表明失敗。若是,就返回TRUE表明顯示設備支持覆蓋表面。
4.3 創建一個覆蓋表面
如果知道顯示設備支持覆蓋表面,就可以創建一個。因爲沒有指明設備怎樣支持覆蓋表面的標準,所以不能夠期望創建任意大小的像素格式的表面。另外,也不要期望第一次創建覆蓋表面就會成功。因此,必須作好準備進行多次創建的嘗試,直到有一個能夠工作爲止。
Mosquito程序在創建表面時遵循“best case to worst case”的原則,首先嚐試創建一個三緩衝頁翻轉複雜覆蓋表面。如果嘗試失敗,程序就改變方法嘗試用其它通用的迅速格式來配置。下面的代碼就是這一思路的表現:
ZeroMemory(&ddsdOverlay, sizeof(ddsdOverlay));
ddsdOverlay.dwSize = sizeof(ddsdOverlay);
ddsdOverlay.dwFlags= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH|DDSD_BACKBUFFERCOUNT| DDSD_PIXELFORMAT;
ddsdOverlay.ddsCaps.dwCaps = DDSCAPS_OVERLAY | DDSCAPS_FLIP|DDSCAPS_COMPLEX | DDSCAPS_VIDEOMEMORY;
ddsdOverlay.dwWidth =320;
ddsdOverlay.dwHeight =240;
ddsdOverlay.dwBackBufferCount=2;
// Try to create an overlay surface using one of the pixel formats in our
// global list.
i=0;
do{
ddsdOverlay.ddpfPixelFormat=g_ddpfOverlayFormats[i];
// Try to create the overlay surface
ddrval = g_lpdd->CreateSurface(&ddsdOverlay,&g_lpddsOverlay, NULL);
} while( FAILED(ddrval) && (++i < NUM_OVERLAY_FORMATS));

程序設置DDSURFACEDESC結構中的標誌和值以反映三緩衝頁翻轉複雜覆蓋表面,然後執行循環。在循環中,程序嘗試用各種常用的像素格式創建要求的表面。如果嘗試成功,循環就終止。如果嘗試失敗,說明很有可能是顯示硬件沒有足夠的顯示內存支持三緩衝的方案或者硬件根本就不支持翻轉覆蓋表面。在這種情況下,在最小要求的配置下使用一個單一的非翻轉覆蓋表面,代碼如下:
// If we failed to create a triple buffered complex overlay surface, try
// again with a single non-flippable buffer.
if(FAILED(ddrval))
{
ddsdOverlay.dwBackBufferCount=0;
ddsdOverlay.ddsCaps.dwCaps=DDSCAPS_OVERLAY| DDSCAPS_VIDEOMEMORY;
ddsdOverlay.dwFlags= DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH|DDSD_PIXELFORMAT;
// Try to create the overlay surface
ddrval = g_lpdd->CreateSurface(&ddsdOverlay,&g_lpddsOverlay, NULL);
i=0;
do
{
ddsdOverlay.ddpfPixelFormat=g_ddpfOverlayFormats[i];
ddrval = g_lpdd->CreateSurface(&ddsdOverlay, &g_lpddsOverlay, NULL);
} while( FAILED(ddrval)
&& (++i < NUM_OVERLAY_FORMATS) );
// We couldn't create an overlay surface. Exit, returning failure.
if (FAILED(ddrval))
return FALSE;
}

  上面的代碼對DDSURFACEDESC結構中的標誌和值復原來反映一個單一的非翻轉覆蓋表面,然後通過像素格式的循環嘗試創建表面。如果創建表面成功,循環就停止。如果不成功,程序返回FALSE表明創建表面失敗。在成功地創建覆蓋表面之後,就可將位圖裝入其中以供顯示。
4.4 顯示覆蓋表面
創建了覆蓋表面之後就可以顯示它了。通常,硬件在用於顯示覆蓋的矩形的位置和像素格式上加上對齊約束。另外,還需要經常通過調整目的矩形的寬度來說明最小要求的拉伸因子以成功地顯示覆蓋表面。Mosquito程序按照以下的步驟準備和顯示覆蓋表面。

4.4.1、檢測顯示的最小要求
大部分的顯示硬件在顯示覆蓋時都會加上約束。你必須很仔細地調整覆蓋使之滿足這些約束。可以通過調用IDirectDraw2::GetCaps方法獲得有關這些約束的信息。該方法填充的結構DDCAPS包含了有關覆蓋能力和使用約束的信息。不同硬件的約束是不同的,因此必須始終要查看包含在dwFlags成員的標誌以確定附加的是哪一種約束。
  Mosquito程序開始時先獲取硬件的能力,然後採用基於最小拉伸因子的方法,如下所示:
// Get driver capabilities
ddrval = g_lpdd->GetCaps(&capsDrv, NULL);
if (FAILED(ddrval))
return FALSE;
// Check the minimum stretch and set the local variable accordingly.
if(capsDrv.dwCaps & DDCAPS_OVERLAYSTRETCH)
uStretchFactor1000 = (capsDrv.dwMinOverlayStretch>1000)
? capsDrv.dwMinOverlayStretch : 1000;
else
uStretchFactor1000 = 1000;

上面的代碼調用IDirectDraw2::GetCaps方法獲取硬件的能力。在本例中,第一個參數是DDCAPS結構的指針;第二個參數是NULL,表明了不需要獲取有關仿真的信息。程序在一個臨時變量中保留了最小拉伸因子以備以後之用。如果驅動程序報告出的拉伸因子大於1000,就表明驅動程序要求所有的目的矩形沿X軸的方向拉伸。例如,若拉伸因子是1.3,源矩形寬320個像素,目的矩形就必須至少要有416(320X1.3=416)個像素的寬。如果驅動程序報告出的拉伸因子小於1000,就表明驅動程序能夠顯示比源矩形小的覆蓋,但不能伸展覆蓋。
下面的代碼是測定描述驅動程序的大小對齊約束的值:
// Grab any alignment restrictions and set the local
variables acordingly.
uSrcSizeAlign = (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC)
?capsDrv.dwAlignSizeSrc:0;
uDestSizeAlign= (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC)
?capsDrv.dwAlignSizeDest:0;
例中使用了更多的臨時變量來保存從dwAlignSizeSrc和dwAlignSizeDest成員中獲得的大小對齊約束。這些值提供了有關像素寬度對齊約束的信息,並且在以後設定源矩形和目的矩形的大小時需要用到。源矩形和目的矩形必須是這些值的倍數。
最後,程序測定描述目的矩形邊界對齊約束的值:
// Set the "destination position alignment" global so we won't have to
// keep calling GetCaps() every time we move the overlay surface.
if (capsDrv.dwCaps & DDCAPS_ALIGNBOUNDARYDEST)
g_dwOverlayXPositionAlignment= capsDrv.dwAlignBoundaryDest;
else
g_dwOverlayXPositionAlignment= 0;
上面的代碼使用了一個全局變量來保存目的矩形邊界約束的值,該值是從dwAlignBoundaryDest成員中的得來的,在以後程序重新放置覆蓋時將會用到。你必須設定目的矩形左上角的X座標在像素格式上同該值對齊。也就是說,如果該值是4,就只能指定左上角的X座標爲0,4,8,12等像素寬的目的矩形。Mosquito程序首先在0,0處顯示覆蓋,於是在第一次顯示覆蓋之前就不需要獲取約束信息。但是因爲不同應用程序的實現過程可能不同,所以你可能需要在顯示覆蓋之前檢查這些信息以調整目的矩形。

4.4.2、設置源矩形和目的矩形
在獲得了驅動程序的覆蓋約束之後,就應該設定有關源矩形和目的矩形的值,確保能夠正確顯示覆蓋。下面的代碼就設定了源矩形的特徵:
// Set initial values in the source RECT.
rs.left=0; rs.top=0;
rs.right = 320;
rs.bottom = 240;
// Apply size alignment restrictions, if necessary.
if (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC &&uSrcSizeAlign)
rs.right -= rs.right % uSrcSizeAlign;

上面的代碼設置了包含整個表面大小的初始值。如果設備驅動程序要求大小對齊,程序就調整源矩形來保證。程序調整了源矩形的寬度使之比初始值要小,這是因爲如果不是完全重新創建表面,就不能夠擴展寬度。
在設定了源矩形的大小後,需要設置和調整目的矩形的大小。這一過程需要稍微多一點的工作,因爲目的矩形可能需要先拉伸再調整以符合大小對齊約束。下面的代碼根據最小拉伸因子來設置和調整目的矩形的大小:
// Set up the destination RECT, starting with the source RECT values.
// We use the source RECT dimensions instead of the surface dimensions in
// case they differ.
rd.left=0; rd.top=0;
rd.right = (rs.right*uStretchFactor1000+999)/1000;
// (Adding 999 avoids integer truncation problems.)
// (This isn't required by DDraw, but we'll stretch the
// height, too, to maintain aspect ratio).
rd.bottom = rs.bottom*uStretchFactor1000/1000;
前面的代碼先設置目的矩形的左上角位置,再根據最小拉伸因子設定目的矩形的寬度。根據拉伸因子調整矩形時,注意程序在寬度和拉伸因子的乘積上又加了999,這是爲了避免出現整數截斷,整數截斷會導致矩形同最小拉伸因子的要求不一致。程序在拉伸寬度後也拉伸了矩形的高度。不過,對高度的拉伸並不是必須的,這裏只是爲了保持位圖的長寬比率避免出現失真的現象。
拉伸目的矩形後,程序對其進行調整以保持和大小對齊約束一致,下面是相應的代碼:
// Adjust the destination RECT's width to comply with any imposed
// alignment restrictions.
if (capsDrv.dwCaps & DDCAPS_ALIGNSIZEDEST &&uDestSizeAlign)
rd.right = (int)((rd.right+uDestSizeAlign-1)/uDestSizeAlign)*uDestSizeAlign;

程序檢測硬件能力的標誌,查看驅動程序是否加了矩形大小對齊約束。如果是,就增加目的矩形的寬度使之滿足大小對齊約束。這裏對矩形的調整是擴展其寬度而不能減少其寬度,因爲減少寬度可能會導致目的矩形比最小拉伸因子要求的還要小,從而引起顯示覆蓋表面失敗。

4.4.3、顯示覆蓋表面
在設置了源矩形和目的矩形後,就可以顯示覆蓋了。如果顯示覆蓋之前的準備工作正確,顯示覆蓋會很簡單。Mosquito程序用如下的代碼來顯示覆蓋:
// Set the flags we'll send to UpdateOverlay
dwUpdateFlags = DDOVER_SHOW | DDOVER_DDFX;
// Does the overlay hardware support source color keying?
// If so, we can hide the black background around the image.
// This probably won't work with YUV formats
if (capsDrv.dwCKeyCaps & DDCKEYCAPS_SRCOVERLAY)
dwUpdateFlags |= DDOVER_KEYSRCOVERRIDE;
// Create an overlay FX structure so we can specify a source color key.
// This information is ignored if the DDOVER_SRCKEYOVERRIDE flag isn't set.
ZeroMemory(&ovfx, sizeof(ovfx));
ovfx.dwSize = sizeof(ovfx);
ovfx.dckSrcColorkey.dwColorSpaceLowValue=0; // Specify black as the color key
ovfx.dckSrcColorkey.dwColorSpaceHighValue=0;
// Call UpdateOverlay() to displays the overlay on the screen.
ddrval = g_lpddsOverlay->UpdateOverlay(&rs,
g_lpddsPrimary, &rd, dwUpdateFlags, &ovfx);
if(FAILED(ddrval))
return FALSE;

程序開始在臨時變量dwUpdateFlags中設定了DDOVER_SHOW和DDOVER_DDFX標誌,指明該覆蓋是第一次顯示,硬件應該使用包含在DDOVERLAYFX結構中的效果信息完成這一工作。然後,程序檢查DDCAPS結構確定覆蓋是否支持源Color Key。如果是,DDOVER_KEYSRCOVERRIDE標誌就包含在dwUpdateFlags變量中利用源Color Key,程序也據此設置Color Key。
準備工作完成之後,程序調用IDirectDrawSurface3::UpdateOverlay方法來顯示覆蓋。在對該方法的調用中,第一個參數和第三個參數是已調整的源矩形和目的矩形的地址。第二個參數是覆蓋顯示在其上的主表面的地址。第四個參數是包括了放置於此前準備的dwUpdateFlags變量中的標誌。第五個參數是DDOVERLAYFX結構的地址,該結構中的成員將設定同那些標誌相匹配。
如果硬件只支持一個覆蓋表面而且該表面正在使用,UpdateOverlay方法就會失敗,並返回DDERR_OUTOFCAPS。另外,有可能硬件報告出的最小拉伸因子過小,在UpdateOverlay方法失敗後,你就需要嘗試減少目的矩形的寬度來應付這種可能性。不過,這種情況很少發生,在Mosquito中也只是簡單地返回一個錯誤信息。
4.5 更新覆蓋的顯示位置
顯示覆蓋表面之後,有時可能就不需要對覆蓋左其它的操作了。但有些軟件還需要重新放置覆蓋,改變覆蓋的顯示位置。Mosquito程序就使用IDirectDrawSurface3::SetOverlayPosition方法重新放置覆蓋,代碼如下:
// Set X- and Y-coordinates
......
// We need to check for any alignment restrictions on the X position
// and align it if necessary.
if (g_dwOverlayXPositionAlignment)
dwXAligned = g_nOverlayXPos- g_nOverlayXPos % g_dwOverlayXPositionAlignment;
else
dwXAligned = g_nOverlayXPos;
// Set the overlay to its new position.
ddrval = g_lpddsOverlay->SetOverlayPosition(dwXAligned,g_nOverlayYPos);
if (ddrval == DDERR_SURFACELOST)
{
if (!RestoreAllSurfaces())
return;
}

程序開始對齊矩形以滿足可能存在的任何目的矩形邊界對齊約束。當程序此前調用IDirectDraw2::GetCaps方法時,全局變量g_dwOverlayXPositionAlignment已經設定爲同DDCAPS結構中dwAlignBoundaryDest成員所報告出的值相等。如果存在目的矩形約束,程序就據此調整新的X座標爲像素對齊的。若不滿足要求,覆蓋表面就不能顯示。
在完成了對X座標的調整之後,程序調用IDirectDrawSurface3::SetOverlayPosition方法重新放置覆蓋。在調用中第一個參數是對齊的新的X座標,第二個參數的新的Y座標。這些值表明了覆蓋左上角新的位置。這裏並不需要取得寬度和高度信息,因爲DirectDraw在開始用IDirectDrawSurface3::UpdateOverlay方法顯示覆蓋時就已經獲得了表面大小的信息。如果因爲一個或多個表面丟失而引起的重新放置覆蓋表面的失敗,Mosquito程序就調用一個應用定義的函數來恢復這些表面並重新裝入它們的位圖。
注意,不要使用太靠近目標表面的右、下邊界的座標。因爲IDirectDraw2::SetOverlayPosition方法並不執行剪切功能,所以使用那些可能導致覆蓋超出目標表面邊界的座標會引起調用的失敗,並返回DDERR_INVALIDPOSITION。
4.6 隱藏覆蓋表面
如果不再需要一個覆蓋表面或只想不讓覆蓋可見,就可以設定適當的標誌調用IDirectDrawSurface3::UpdateOverlay方法來隱藏該覆蓋表面。Mosquito用以下代碼隱藏覆蓋表面並準備關閉應用程序:
void DestroyOverlay()
{
if (g_lpddsOverlay){
// Use UpdateOverlay() with the DDOVER_HIDE flag to remove an overlay
// from the display.
g_lpddsOverlay->UpdateOverlay(NULL,g_lpddsPrimary, NULL, DDOVER_HIDE, NULL);
g_lpddsOverlay->Release();
g_lpddsOverlay=NULL;
}
}
在調用IDirectDrawSurface3::UpdateOverlay時,對源矩形和目的矩形指定了NULL,因爲在隱藏覆蓋的過程中不需要源矩形和目的矩形。
同理,第五個參數也被指定爲NULL是因爲不使用覆蓋效果。第二個參數是目標表面的指針。最後,程序在第四個參數使用DDOVER_HIDE標誌表明該覆蓋將從視口中取消。  程序在隱藏覆蓋之後,釋放了它的IDirectDrawSurface3接口,並且將全局變量設爲NULL使之變得無效。對於Mosquito程序來說,覆蓋就不再需要了。如果在應用程序中還需要使用該覆蓋,就只需簡單地隱藏覆蓋,而不要釋放它,然後在需要的時候再重新顯示。

五 DirectDraw中其它的DirectDraw範例
要熟練掌握有關DirectDraw的應用,還應該多研究包含在DirectX
SDK:之中的下述範例。
1、Stretch
描述了怎樣在一個窗口中創建一個非獨佔模式的動畫,它具有剪切位塊傳輸和拉伸剪切位塊傳輸的功能。
2、Donut
描述了多個獨佔模式的應用同非獨佔模式應用之間的交互。
3、Wormhole
該範例描述了詳細的調色板動畫。
4、Dxview
詳細描述了怎樣獲取顯示硬件的能力。
  其它的還有Duel、Iklowns、Foxbear、Palette和Flip2d等,只要多這些範例多加分析,掌握DirectX最基本的技術DirectDraw是不難的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章