|
楼主 |
发表于 2007-2-8 14:27:36
|
显示全部楼层
重写后的函数看上去似乎多了很多无谓的操作,这是因为我们现在只有一个动画对象,如果我们有多个动画,而且还需要绘制动画的子窗口,那这样做的效果就会非常的好,不会有任何闪烁,而且向文章最后提到的图形MUD客户端,还能达到60FPS呢(在我家的赛阳433上)。<br/>到此为止,我们的基本动画系统已经有了一个很好的基础了。<br/>透明色(color key)处理<br/> 透明色就是指在绘制一张图片的时候,该颜色的像素不会被绘制上去,这通常用来做游戏的spirit动画,所以你可以看到各种形状不规则的人物动画。但是他们的数据都是一个矩形的像素区域,只是绘制的时候有些像素不被画上去罢了。<br/> GDI提供一个TransparentBlt()函数来支持Color Key,你可以在MSDN中查到该函数的说明。但是我的代码中使用这个函数后,在Win9X系统下产生了严重的资源泄漏,但是在<a href="http://tech.acnow.net/Html/OS/Windows2000/" target="_blank">Win2000</a>下却没事,所以如果你也发现这问题的话,我建议你使用下面的代码,来把一个CBitmap透明的绘制到DC上。假设你有一个CBitmap的派生类CMyBitmap: <p></p><p>BOOL CMyBitmap:rawTransparentInPoint(CDC *pdc, int x, int y, COLORREF mask/*要过滤掉的颜色值*/)<br/>{<br/> file://Quick return<br/> if(pdc->GetSafeHdc()==NULL)<br/> return FALSE;<br/> if (m_hObject == NULL)<br/> return FALSE;</p><p> CRect DRect;<br/> DRect=Rect();<br/> DRect.OffsetRect(x,y);<br/> if(!pdc->RectVisible(&DRect))<br/> return FALSE;</p><p> COLORREF crOldBack=pdc->SetBkColor(RGB(255,255,255));<br/> COLORREF crOldText=pdc->SetTextColor(RGB(0,0,0));</p><p> CDC dcimg,dctrans;<br/> if(dcimg.CreateCompatibleDC(pdc)!=TRUE)<br/> return FALSE;<br/> if(dctrans.CreateCompatibleDC(pdc)!=TRUE)<br/> return FALSE;</p><p> CBitmap *oldbmpimg=dcimg.SelectObject(this);</p><p> CBitmap bmptrans;<br/> if(bmptrans.CreateBitmap(Width(),Height(),1,1,NULL)!=TRUE)<br/> return FALSE;</p><p> CBitmap *oldbmptrans=dctrans.SelectObject(&bmptrans);</p><p> dcimg.SetBkColor(mask);<br/> dctrans.BitBlt(0,0,Width(),Height(),&dcimg,0,0,SRCCOPY);</p><p> pdc->BitBlt(x,y,Width(),Height(),&dcimg,0,0,SRCINVERT);<br/> pdc->BitBlt(x,y,Width(),Height(),&dctrans,0,0,SRCAND);<br/> pdc->BitBlt(x,y,Width(),Height(),&dcimg,0,0,SRCINVERT);</p><p> if(oldbmpimg)<br/> dcimg.SelectObject(oldbmpimg);<br/> if(oldbmptrans)<br/> dctrans.SelectObject(oldbmptrans);<br/> pdc->SetBkColor(crOldBack);<br/> pdc->SetTextColor(crOldText);</p><p> return TRUE;<br/>}<br/>Alpha混合<br/> Alpha混合是一种像素混合的方法。所谓的像素混合就是使用一定的算法把两个像素的值混合成一个新的像素值(倒,和没说一样),通常我们都把两个像素的值,分别叫做源(src)和目的(dst),然后把混合后的结果存入dst中:</p><p>dst= src blend dst<br/>如果源像素和目的像素都是RGBA格式,你可以使用每个像素的Alpha信息(或者叫做Alpha通道)组合出各种运算公式,例如<br/> dst= src*src.alpha+dst*dst.alpha;<br/> 或者<br/>dst=src*src.alpha + dst*(1-src.alpha)//这里我们假设alpha值是0~1的浮点数。<br/>可惜标准GDI没有支持类似这种操作的函数(起码我没找到),它只支持另一种Alpha混合,我把它叫做const alpha blend,也就是把两幅都不包含Alpha通道的图像的按照一个固定的Alpha值混合到一起,也就是每个像素都使用同一Alpha值。GDI的支持这个操作的函数是:<br/>AlphaBlend(<br/> HDC hdcDest,<br/> int nXOriginDest,<br/> int nYOriginDest,<br/> int nWidthDest,<br/> int hHeightDest,<br/> HDC hdcSrc,<br/> int nXOriginSrc,<br/> int nYOriginSrc,<br/> int nWidthSrc,<br/> int nHeightSrc,<br/> BLENDFUNCTION blendFunction<br/>);<br/>这个API的参数个数略多了一些,但是我想其中的位置参数你可以轻松搞定,还有就是源DC和目的DC,当然了,我们的GDI只能对DC操作,而不是对我们的像素数据,而我们只要把我的位图select到DC中就OK了,最后一个参数是一个结构,是用来指定Alpha的运算方式的,请看一个实际的例子:<br/>BLENDFUNCTION bf;<br/> bf.AlphaFormat=0;<br/> bf.BlendFlags=0;<br/> bf.BlendOp=AC_SRC_OVER;<br/> bf.SourceConstantAlpha=100;//指明透明度,取值范围是0~255<br/> <br/> AlphaBlend(pdc->GetSafeHdc(),rc.left,rc.top,rc.Width(),rc.Height(),<br/> memdc.GetSafeHdc(),0,0,rc.Width(),rc.Height(),bf);<br/> 也许你看过很多游戏,在弹出文字对话框的时候都是在游戏画面上蒙一层半透明的黑色,然后在这上面印字。使用上述操作就可以达到此效果。你可以先建立一个Memory DC,然后把他填充为黑,然后把Alpha值设为128,然后混合到你要绘制的DC上(不一定是窗口DC哦,记得我们前面将的双缓冲吗?)就OK了。<br/>读取JPEG,GIF文件<br/> JPEG压缩算法综合的信号学和视觉心理学,而GIF格式,特别是支持动画的GIF89a格式为了节约容量也做了很多种非常变态的优化,所以要写一个完全支持这些标准格式的解码器相当困难,也没有必要。<br/> 如果你需要进行JPEG文件的读写我推荐你使用Intel Jpeg Lib,速度相当令人满意。而GIF由于授权问题,没有任何官方组织提供的读写代码。<br/> 如果你只是需要读入JPEG和静态GIF(或者只一帧的动态GIF),我推荐你使用Windows提供的OleLoadPicture函数,下面这段代码可以把一个JPG,GIF,BMP读入到Bitmap对象中:<br/>BOOL CIJLBitmap:oad(LPCTSTR lpszPathName)<br/>{<br/> BOOL bSuccess = FALSE;<br/> <br/> file://Free up any resource we may currently have<br/> DeleteObject();<br/> <br/> file://open the file<br/> CFile f;<br/> if (!f.Open(lpszPathName, CFile::modeRead))<br/> {<br/> TRACE(_T("Failed to open file %s, Error:%x\n"), lpszPathName, ::GetLastError());<br/> return FALSE;<br/> }<br/> <br/> file://get the file size<br/> DWORD dwFileSize = f.GetLength();<br/> <br/> file://Allocate memory based on file size<br/> LPVOID pvData = NULL;<br/> HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);<br/> if (hGlobal == NULL)</p><p>{<br/> TRACE(_T("Failed to allocate memory for file %s, Error:%x\n"), lpszPathName, ::GetLastError());<br/> return FALSE;<br/> }<br/> pvData = GlobalLock(hGlobal);<br/> file://ASSERT(pvData);<br/> if(pvData==NULL)<br/> {<br/> TRACE(_T("Failed to lock memory\r\n"));<br/> return FALSE;<br/> }<br/> // read file and store in global memory<br/> if (f.Read(pvData, dwFileSize) != dwFileSize)<br/> {<br/> TRACE(_T("Failed to read in image date from file %s, Error:%x\n"), lpszPathName, ::GetLastError());<br/> GlobalUnlock(hGlobal);<br/> GlobalFree(hGlobal);<br/> return FALSE;<br/> }<br/> <br/> file://Tidy up the memory and close the file handle<br/> GlobalUnlock(hGlobal);<br/> <br/> file://create IStream* from global memory<br/> LPSTREAM pStream = NULL;<br/> if (FAILED(CreateStreamOnHGlobal(hGlobal, TRUE, &pStream)))<br/> {<br/> TRACE(_T("Failed to create IStream interface from file %s, Error:%x\n"), lpszPathName, ::GetLastError());<br/> GlobalFree(hGlobal);<br/> return FALSE;<br/> }<br/> <br/> // Create IPicture from image file<br/> if (SUCCEEDED(::OleLoadPicture(pStream, dwFileSize, FALSE, IID_IPicture, (LPVOID*)&m_pPicture)))<br/> {<br/> short nType = PICTYPE_UNINITIALIZED;<br/> if (SUCCEEDED(m_pPicture->get_Type(&nType)) && (nType == PICTYPE_BITMAP))<br/> {<br/> OLE_HANDLE hBitmap;<br/> OLE_HANDLE hPalette;<br/> if (SUCCEEDED(m_pPicture->get_Handle(&hBitmap)) &&<br/> SUCCEEDED(m_pPicture->get_hPal(&hPalette)))<br/> {<br/> Attach((HBITMAP) hBitmap);<br/> m_Palette.Attach((HPALETTE) hPalette);<br/> bSuccess = TRUE;<br/> }</p><p></p><p> }<br/> }<br/> <br/> file://Free up the IStream* interface<br/> pStream->Release();<br/> <br/> return bSuccess;<br/>}<br/>这个class的完整代码请看文章最后的参考。<br/>子窗口管理<br/> 你也许注意过几乎所有游戏界面中的窗口都是使用动画的从屏幕外飞出(而且是半透明的,这你已经可以做到了)。游戏中一般都使用自己的UI系统。这里我们可以借助Windows对窗口的管理来轻松实现各种动画子窗口。<br/> 首先让我们从最简单的开始。假设在我们的动画窗口中需要一个漂亮的按钮怎么办,我劝你最好不要使用CBitmapButton,因为你已经上了每秒重画窗口16次以上这条贼船,我建议你在每次重画父窗口的时候重画所有子窗口,如此一来子窗口上如果要求有动画操作,也可以轻松实现了。既然做了,就把它做到最好。J<br/>那我们怎么定义一个button呢?你也许想到自己定义一个矩形区域,然后在父窗口的消息响应函数中检测是否是对此区域操作,这样在重画父窗口的时候特殊的画一次这个矩形区域就好了。这样是可以实现,但是显然不符合我们的OOP精神,界面元素一多,你很可能就会乱了阵脚。最后的解决方法当然是使用我们可爱的CWnd类,显然所有的界面元素都可以作为一个CWnd派生类的对象。不过我建议你不要从CButton派生,这带来的麻烦远多于它的价值。从CWnd派生一个类,然后在Create时注意使用WS_CHILD风格,并且指定父窗口为我们的动画窗口。<br/>下面一个问题是如何调用这些子窗口重画操作呢?第一种较好的解决方法是先建立这样一个虚基类:<br/>CmyAniWnd :public CWnd<br/>{<br/> …<br/> virtual void Render(CDC *pdc)=0;<br/> …<br/>} <br/>假设你有一个Button类和一个TextBox类:<br/>CmyButton : public CmyAniWnd<br/>CmyTextBox: public CmyAniWnd<br/>这两个类都必须实现Render函数,这样在父窗口类中你可以保存一个指针数组,例如这样:<br/>CPtrArray m_allchild;<br/>在创建一个Button时这样写:<br/>CmyButton *pbtn=new CmyButton;<br/>m_allchild.Add(pbtn);<br/>pbtn->Create(…);<br/>然后在我们父窗口的RenderView函数(前面提到的,每次更新调用)中这样写即可:<br/>CmyAniWnd *pchild=NULL;<br/>for(int I;I<m_allchild.GetSize();I++)<br/>{<br/> pchild=static_cast<CmyAniWnd*>(m_allchild.GetAt(i));<br/> ASSERT(::IsWindow(pchild->GetSafeHwnd());<br/> pchild->Render(&memdc);<br/>}<br/>这是一个典型的虚函数的应用,在调用这些子窗口的Render函数时,我们不需要知道它到底是Button还是TextBox,虚函数机制会自动帮我们找到该调用的函数。还有一点就是,请注意,一定要把子窗口渲染到我们的后台缓冲,也就是Memory DC中,否则还是会闪烁的。<br/>上面这种方法适合于子窗口数目固定,更高级的界面会要求触发某个事件的时候产生一个子窗口,子窗口不断更新自己,并且在适当的时候把自己从UI系统中去除。让每个子窗口管理自己的生命期,是个不错的主意,不是吗?那你最好不要使用上面保存指针数组的方法,那样的话,子窗口在杀死自己的时候还要通知父窗口,以让父窗口把它的指针从数组中移除,这显然具有很高的偶合性,不是我们想要的。因为我们的所有子窗口都是标准的Windows对象,所以这使得我们有使用Windows消息的机会。我们首先要枚举所有子窗口,然后发一个自定义的更新消息给它,并把我们的MemoryDC的指针作为参数,具体例子代码如下:<br/>void CMyView::RenderView()<br/>{<br/> …//其它更新操作<br/> ::EnumChildWindows(GetSafeHwnd(),CMyView::UpdateChildWnd,LPARAM(&memdc));<br/> …//其它更新操作<br/>}<br/>其中第二个参数是一个回调函数,你必须把它声明成全局函数,或者类的static成员函数,这里我们使用了后者。</p> |
|