DirectDraw顏色鍵和剪切處理

 


☆ 顏色鍵

顏色鍵使一個位圖被拷貝到另外一個位圖上時,不使全部的象素都顯現。例如:當你把一個精靈(遊戲中會動的對象通常都稱做精靈)拷貝到地圖上(背景上)時,這個精靈位圖通常不會是一個精靈形狀的位圖,它一般都是一個矩形位圖,位圖裏包含你所須要的精靈(除非你的精靈就是一個矩形機器人^_^),不使用顏色鍵拷貝的結果如圖一:

DirectDraw顏色鍵和剪切處理

【圖一】
這並非咱們想在遊戲中獲得的效果。遊戲中,這個精靈是不會有那個黑色的底框的。地圖是先於精靈顯示的,那麼精靈走到樹後時,還應有相應被遮擋的部分,這個先不討論,下一節再說。如今,對咱們更重要的是,若是不該用顏色鍵,這個精靈將永遠帶着這個黑色底框,這是絕對不能容忍的。
爲了解決這個問題,咱們使用源顏色鍵。這個源顏色鍵告訴你精靈矩形的哪些顏色將不被拷貝(固然咱們是讓黑色不被拷了^_^)。一個顏色鍵由兩個值組成:一個低位顏色值,一個高位顏色值。當一個顏色鍵被申請使用時,在兩個值之間的顏色,包括這兩個值的顏色都將不會被拷貝。在DirectX中有一個結構用來處理它,叫做DDCOLORKEY,看看吧:

typedef struct _DDCOLORKEY{
    DWORD dwColorSpaceLowValue;
    DWORD dwColorSpaceHighValue;
} DDCOLORKEY, FAR* LPDDCOLORKEY;


很簡單的結構,我就不解釋了。我將展現給你使用了顏色鍵以後的效果。我使用顏色鍵的高位和低位兩個值僅僅把黑色包括在它們之間。所以,黑色是惟一不會被拷貝的顏色。圖二就是使用顏色鍵的結果:

【圖二】
好多了,是否是?這就是咱們想獲得的結果!如今,在我告訴你怎樣創建和使用顏色鍵以前,我還有說一說目標顏色鍵,儘管咱們的確咱們不經常使用到它(咱們經常使用的是源顏色鍵)。鑑於源顏色鍵定義了哪些顏色鍵不能被拷貝,目標顏色鍵定義了哪些顏色不能被寫入(覆蓋)。聽起來很怪異,是否是?我也有同感。^_^ 舉個實例你就明白了。當你要把A位圖拷貝到B位圖的下面,意思就是把A位圖做爲背景,例如因爲某種理由,須要把一個文本框拷貝到空的後緩衝區,而後再把背景畫面拷貝到這個後緩衝區,但你又不能覆蓋先前的文本框。所以,在後緩衝區裏除了文本框的那些黑色的部分才能被寫入象素。看看圖三,真相大白:

【圖三】
我也不清楚你何時須要處理這種狀況,可是你的確可能用到(一旦你用到了,可千萬要告訴我哦,我一直沒有遇到這種狀況呢^_^)。如今,你已經知道什麼是顏色鍵了,讓咱們看看怎樣使用它們吧!

☆ 設置顏色鍵

在DirectDraw中有兩種方法使用顏色鍵。第一種,你能夠連接一個顏色鍵(或者兩個,若是你同時使用源和目標顏色鍵)到表面,而後在位塊傳輸時定義DDBLT_KEYSRC,DDBLT_KEYDEST,DDBLTFAST_SRCCOLORKEY或DDBLTFAST_DESTCOLORKEY標誌,具體使用哪一個標誌,取決於你使用哪一個位塊傳輸函數和使用哪一種顏色鍵。第二種,你能夠建立一個顏色鍵,而後經過DDBLTFX結構傳送給位塊傳輸操做。當你不斷地須要使用顏色鍵時,我向你推薦第一種方法;反之,當你偶然要使用一次顏色鍵,就用第二種方法吧!
你能夠把顏色鍵連接到已經創建好了的表面,也能夠在創建表面的同時創建顏色鍵。兩種方法我都將詳細告訴你。假設你工做在16-bit顯示模式下,是565象素格式,你要在後緩衝區使用一個僅包含黑色的源顏色鍵。若是你的後緩衝區已經創建好了,你就能夠簡單創建一個DDCOLORKEY結構,而後把它傳遞給IDirectDrawSurface7::SetColorKey()函數,以下所示:

HRESULT SetColorKey(
    DWORD dwFlags,
    LPDDCOLORKEY lpDDColorKey
);


記住要用FAILED()宏檢測這個函數的返回值,保證一切都在計劃之中。函數的參數很簡單:
※ DWORD dwFlags:決定所使用顏色鍵類型的標誌。如下三個是你將用到的:

◎ DDCKEY_COLORSPACE:該結構包含一個顏色範圍,若是結構包含的是單獨的顏色鍵則不做設置。
◎ DDCKEY_DESTBLT:該結構指定顏色鍵或者顏色範圍做爲用於位塊傳輸操做的目標顏色鍵。
◎ DDCKEY_SRCBLT:該結構指定顏色鍵或者顏色範圍做爲用於覆蓋操做的顏色鍵。

※ LPDDCOLORKEY lpDDColorKey:這是指向DDCOLORKEY結構的指針。

就這麼多。你能夠根據你所須要使用的顏色鍵適當地定義位塊傳輸的標誌。注意,一個顏色鍵連接到表面,並不意味着你每一次必須使用它。若是你只定義了DDBLT-WAIT或DDBLTFAST_WAIT標誌,顏色鍵將被忽略。下面是設置顏色鍵的方法:

DDCOLORKEY ckey;
ckey.dwColorSpaceLowValue = RGB_16BIT565(0, 0, 0); // or we could just say '0'
ckey.dwColorSpaceHighValue = RGB_16BIT565(0, 0, 0);
if (FAILED(lpddsBack->SetColorKey(DDCKEY_SRCBLT, &ckey)))
{
    // error-handling code here
}


若是你要爲已經創建的顏色鍵連接一個表面,有幾件事情你須要作。首先,當你定義DDSURFACEDESC2結構的有效成員時,你須要使dwFlags成員包含DDSD_CKSRCBLT或者DDSD_CKDESTBLT標誌,具體使用哪一個標誌,取決於你要使用哪一種顏色鍵。回頭再看看DDSURFACEDESC2結構,它包含兩種DDCOLORKEY結構。一種稱爲ddcdCKSrcBlt,另外一種稱爲ddcdCKDestBlt,填寫適當的結構來建立表面。你就須要幹這麼多!如下是關於640×480大小的離屏表面的實例代碼:

// set up surface description structure
INIT_DXSTRUCT(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CKSRCBLT;
ddsd.dwWidth = 640; // width of the surface
ddsd.dwHeight = 480; // and its height
ddsd.ddckCKSrcBlt.dwColorSpaceLowValue = RGB_16BIT(0,0,0); // color key low value
ddsd.ddckCKSrcBlt.dwColorSpaceHighValue = RGB_16BIT(0,0,0); // color key high value
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; // type of surface

// now create the surface
if (FAILED(lpdd7->CreateSurface(&ddsd, &lpddsBack, NULL)))
{
    // error-handling code here
}


關於顏色鍵的部分到此結束。如今咱們能夠進行本章最後一項了——剪切。

☆ IDirectDrawClipper接口

假設你有一個圖形,而你卻只想把它的一部分顯示在屏幕上。你應該怎樣作呢?若是你曾經在DOS下編寫過遊戲,你可能對剪切望而生畏。Well,在DirectX下,這只是小菜一碟!首先的首先,這的確是很容易作到的,由於DirectX用矩形來作位塊傳輸,改變矩形的座標要比指定內存中的哪一部分圖形被拷貝(就像在DOS下所作的)要容易的多。其次,DirectDraw還爲此提供了一個接口——IDirectDrawClipper。
DirectDraw的剪切性能徹底能夠知足你的要求,你不但能夠剪切矩形區域,你還能夠剪切任意多邊形區域!真的是很棒!若是你在屏幕上同時要顯示一個主窗口,在屏幕的一邊顯示一個狀態欄,在屏幕的底部顯示一個文字提示欄,而且用黑色分隔開這些區域,你能夠用DirectDraw的剪切功能完成它,很是容易的!
作這樣的操做你須要分幾步走。首先你得獲得一個指向IDirectDrawClipper接口的指針。沒什麼難的,只須要調用IDirectDraw7::CreateClipper(),以下:

HRESULT CreateClipper(
    DWORD dwFlags,
    LPDIRECTDRAWCLIPPER FAR *lplpDDClipper,
    IUnknown FAR *pUnkOuter
);


在你調用這個函數前,你應該首先聲明一個LPDIRECTDRAWCLIPPER類型的指針,這樣你才能把它的地址傳遞給這個函數。記着要檢測函數的調用是否成功哦!如下是函數參數的解釋:
※ DWORD dwFlags:簡直就是幸福——這個參數尚未使用過,設置爲0。^_^
※ LPDIRECTDRAWCLIPPER FAR *lplpDDClipper:把你的LPDIRECTDRAWCLIPPER指針的地址傳遞給它。
※ IUnknown FAR *pUnkOuter:你知道怎麼作,設置爲NULL。^_^

一旦你有了本身的接口指針,下一件事就是建立剪切列表(clip list)。多個剪切的矩形組成了剪切列表。須要使用到RGNDATA結構,它包含了足夠的信息來定義一個任意的區域,看看它的原形吧:

typedef struct _RGNDATA {
    RGNDATAHEADER rdh;
    char Buffer[1];
} RGNDATA;


我須要詳細解說一下它的參數。
※ RGNDATAHEADER rdh:它是RGNDATA結構中嵌套的一個結構。它包含了第二個參數——Buffer的全部信息。它定義了須要剪切區域裏的矩形的數目,整個區域的形狀等信息。咱們過一下子再具體討論它。
※ char Buffer[1]:這並不意味着是隻有一個值得數組;它將是在內存中任意大小的區域來控制着實際的剪切區域數據。一樣的,對於RGNDATA結構,咱們要聲明一個指向該結構的指針,而後使用malloc()函數爲RGNDATAHEADER設置足夠的內存空間,也就是爲剪切列表設置足夠的空間。有一件事我要提醒你:剪切列表裏的矩形按從上到下,而後從左到右排列,不能交迭。

我已經意識到你有些胡塗了,沒關係,繼續學習,一切會好起來的。下面是RGNDATAHEADER結構的原形,它比較好理解:

typedef struct _RGNDATAHEADER {
    DWORD dwSize;
    DWORD iType;
    DWORD nCount;
    DWORD nRgnSize;
    RECT rcBound;
} RGNDATAHEADER;


※ DWORD dwSize:結構的大小。簡單的使用sizeof(RGNDATAHEADER)好了。
※ DWORD iType:它描述了每一個區域的外形。它是另有玄機的,之後咱們再把它擴展開來討論。如今,你只要把它設置爲RDH_RECTANGLES就行了,這也正是咱們須要的。
※ DWORD nCount:這是組成該區域的矩形的數量。換句話說,就是你的剪切列表理的矩形數。
※ DWORD nRgnSize:爲緩衝區的大小設置它,將獲得自身的區域數據。因爲咱們使用n個矩形組成了剪切區域,因此它的大小應該是sizeof(RECT) *nCount。
※ DWORD rcBound:這是一個矩形類型,包含了剪切列表裏的全部矩形。一般你把它設置成表面上須要剪切部分的尺寸。

如今,咱們能夠創建一個剪切列表了。首先咱們聲明一個LPRGNDATA的指針,分配給剪切列表足夠的內存空間;而後根據上面咱們所學的設置每一個成員。讓咱們看看最簡單的實例,你可能常常要用到它哦!它只有一個剪切區域,而且,就是整個屏幕,是640×480顯示模式的。如下就是代碼:

// first set up the pointer -- we allocate enough memory for the RGNDATAHEADER
// along with one RECT. If we were using multiple clipping area, we would have
// to allocate more memory.
LPRGNDATA lpClipList = (LPRGNDATA)malloc(sizeof(RGNDATAHEADER) + sizeof(RECT));

// this is the RECT we want to clip to: the whole display area
RECT rcClipRect = {0, 0, 640, 480};

// now fill out all the structure fields
memcpy(lpClipList->Buffer, &rcClipRect, sizeof(RECT)); // copy the actual clip region
lpClipList->rdh.dwSize = sizeof(RGNDATAHEADER); // size of header structure
lpClipList->rdh.iType = RDH_RECTANGLES; // type of clip region
lpClipList->rdh.nCount = 1; // number of clip regions
lpClipList->rdh.nRgnSize = sizeof(RECT); // size of lpClipList->Buffer
lpClipList->rdh.rcBound = rcClipRect; // the bounding RECT

一旦有了剪切列表,你須要把它做爲你的剪裁板。你將調用IDirectDrawClipper接口的函數SetClipList()。就是下面這個東東:

HRESULT SetClipList(
    LPRGNDATA lpClipList,
    DWORD dwFlags
);


你所要作的就是把RGNDATA的指針傳遞給它。參數dwFlags沒有用,設置爲0。如今,剪切列表設置好了,還須要一步,就是把剪切列表連接到你所要控制的表面上,這須要調用SetClipper()函數,它將表面指針做爲其惟一的參數:

HRESULT SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper);

你知道應該怎樣作:就是把你已經設置好的接口的指針傳遞給它。任什麼時候候,你要位塊傳輸一個有剪裁板相關聯的表面,剪裁板將作全部的工做。因此若是你要在屏幕上顯示一個貼片的一部分,例如傳輸一個矩形座標爲{-10,-10,6,6},或者相似的矩形貼圖,都不會有麻煩的。很不錯吧,嗯?
關於剪切的最後一件事情是必需要用free()函數釋放你用malloc()設置的內存空間。還有,就是因爲某種緣由在調用SetClipList()或SetClipper()失敗後,在返回錯誤代碼前或你要根據失敗的結果進行操做前,要釋放內存空間。在你完成用LPRGNDATA的指針設置剪切列表後,這個指針就沒有存在的意義了,因此它佔用的內存空間將被當即釋放。

☆ 總結

到此,關於DirectDraw的部分就討論完了!你真的從這六章裏學到了不少的知識,若是你堅持到如今,那麼祝賀你,你真的已經走了很長一段路了。^_^ 對於這一章咱們所學習的,我將把它們組合在一塊兒,給你一個Demo程序,它將從資源調用位圖,使用位塊傳輸位圖,顏色填充,放縮位圖比例,使用剪切功能。這個就是源代碼下載的地址:http://www.aeon-software.com/downloads/clipscale.zip 。
仍然有些東東咱們應該討論,例如頁面的切換(flipping),雙緩衝區的應用等,不管如何,咱們還將繼續,因此沒必要擔憂咱們會遺漏重要的內容。^_^
如今,最初的原始積累已經結束,咱們之後的焦點會從建立Windows程序轉移到建立一個貼圖基礎的RPG遊戲。之後的章節將包括用DirectInput創建一個好的輸入機制,寫一個基礎的引擎腳本,播放背景音樂和配音,等等。下一章,咱們將學習爲貼圖遊戲製做一個簡單的卷軸遊戲引擎,很容易的,沒有你想象的那麼難哦!
數組

相關文章
相關標籤/搜索