六月婷婷综合激情-六月婷婷综合-六月婷婷在线观看-六月婷婷在线-亚洲黄色在线网站-亚洲黄色在线观看网站

明輝手游網(wǎng)中心:是一個免費提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

使用規(guī)范GDI完成游戲品質(zhì)的動畫系統(tǒng)

[摘要]燕良 2002年1月http://www.diamondgarden.net/ 前言 2GDI基礎(chǔ) 3繪制一個位圖(BITMAP)對象 3常用像素格式 4WINDOWS下的基本動畫系統(tǒng) 4動畫驅(qū)動方式 4播放動畫 5消除閃爍 6透明色(COLOR KEY)處理 7ALPHA混合 9讀取JPEG,G...
燕良 2002年1月
http://www.diamondgarden.net/


前言 2
GDI基礎(chǔ) 3
繪制一個位圖(BITMAP)對象 3
常用像素格式 4
WINDOWS下的基本動畫系統(tǒng) 4
動畫驅(qū)動方式 4
播放動畫 5
消除閃爍 6
透明色(COLOR KEY)處理 7
ALPHA混合 9
讀取JPEG,GIF文件 10
子窗口管理 12
進(jìn)階技巧--使用DIB 14
像素操作 14
RLE壓縮 15
參考 15
華山論鍵 15
其它類庫 16
前言
說到實現(xiàn)游戲品質(zhì)的動畫,很多人會立刻想到DirectX,沒錯DirectDraw很強(qiáng)大,但是并不是必須用DirectDraw才行。動畫后面的理論和技巧都是一樣的,這和末端使用什么API沒有太大關(guān)系(如果那API不是太~~慢的話)。就筆者實現(xiàn)的NewImage Lib的測試結(jié)果,內(nèi)部所有像素數(shù)據(jù)的存儲和運算都純軟件實現(xiàn),最后一步輸出到屏幕使用GDI的性能比DirectDraw低不到10%,在Window9X系統(tǒng)上要低20%左右,這對很多軟件來說是絕對可以接受的。

現(xiàn)在應(yīng)用程序界面越做越華麗,除了支持SKIN外,很多人都想在程序中加入一些例如sprite動畫這種原本用在游戲上的技術(shù),因為這原因引入DirectX API,顯然是不值得的(況且DX版本升級頻繁,DX8中已經(jīng)用DirectGraphic取代了DirectDraw)。本文將以筆者使用標(biāo)準(zhǔn)GDI函數(shù)實現(xiàn)的商業(yè)游戲為例,帶你進(jìn)入高品質(zhì)2D動畫編程領(lǐng)域,并且保證其設(shè)備無關(guān)性。

本文假設(shè)讀者有C/C++語言知識,Windows編程基礎(chǔ),GDI基本概念。下面我將主要講述我在過去工作中積累的經(jīng)驗和一些技巧,但是將不講解以上基本概念。讀者最好有MFC基礎(chǔ),本文給出的代碼將主要使用MFC,但是其中的道理卻不限于MFC。

GDI基礎(chǔ)
繪制一個位圖(Bitmap)對象
  GDI的所有操作都是在DC(device context)上進(jìn)行的,所以首先你應(yīng)該有DC的概念,如果你對DC還不了解,現(xiàn)在就去翻一翻Windows編程的書吧。
首先我們要Load一個Bitmap對象,使用Win32 API可以寫成這樣:
file://從資源Load一個位圖,如果從文件load的話,可以使用::LoadImage()
HBITMAP hbmp=::LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_MYBMP));
如果使用MFC可以這樣寫:
    CBitmap bmp;
    Bmp.LoadBitmap(IDB_MYBMP);
想把這個位圖對象繪制到窗口上就要先得到窗口的DC,然后對這個DC操作。請留意創(chuàng)建MemoryDC的代碼,后面會用到。
Win32 API的版本:
file://假設(shè)位圖大小為100*100像素
    file://假設(shè)hwnd是要繪制的窗口的HANDLE
    HDC hwnddc=::GetDC(hwnd);
    HDC memdc=::CreateCompatibleDC(hwnddc);
    HBITMAP oldbmp=::SelectObject(memdc,hbmp);
    ::BitBlt(hwnddc,0,0,100,100,memdc,0,0,SRCCOPY);
    if(oldbmp)
        ::SelectObject(memdc,oldbmp);
    DeleteDC(memdc);
    ::ReleaseDC(hwnd,hwnddc);
MFC版本:
    file://假設(shè)是在一個CWnd派生類的成員函數(shù)中
    CClientDC dc(this);
    CDC memdc;
    memdc.CreateCompatibleDC(&dc);
    CBitmap *oldbmp=memdc.SelectObject(&bmp);
    dc.BitBlt(0,0,100,100,&memdc,0,0,SRCCOPY);
    if(oldbmp)
        memdc.SelectObject(oldbmp);
也可以這樣:
     CClientDC dc(this);
dc.DrawState(CPoint(0,0),CSize(100,100),&bmp,DST_BITMAP);

基本的代碼就是這樣,當(dāng)然有更多的API可以用,這就要看你自己的了。J
常用像素格式
  要進(jìn)行圖像編程的化對像素格式不了解似乎說不過去。我想應(yīng)該有較多的人并不太了解,所以這里簡要的介紹一下。
1. 8bit
  也叫做256色模式。每個像素占一個字節(jié), 使用調(diào)色板。調(diào)色板實際上是一個顏色表,簡單的講就是,我們有256個油漆桶(因為像素的取值范圍是0到255),每個油漆桶里面漆的顏色都由紅,綠,藍(lán)(RGB)三中基本的油漆按不同比例配置而成。所以我們指定一個像素的顏色的時候只需要指定它用的第幾號桶就好了。
  這種模式造就了DOS時代的神奇模式—13H(320*200*256色),因為320*200*1Byte正好是16bit指針尋址能力的范圍。這種模式有2的18次方種顏色(通過改變調(diào)色板實現(xiàn)),可以同時顯示256中顏色。這模式剛剛推出的時候,有人驚呼這是人類智慧的結(jié)晶呢!也是這種模式造就了1992年WestWood的<<卡蘭蒂亞傳奇>>和1995年大宇資訊的<<仙劍奇?zhèn)b傳>>這樣的經(jīng)典游戲。
  在Windows下硬件調(diào)色板應(yīng)該極少用到,但是你可以用軟件調(diào)色板來壓縮你的動畫,這也是在2D游戲中常用的技巧。
2. 16bit
  這也是筆者最喜歡的模式。它不使用調(diào)色板。每個像素占兩個字節(jié),存儲RGB值。我覺得這種像素格式的效果(同時顯示顏色數(shù))和存儲量(也影響速度)取得了比較好的統(tǒng)一。但是如果你是寫應(yīng)用程序的話,我勸你不要用它。因為它的RGB值都不是整個BYTE,例如565模式(16bit的一種模式),它的RGB所占用的bit就是這樣的:
      RRRR  RGGG  GGGB  BBBB
3. 24bit
  每個像素有三個BYTE,分別存儲RGB值,這對你來說是不是很方便?是不是太好了?可惜對我們可憐的計算機(jī)卻不是,因為CPU訪問奇數(shù)的地址會很費勁,而且在硬件工藝上也有很多困難(具體我也不太清楚,請做過硬件的高手指點),所以你會發(fā)現(xiàn)你的顯卡不支持這種模式,但是你可以在自己的軟件中使用。
4. 32bit
  每個像素4個BYTE,分別存儲RGBA,A值就是Alpha,也就是透明度,可以用像素混合算法實現(xiàn)多種效果,后面你就會看到。
Windows下的基本動畫系統(tǒng)
動畫驅(qū)動方式
  先略說一下動畫的基本原理,程序播放動畫一般過程都是: 繪制—擦除—繪制,這樣的重復(fù)過程,只要你重復(fù)的夠快,至少每秒16次(被稱作16FPS,F(xiàn)rame per Second),我們可憐的眼睛就分辨不出單幀的圖像了,看上去就是動畫了。
  在Windows環(huán)境下要驅(qū)動這樣重復(fù)不停的操作有兩種方法:
1. 設(shè)置Timer
  這很簡單,只要設(shè)置一個足夠短的Timer,然后響應(yīng)WM_TIME(對應(yīng)MFC中的OnTimer函數(shù))就可以滿足絕大部分應(yīng)用程序的需要。缺點是不夠精確,而且Win2000和Win9x系統(tǒng)的精確性又有較大差異。
2. 在消息循環(huán)中執(zhí)行動畫操作
這是在游戲中常用的方法,一般都會把WinMain中的消息循環(huán)寫成這樣:
    while( TRUE )
    {
        // Look for messages, if none are found then
        // update the state and display it
        if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
        {
            if( 0 == GetMessage(&msg, NULL, 0, 0 ) )
            {
                // WM_QUIT was posted, so exit
                return (int)msg.wParam;
            }
           TranslateMessage( &msg );
           DispatchMessage( &msg );
        }
        else
        {
            if( g_bActive )//在主窗口不激活時不更新,以節(jié)省資源
            {
         file://執(zhí)行動畫更新操作
            }
            // Make sure we go to sleep if we have nothing else to do
            else WaitMessage();
        }
}
如果你使用MFC,則需要重載CWinAPP的Run虛函數(shù),把上述消息循環(huán)替換進(jìn)去。
播放動畫
現(xiàn)在我們有了一個適當(dāng)?shù)臅r機(jī)執(zhí)行更新操作了,現(xiàn)在就讓我們試試動畫吧。下面的代碼將不再提供Win32的版本。
為了敘述方便,我需要一個播放動畫的窗口,它必須是一個CWnd的派生類,假設(shè)這個類叫做CMyView,我們將在這個窗口中繪制動畫。首先我們?yōu)檫@個類添加一個成員函數(shù)”void CMyView::RenderView()”,你可以使用上面提到的方法調(diào)用這個函數(shù)。
現(xiàn)在準(zhǔn)備工作都做好了,我們的動畫該怎么存儲呢?別提動畫GIF89a格式(如果你覺得只有GIF才有動畫的話,那我勸你去做美術(shù)好了,別干程序了),如果你只想要個簡單的動畫播放當(dāng)然可以,但是如果你想要做復(fù)雜點的,交互式動畫,我勸你還是別用那東西。假設(shè)我們有一個4幀的動畫,怎么存儲它呢?我首先想到的就是存4個BMP文件,然后讀入到一個CBitmap對象數(shù)組中,但是尊敬的大師Scott Meyers警告我們不要使用多態(tài)數(shù)組,因為編譯器在某些情況下不能準(zhǔn)確計算數(shù)組中對象的大小,所以下標(biāo)運算符會產(chǎn)生可怕的效果。然后我就想到了用CBitmap指針數(shù)組,這到是不錯,不過管理起來稍嫌麻煩。現(xiàn)在看看我最終的解決方法吧。把一個幀序列安順序拼接成一個文件,象這樣:

然后用它創(chuàng)建一個CImageList對象,讓我們仔細(xì)看一下創(chuàng)建的方法,使用
BOOL CImageList::Create( int cx, int cy, UINT nFlags, int nInitial, int nGrow );
函數(shù),前面兩個參數(shù)用來指定我們一幀動畫的尺寸。這樣就創(chuàng)建了一個空的ImageList,這樣做的好處是可擴(kuò)展行比較強(qiáng)。下面我們需要把那個幀序列文件Load到一個CBitmap對象中,你可以存成JPG或者GIF文件來節(jié)省容量(后面將提到讀取這些文件的簡單方法,并且附一個實用類)。當(dāng)我們有了一個合適的CBitmap對象后,可以把他添加到我們的ImageList中,使用:
BOOL CImageList::int Add( CBitmap* pbmImage, COLORREF crMask );
一個實例:
const int SPRIRT_WIDTH=32;
const int SPIRIT_HEIGHT=32;
….
m_myimglist.Create(SPIRIT_WIDTH,SPIRIT_HIGHT,ILC_COLOR24 ILC_MASK,1,1);
if(bmp.Load(“myani.bmp”))
m_myimglist.Add(&bmp,RGB(152,152,152));
  好了,現(xiàn)在我們已經(jīng)準(zhǔn)備好了這些數(shù)據(jù),讓我們來實作渲染函數(shù)吧,下面這端代碼可以循環(huán)播放上面的4幀動畫,并且支持透明色(如果你不知道這個名字,稍后有講解)哦!
  void CMyView::RenderView()
  {
        CclientDC dc(this);
        Static int curframe=0;
         m_myimglist.Draw(&dc,curframe,Cpoint(0,0),ILD_TRANSPARENT);
        curframe++;
        If(curframe > m_myimglist.GetImageCount())
            Curframe=0;
}
上面這個代碼沒有寫擦除的操作,因為這根據(jù)具體需要有較大不同。如果你只有一個精靈動畫的話,你可以用一個Bitmap對象保存精靈所占矩形區(qū)域的圖像。你也可能需要有一個大的背景圖每幀都要更新(這里我不討論象dirty rect這樣的優(yōu)化方法),所以你只要每次都畫背景,然后畫精靈就好了。
怎么樣?你已經(jīng)實現(xiàn)了基本的動畫系統(tǒng),就是這么簡單。
消除閃爍
  如果你真正實現(xiàn)上面的代碼的話,你會發(fā)現(xiàn)畫面一閃一閃的,十分的不爽。L 很多人都會怪到GDI頭上,他們又會罵MS,說GDI太慢了。其實非也(不是指MS不該罵,呵呵),任何直接寫屏幕的操作都會產(chǎn)生閃爍,在DOS下直接寫顯存或者用DirectDraw API直接寫Primary Surface都會閃爍,因為你每個更新顯示的操作都會被用戶馬上看到(因為垂直回掃的原因, 或許會有延遲)。
  消除閃爍最簡單也是最經(jīng)典的方法就是雙緩沖(Double buffer)。所謂的雙緩沖其實道理非常簡單,就是說我們在其它地方(簡單的說就是不針對屏幕,不顯示出來的地方)開辟一個存儲空間,我們把所有的動畫都要渲染到這個地方,而不是直接渲染到屏幕上(針對屏幕的存儲區(qū)域)。在GDI中,直接針對屏幕就是窗口DC,”不可見的地方”一般可以用Memory DC。在把所有動畫渲染到后臺緩沖之后,再一下次整體拷貝到屏幕緩沖區(qū)!
在純軟件2D圖形引擎中,雙緩沖一般意味著在內(nèi)存中開辟一個區(qū)域用來存儲像素數(shù)據(jù)。而在DirectDraw中可以創(chuàng)建Back Surface,在把所有動畫渲染到Back Suface上之后,然后使用Flip操作使其可見,F(xiàn)lip操作因為只是設(shè)置可見surface的地址,所以非常快速。
讓我們重寫一下void CMyView::RenderView()函數(shù),來用GDI實現(xiàn)雙緩沖:
  void CMyView::RenderView()
  {
        CClientDC dc(this);
        CRect rc;
        GetClientRect(rc);
  CDC memdc;
   memdc.CreateCompatibleDC(&dc);
    CBitmap bmp;
        Bmp. CreateCompatibleBitmap (&dc,rc.Width(),rc.Height());
    CBitmap *oldbmp=memdc.SelectObject(&bmp);

        Static int curframe=0;
         m_myimglist.Draw(&memdc,curframe,Cpoint(0,0),ILD_TRANSPARENT);
        curframe++;
        If(curframe > m_myimglist.GetImageCount())
Curframe=0;
    if(oldbmp)
           memdc.SelectObject(oldbmp);
        dc.BitBlt(0,0,rc.Width(),rc.Height(),&memdc,0,0,SRCCOPY);
}
其中創(chuàng)建一個Bitmap對象,然后選入Memory DC是必須的,因為CreateCompatibleDC所創(chuàng)建的DC里面只含有一個1*1像素的單色Bitmap對象,所以如果缺了這個步驟,任何在MemoryDC上的繪圖操作都會沒有效果。延伸出一個問題, CreateCompatibleBitmap函數(shù)的第一個參數(shù)顯然不可寫成&memdc,如果那樣的化,你就創(chuàng)建了一個單色的位圖,我想你肯定不希望這樣。J
重寫后的函數(shù)看上去似乎多了很多無謂的操作,這是因為我們現(xiàn)在只有一個動畫對象,如果我們有多個動畫,而且還需要繪制動畫的子窗口,那這樣做的效果就會非常的好,不會有任何閃爍,而且向文章最后提到的圖形MUD客戶端,還能達(dá)到60FPS呢(在我家的賽陽433上)。
到此為止,我們的基本動畫系統(tǒng)已經(jīng)有了一個很好的基礎(chǔ)了。
透明色(color key)處理
  透明色就是指在繪制一張圖片的時候,該顏色的像素不會被繪制上去,這通常用來做游戲的spirit動畫,所以你可以看到各種形狀不規(guī)則的人物動畫。但是他們的數(shù)據(jù)都是一個矩形的像素區(qū)域,只是繪制的時候有些像素不被畫上去罷了。
  GDI提供一個TransparentBlt()函數(shù)來支持Color Key,你可以在MSDN中查到該函數(shù)的說明。但是我的代碼中使用這個函數(shù)后,在Win9X系統(tǒng)下產(chǎn)生了嚴(yán)重的資源泄漏,但是在Win2000下卻沒事,所以如果你也發(fā)現(xiàn)這問題的話,我建議你使用下面的代碼,來把一個CBitmap透明的繪制到DC上。假設(shè)你有一個CBitmap的派生類CMyBitmap:

BOOL CMyBitmap::DrawTransparentInPoint(CDC *pdc, int x, int y, COLORREF mask/*要過濾掉的顏色值*/)
{
    file://Quick return
    if(pdc->GetSafeHdc()==NULL)
        return FALSE;
    if (m_hObject == NULL)
        return FALSE;

    CRect DRect;
    DRect=Rect();
    DRect.OffsetRect(x,y);
    if(!pdc->RectVisible(&DRect))
        return FALSE;

    COLORREF crOldBack=pdc->SetBkColor(RGB(255,255,255));
    COLORREF crOldText=pdc->SetTextColor(RGB(0,0,0));

    CDC dcimg,dctrans;
    if(dcimg.CreateCompatibleDC(pdc)!=TRUE)
        return FALSE;
    if(dctrans.CreateCompatibleDC(pdc)!=TRUE)
        return FALSE;

    CBitmap *oldbmpimg=dcimg.SelectObject(this);

    CBitmap bmptrans;
    if(bmptrans.CreateBitmap(Width(),Height(),1,1,NULL)!=TRUE)
        return FALSE;

    CBitmap *oldbmptrans=dctrans.SelectObject(&bmptrans);

    dcimg.SetBkColor(mask);
    dctrans.BitBlt(0,0,Width(),Height(),&dcimg,0,0,SRCCOPY);

    pdc->BitBlt(x,y,Width(),Height(),&dcimg,0,0,SRCINVERT);
    pdc->BitBlt(x,y,Width(),Height(),&dctrans,0,0,SRCAND);
    pdc->BitBlt(x,y,Width(),Height(),&dcimg,0,0,SRCINVERT);

    if(oldbmpimg)
        dcimg.SelectObject(oldbmpimg);
    if(oldbmptrans)
        dctrans.SelectObject(oldbmptrans);
    pdc->SetBkColor(crOldBack);
    pdc->SetTextColor(crOldText);

    return TRUE;
}
Alpha混合
  Alpha混合是一種像素混合的方法。所謂的像素混合就是使用一定的算法把兩個像素的值混合成一個新的像素值(倒,和沒說一樣),通常我們都把兩個像素的值,分別叫做源(src)和目的(dst),然后把混合后的結(jié)果存入dst中:
  dst= src blend dst
如果源像素和目的像素都是RGBA格式,你可以使用每個像素的Alpha信息(或者叫做Alpha通道)組合出各種運算公式,例如
  dst= src*src.alpha+dst*dst.alpha;
  或者
dst=src*src.alpha + dst*(1-src.alpha)//這里我們假設(shè)alpha值是0~1的浮點數(shù)。
可惜標(biāo)準(zhǔn)GDI沒有支持類似這種操作的函數(shù)(起碼我沒找到),它只支持另一種Alpha混合,我把它叫做const alpha blend,也就是把兩幅都不包含Alpha通道的圖像的按照一個固定的Alpha值混合到一起,也就是每個像素都使用同一Alpha值。GDI的支持這個操作的函數(shù)是:
AlphaBlend(
  HDC hdcDest,
  int nXOriginDest,
  int nYOriginDest,
  int nWidthDest,
  int hHeightDest,
  HDC hdcSrc,
  int nXOriginSrc,
  int nYOriginSrc,
  int nWidthSrc,
  int nHeightSrc,
  BLENDFUNCTION blendFunction
);
這個API的參數(shù)個數(shù)略多了一些,但是我想其中的位置參數(shù)你可以輕松搞定,還有就是源DC和目的DC,當(dāng)然了,我們的GDI只能對DC操作,而不是對我們的像素數(shù)據(jù),而我們只要把我的位圖select到DC中就OK了,最后一個參數(shù)是一個結(jié)構(gòu),是用來指定Alpha的運算方式的,請看一個實際的例子:
BLENDFUNCTION bf;
bf.AlphaFormat=0;
bf.BlendFlags=0;
bf.BlendOp=AC_SRC_OVER;
bf.SourceConstantAlpha=100;//指明透明度,取值范圍是0~255

AlphaBlend(pdc->GetSafeHdc(),rc.left,rc.top,rc.Width(),rc.Height(),
  memdc.GetSafeHdc(),0,0,rc.Width(),rc.Height(),bf);
也許你看過很多游戲,在彈出文字對話框的時候都是在游戲畫面上蒙一層半透明的黑色,然后在這上面印字。使用上述操作就可以達(dá)到此效果。你可以先建立一個Memory DC,然后把他填充為黑,然后把Alpha值設(shè)為128,然后混合到你要繪制的DC上(不一定是窗口DC哦,記得我們前面將的雙緩沖嗎?)就OK了。
讀取JPEG,GIF文件
JPEG壓縮算法綜合的信號學(xué)和視覺心理學(xué),而GIF格式,特別是支持動畫的GIF89a格式為了節(jié)約容量也做了很多種非常變態(tài)的優(yōu)化,所以要寫一個完全支持這些標(biāo)準(zhǔn)格式的解碼器相當(dāng)困難,也沒有必要。
如果你需要進(jìn)行JPEG文件的讀寫我推薦你使用Intel Jpeg Lib,速度相當(dāng)令人滿意。而GIF由于授權(quán)問題,沒有任何官方組織提供的讀寫代碼。
如果你只是需要讀入JPEG和靜態(tài)GIF(或者只一幀的動態(tài)GIF),我推薦你使用Windows提供的OleLoadPicture函數(shù),下面這段代碼可以把一個JPG,GIF,BMP讀入到Bitmap對象中:
BOOL CIJLBitmap::Load(LPCTSTR lpszPathName)
{
    BOOL bSuccess = FALSE;
    
    file://Free up any resource we may currently have
    DeleteObject();
    
    file://open the file
    CFile f;
    if (!f.Open(lpszPathName, CFile::modeRead))
    {
        TRACE(_T("Failed to open file %s, Error:%x\n"), lpszPathName, ::GetLastError());
        return FALSE;
    }
    
    file://get the file size
    DWORD dwFileSize = f.GetLength();
    
    file://Allocate memory based on file size
    LPVOID pvData = NULL;
    HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);
    if (hGlobal == NULL)
    {
        TRACE(_T("Failed to allocate memory for file %s, Error:%x\n"), lpszPathName, ::GetLastError());
        return FALSE;
    }
    pvData = GlobalLock(hGlobal);
    file://ASSERT(pvData);
    if(pvData==NULL)
    {
        TRACE(_T("Failed to lock memory\r\n"));
        return FALSE;
    }
    // read file and store in global memory
    if (f.Read(pvData, dwFileSize) != dwFileSize)
    {
        TRACE(_T("Failed to read in image date from file %s, Error:%x\n"), lpszPathName, ::GetLastError());
        GlobalUnlock(hGlobal);
        GlobalFree(hGlobal);
        return FALSE;
    }
    
    file://Tidy up the memory and close the file handle
    GlobalUnlock(hGlobal);
    
    file://create IStream* from global memory
    LPSTREAM pStream = NULL;
    if (FAILED(CreateStreamOnHGlobal(hGlobal, TRUE, &pStream)))
    {
        TRACE(_T("Failed to create IStream interface from file %s, Error:%x\n"), lpszPathName, ::GetLastError());
        GlobalFree(hGlobal);
        return FALSE;
    }
    
    // Create IPicture from image file
    if (SUCCEEDED(::OleLoadPicture(pStream, dwFileSize, FALSE, IID_IPicture, (LPVOID*)&m_pPicture)))
    {
        short nType = PICTYPE_UNINITIALIZED;
        if (SUCCEEDED(m_pPicture->get_Type(&nType)) && (nType == PICTYPE_BITMAP))
        {
            OLE_HANDLE hBitmap;
            OLE_HANDLE hPalette;
            if (SUCCEEDED(m_pPicture->get_Handle(&hBitmap)) &&
                SUCCEEDED(m_pPicture->get_hPal(&hPalette)))
            {
                Attach((HBITMAP) hBitmap);
                m_Palette.Attach((HPALETTE) hPalette);
                bSuccess = TRUE;
            }
        }
    }
    
    file://Free up the IStream* interface
    pStream->Release();
    
    return bSuccess;
}
這個class的完整代碼請看文章最后的參考。
子窗口管理
  你也許注意過幾乎所有游戲界面中的窗口都是使用動畫的從屏幕外飛出(而且是半透明的,這你已經(jīng)可以做到了)。游戲中一般都使用自己的UI系統(tǒng)。這里我們可以借助Windows對窗口的管理來輕松實現(xiàn)各種動畫子窗口。
  首先讓我們從最簡單的開始。假設(shè)在我們的動畫窗口中需要一個漂亮的按鈕怎么辦,我勸你最好不要使用CBitmapButton,因為你已經(jīng)上了每秒重畫窗口16次以上這條賊船,我建議你在每次重畫父窗口的時候重畫所有子窗口,如此一來子窗口上如果要求有動畫操作,也可以輕松實現(xiàn)了。既然做了,就把它做到最好。J
那我們怎么定義一個button呢?你也許想到自己定義一個矩形區(qū)域,然后在父窗口的消息響應(yīng)函數(shù)中檢測是否是對此區(qū)域操作,這樣在重畫父窗口的時候特殊的畫一次這個矩形區(qū)域就好了。這樣是可以實現(xiàn),但是顯然不符合我們的OOP精神,界面元素一多,你很可能就會亂了陣腳。最后的解決方法當(dāng)然是使用我們可愛的CWnd類,顯然所有的界面元素都可以作為一個CWnd派生類的對象。不過我建議你不要從CButton派生,這帶來的麻煩遠(yuǎn)多于它的價值。從CWnd派生一個類,然后在Create時注意使用WS_CHILD風(fēng)格,并且指定父窗口為我們的動畫窗口。
下面一個問題是如何調(diào)用這些子窗口重畫操作呢?第一種較好的解決方法是先建立這樣一個虛基類:
CmyAniWnd :public CWnd
{

virtual void Render(CDC *pdc)=0;

}
假設(shè)你有一個Button類和一個TextBox類:
CmyButton : public CmyAniWnd
CmyTextBox: public CmyAniWnd
這兩個類都必須實現(xiàn)Render函數(shù),這樣在父窗口類中你可以保存一個指針數(shù)組,例如這樣:
CPtrArray m_allchild;
在創(chuàng)建一個Button時這樣寫:
CmyButton *pbtn=new CmyButton;
m_allchild.Add(pbtn);
pbtn->Create(…);
然后在我們父窗口的RenderView函數(shù)(前面提到的,每次更新調(diào)用)中這樣寫即可:
CmyAniWnd *pchild=NULL;
for(int I;I<m_allchild.GetSize();I++)
{
pchild=static_cast<CmyAniWnd*>(m_allchild.GetAt(i));
ASSERT(::IsWindow(pchild->GetSafeHwnd());
pchild->Render(&memdc);
}
這是一個典型的虛函數(shù)的應(yīng)用,在調(diào)用這些子窗口的Render函數(shù)時,我們不需要知道它到底是Button還是TextBox,虛函數(shù)機(jī)制會自動幫我們找到該調(diào)用的函數(shù)。還有一點就是,請注意,一定要把子窗口渲染到我們的后臺緩沖,也就是Memory DC中,否則還是會閃爍的。
上面這種方法適合于子窗口數(shù)目固定,更高級的界面會要求觸發(fā)某個事件的時候產(chǎn)生一個子窗口,子窗口不斷更新自己,并且在適當(dāng)?shù)臅r候把自己從UI系統(tǒng)中去除。讓每個子窗口管理自己的生命期,是個不錯的主意,不是嗎?那你最好不要使用上面保存指針數(shù)組的方法,那樣的話,子窗口在殺死自己的時候還要通知父窗口,以讓父窗口把它的指針從數(shù)組中移除,這顯然具有很高的偶合性,不是我們想要的。因為我們的所有子窗口都是標(biāo)準(zhǔn)的Windows對象,所以這使得我們有使用Windows消息的機(jī)會。我們首先要枚舉所有子窗口,然后發(fā)一個自定義的更新消息給它,并把我們的MemoryDC的指針作為參數(shù),具體例子代碼如下:
void CMyView::RenderView()
{
…//其它更新操作
::EnumChildWindows(GetSafeHwnd(),CMyView::UpdateChildWnd,LPARAM(&memdc));
…//其它更新操作
}
其中第二個參數(shù)是一個回調(diào)函數(shù),你必須把它聲明成全局函數(shù),或者類的static成員函數(shù),這里我們使用了后者。
BOOL CALLBACK CMyWnd::UpdateChildWnd(HWND hwnd, LPARAM lParam/*CDC* */)
{
::SendMessage(hwnd,WM_COMMAND,CHILDCMD_RENDER,lParam);
return TRUE;
}
這里我沒有使用自定義消息,而是發(fā)送標(biāo)準(zhǔn)的WM_COMMAND,這樣你可以給那個CmyAniWnd虛基類添加一個CWnd虛函數(shù)OnCommand(),然后在那里面檢測如果wParam是CHILDCMD_RENDER的話,就調(diào)用純虛函數(shù)Render(以lParam作為參數(shù)),子窗口派生類只要實現(xiàn)自己的Render函數(shù)就好,其它不用管了。
這里還有一個要注意的問題就是繪制的順序問題,如果你想讓子窗口蓋住某些動畫,就應(yīng)該先渲染那些動畫,然后渲染子窗口,反之亦反。
進(jìn)階技巧--使用DIB
像素操作
以上所有操作都局限于標(biāo)準(zhǔn)GDI函數(shù),如果我們要實現(xiàn)更進(jìn)一步的操作,例如當(dāng)傍晚你希望把整個畫面的顏色渲染能淡紅色調(diào),晚上的時候你要把整個畫面變暗,早上再把它恢復(fù)到原來的亮度這些GDI都無法幫你做到。 如果想達(dá)到上述效果,就必須自己對像素的RGB值進(jìn)行操作。
首先讓我們要得到一個Bitmap對象中的像素數(shù)據(jù)。讓我們看一下具體該怎么操作。假設(shè)我們有一個mybmp是一個CBitmap對象(或者其派生類對象),下面的代碼把CBitmap中的像素取出:
BITMAP bm;
mybmp.GetBitmap(&bm);
BITMAPINFO binfo;
ZeroMemory(&binfo,sizeof(BITMAPINFO));
binfo.bmiHeader.biBitCount=24; file://24bit像素格式
binfo.bmiHeader.biCompression=0;
binfo.bmiHeader.biHeight=-bm.bmHeight;
binfo.bmiHeader.biPlanes=1;
binfo.bmiHeader.biSizeImage=0;
binfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
binfo.bmiHeader.biWidth=bm.bmWidth;

CClientDC dc(this);
BYTE *pbuf;//用來存儲像素數(shù)據(jù)
int linebytes=(bm.bmWidth*3+3)&(~3);//4字節(jié)對齊
int size=linebytes*bm.bmHeight;
pbuf=new BYTE[size];
::GetDIBits(dc,m_bmpSword,0,bm.bmHeight,pbuf,&binfo,DIB_RGB_COLORS);上面代碼執(zhí)行后,我們的pbuf中就存儲了從mybmp拷貝而來的像素數(shù)據(jù),而且是24bit模式的,這樣你就可以對所有這些像素進(jìn)行你所需要的操作了,例如晚上了,你想把這個Bitmap變暗,我這里粗略的把每個像素的RGB值都降低一半,可以使用下面的循環(huán):
for(int I;I<size;I++)
  pbuf[i]=pbuf[i]/2;
得到了像素你就得到了一切,所有操作你都可以進(jìn)行,例如上面提到的標(biāo)準(zhǔn)GDI不支持的Alpha通道。
呵呵,像素交給你了,這樣我就放心了,那我走了…。。等等,你得到了這些像素,但是渲染時我們還是要使用標(biāo)準(zhǔn)GDI操作,所以好把這些像素設(shè)置回Bitmap對象中才行,好吧,這其實很簡單,繼續(xù)上面的代碼:
SetDIBits(dc,mybmp,0,bm.bmHeight,pbuf,&binfo,DIB_RGB_COLORS);
最后別忘了:
delete[] pbuf;
RLE壓縮
現(xiàn)在的個人電腦內(nèi)存容量已經(jīng)非常大了,但是對某些人來說還顯得不夠(或者他們不愿意浪費這些可憐的資源雖然它們可再生),例如在Diablo中一個骷髏兵從地上站起來的動畫為96*96像素*100幀,所以你有很多這樣的動畫,最好壓縮一下.
RLE是游戲常用的技巧,但是似乎已經(jīng)超出了本文的范圍。而且這方面的文章很多,我這里就不贅述了,留給你自己去進(jìn)一步發(fā)掘.J
最后,更多編程文章,請訪問我個人網(wǎng)站http://www.diamondgarden.net/。
參考
華山論鍵
2001年上半年,我為號稱國內(nèi)最大武俠社區(qū)的笑傲江湖.com實現(xiàn)的圖形MUD客戶端軟件,基于上述技術(shù)。詳情請見http://hslj.Xajh.com

其它類庫
CIJLBitmap  一個CBitmap的派生類,可以Load BMP,JPG,GIF文件
NewImage Lib  純軟件2D圖像引擎,支持RLE,Alpha通道等,與GDI和DX無關(guān),所謂的Open-ending。
以上兩個都可以到我個人網(wǎng)站http://www。diamondgarden。net下載。
鄭重聲明:本文所有使用的所有圖片,其版權(quán)都?xì)w笑傲江湖.Com(http://www.xajh.com)武俠文化社區(qū)所有!不得擅自使用,否則責(zé)任自負(fù),與本文作者無關(guān)。


主站蜘蛛池模板: 香蕉免费一区二区三区 | 青青国产精品视频 | 新一级毛片国语版 | 天天干夜夜噜 | 五月天激情婷婷大综合 | 欧美一区二区三区四区五区六区 | 青青影视 | 视频二区日韩 | 亚欧成人中文字幕一区 | 欧美性色一级在线观看 | 天堂网avtt| 日韩亚洲成a人片在线观看 日韩亚州 | 色成人综合网 | 青青草原视频在线 | 亚洲欧美91 | 亚洲成在人线影视天堂网 | 四虎影片 | 情不自禁完整版在线观看免费 | 五月婷婷六月香 | 亚洲成a人不卡在线观看 | 天天射日日 | 中文字幕不卡免费高清视频 | 性生大片免费观看性 | 四虎sihu新版影院亚洲精品 | 亚洲va久久久噜噜噜久久男同 | 四虎影院国产精品 | 日本成人一区二区三区 | 日韩中文字幕a | 青春草在线视频精品 | 欧美午夜精品久久久久免费视 | 午夜三级成人三级 | 中文字幕在线视频在线看 | 中国一级做a爰片久久毛片 中国性欧美 | 午夜黄色福利视频 | 伊人2233 | 人九九精品 | 一级黄色网 | 一级aa免费视频毛片 | 欧美一区二区三区精品 | 日本免费高清一区 | 亚洲aa |