源代码下载地址---------->第四集源代码
这一章的任务是学习相关的逻辑代数的知识,理解透明位图的原理,掌握透明位图
显示方法和一个简单的屏幕刷新的方法。
图4-1
位图拷贝中 BitBlt(⋯⋯)最后一个入口参数为“执行方式”,它可以是逻辑运算方式。
例如:
SRCCOPY 是我们前面用过的直接拷贝方式。
SRCAND 逻辑“与”运算,源图形和目标上的图形进行“与运算” ,结果在目标设备场景
中。
SRCPAINT 逻辑“或”运算,源图形和目标上的图形进行“或运算”,结果在目标设备场
景中。
透明显示位图就是用位图拷贝 BitBlt(⋯⋯)的逻辑操作实现的。
要形成透明位图的图形有一个特殊的要求,就是图形的背景必须是单一色的(图
4-2 就是符合要求的图形, 它的背景是统一的黑色)。生成透明位图还需要原图的掩码
位图,掩码位图就是原图的图形部分为原图的背景色一至,背景部分为原图背景的反
色(黑色的反色是白色)。有背景缕空了的对象图片和它的掩码位图,我们就可以运用
逻辑代数的知识来生成透明位图了。
这是背景缕空了的对象图片,它的背景是黑色(二进制数
表示为0),我们将它加到下面的场景中。
程序中将对象图片装入设备场景MEMDC1 的方法。
loadbmp("对象.BMP");
SelectObject(MEMDC1,bit);
图 4-2
这是上图原样形成的掩码位图。注意,原来的黑色背景
设为白色(二进制数表示为1),原来的对象形象一律变成黑
色(二进制数表示为0)。
程序中将掩码位图装入设备场景MEMDC0 的方法。
loadbmp("掩码.BMP");
SelectObject(MEMDC0,bit);
图4-3
这是我们要这群人去的地图背景图片。
程序中将背景图片装入设备场景MEMDCB 的方法。
loadbmp("背景.BMP");
SelectObject(MEMDCB,bit);
二进制数与(乘)运算的规则是:
0*0=0 见0 为0
0*1=0 见0 为0
1*0=0 见0 为0
1*1=1 全1 为1
0 乘任何数都是0, 1 乘任何数都不变。
掩码 MEMDC0 与地图背景MEMDCB“与运算”时;掩码中的0 (黑色) 使地图背景
中的对应位置变成0(黑色),0 乘任何数都是0;而掩码中的1(白色) 对地图背景中的对
应位置没影响,1 乘任何数都不变。所以掩码与地图背景“与运算”的结果就是仿佛用
对象的轮廓在地图背景中打洞。
处理方法: BitBlt(MEMDCB,x,y,w,h, MEMDC0,0,0,SRCAND);
掩码 MEMDC0 与地图背景MEMDCB “与运算”(SRCAND), 在MEMDCB 中形成“打洞背景”。
掩膜MEMDCB1 背景MEMDCB “打洞背景”MEMDCB
图4-5
二进制数或运算的规则是:
0+0=0 全0 为0
0+1=1 见1 为1
1+0=1 见1 为1
1+1=1 见1 为1
1 加任何数都是1, 0 加任何数都不变(注意,这与我们熟悉的十进制不一样)。
对象图片 MEMDC1 的背景中为全0 数(黑色),对象的形象是任意数。对象与打洞
背景“或运算”时;对象的背景0 对打洞背景毫无影响,0 加任何数都不变;对象的形
象正好处在打洞背景的黑洞0 处,0 加任何数都不变,所以也不变。这样我们就把这群
人搬到一个好地方了。
处理方法: BitBlt(MEMDCB,x,y,w,h, MEMDC1,0,0,SRCPAINT);
对象MEMDC1 与背景MEMDCB“或运算”( SRCPAINT), 在MEMDCB 中形成透明位图。
对象MEMDCB1 背景MEMDCB 背景MEMDCB,透明效果
图4-6
显示透明位图的算法如下:
MEMDCB 背景图片,MEMDC1 为背景缕空的透明显示对象,MEMDC0 为MEMDC1 的
掩膜。
BitBlt(MEMDCB,x,y,w,h, MEMDC0,0,0,SRCAND);
//掩膜和背景与操作,在MEMDCB 中生成“打洞背景”。
BitBlt(MEMDCB,x,y,w,h, MEMDC1,0,0,SRCPAINT);
//对象和背景或操作,在MEMDCB 中生成透明图。
由于透明显示位图在游戏中用得相当多,所以我们也把它做成一个C++的功能函数
TransparentBlt2()
//**************************************************
// TransparentBlt2 (......)透明显示
// 根据关键色,将hdc1 中的图形在hdc0 中自动生成掩模,并生成透明图形。
//**************************************************
void TransparentBlt2( HDC hdc0, // 目标DC
int nX0,int nY0,// 目标偏移
int nW0,int nH0,// 目标宽高度
HDC hdc1, // 源DC
int nX1,int nY1,// 源起点
int nW1,int nH1,// 源宽高度
UINT Tcol // 透明色,COLORREF 类型
) //透明显示
{//A.建立图形资源。
HBITMAP hBMP =CreateCompatibleBitmap(hdc0,nW0, nH0); //创建位图内存
HBITMAP mBMP =CreateBitmap(nW0,nH0,1,1,NULL); //创建单色掩码位图
HDC hDC = CreateCompatibleDC(hdc0); //创建设备场景
HDC mDC = CreateCompatibleDC(hdc0); //创建设备场景
HBITMAP oldBMP =(HBITMAP)SelectObject(hDC, hBMP);
HBITMAP oldmBMP=(HBITMAP)SelectObject(mDC, mBMP);
//B.拷贝或压缩拷贝源DC 中的位图到临时hDC 中。
if (nW0==nW1&&nH0==nH1) //源DC 宽、高与目标DC 一致
BitBlt(hDC,0,0,nW0,nH0,hdc1,nX1,nY1,SRCCOPY);//将源DC 的位图拷贝到临时hDC 中
else //源DC 宽、高与目标DC 不一致
StretchBlt(hDC,0,0,nW0,nH0,hdc1,nX1,nY1,nW1,nH1,SRCCOPY);
//将源DC 的位图拷贝到临时hDC 中
//C.生成掩码位图。
SetBkColor(hDC, Tcol);// 设置透明色
BitBlt(mDC,0,0,nW0,nH0,hDC,0,0,SRCCOPY);
//生成白色透明区,其它区为黑色的掩码位图
SetBkColor(hDC, RGB(0,0,0)); //生成黑色透明区,其它区域保持不变的位图
SetTextColor(hDC, RGB(255,255,255)); // 白色
BitBlt(hDC,0,0,nW0,nH0,mDC,0,0,SRCAND);
SetBkColor(hdc0,RGB(255,255,255)); // 透明部分保持屏幕不变,其它部分变成黑色
SetTextColor(hdc0,RGB(0,0,0)); // 黑色
//D.透明显示
BitBlt(hdc0,nX0,nY0,nW0,nH0,mDC,0,0,SRCAND); //“与”运算,在hdc0 生成掩模洞
BitBlt(hdc0,nX0,nY0,nW0,nH0,hDC,0,0,SRCPAINT);//“或”运算,生成最终透明效果
//E.释放图形资源
SelectObject(hDC, oldBMP);
DeleteDC(hDC);
SelectObject(mDC, oldmBMP);
DeleteDC(mDC);
DeleteObject(hBMP);
DeleteObject(mBMP);
}
在TransparentBlt2 中,我们用程序实现了由对象图片自动形成掩码位图,这样我们
就不用再去费力地对每幅对象图片做掩码了。
好,你只要把第三章中的时钟消息OnTimer()中的BitBil(… … )改成TransparentBlt2(… … ),
那个讨厌的框框就没有了。
BitBil(dc.m_hDC,20,20,w,h,MemDC,0,0,SRCCOPY); //角色拷贝
改成。
TransparentBlt2(dc.m_hDC,20,20,w,h,MemDC,0,0,RGB(0,0,0));//角色透明显示
注 : Windows API 函数有一个透明显示函数TransparentBlt(......), 功能和这里的
TransparentBlt2 相同。它在Windows2000 和以后的系统上可用。而在Windows98 中有严重
的资源泄漏,根本不能用(所谓的资源泄漏:你把TransparentBlt2 最后6 句注释掉就可看
到,机器很快就因资源耗尽,造成系统崩溃)。
如果你要在 Windows2000 上使用TransparentBlt(......),一定要引用:
#include <wingdi.h> //头文件
#pragma comment (lib,"msimg32.lib") //连接库
运行程序看看, 现在那个讨厌的框框已经没有了。但好像还不对,游戏角色多次
运动的影像叠加在一起了(见图4-7)。
图4-7
这好办, 在显示游戏角色的下一个动作影像时,我们用地图背景将上一个影像掩
盖就行了,这叫刷屏。
刷屏的实现方法。前面我们只用的一个设备场景 MemDC, 显示过地图后就用于人
物的显示了, 地图没有保留。现在我们在OnInitDialog()再创建一个地图设备场景
DCBak。
单独用于调入、显示地图。
⋯⋯
loadbmp(dir+"地面.BMP"); //调背景图片
SelectObject(DCBak,bit); //调入位图关联到地图设备场景
⋯⋯
原来的设备场景 MemDC 只用于调入显示人物。
这样,我们只要在时钟消息OnTimer()中加入用地图刷屏命令就行了。
BitBlt(dc.m_hDC,x,y,100,100,DCBak,x,y,SRCCOPY);
void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数,[类向导中定义生成]
{ CClientDC dc(this); //客户区设备环境
int x=200,y=200; //角色显示位置
BitBlt(dc.m_hDC,x,y,100,100,DCBak,x,y,SRCCOPY);
//用地图刷新窗口,将DCBak 指向的位图拷贝到dc.m_hDC 指向的当前窗口内存
if(getpic("人",p)==FALSE) //调角色图片
{AfxMessageBox(cc+"没找到!");return;}
SelectObject(MemDC,bit); //调入的位图关联到角色设备场景
TransparentBlt2(dc.m_hDC,x,y,w,h,MemDC,0,0,w,h,RGB(255,255,255));
p++; //下一动作
if(p>m1) p=m0; //若动作完成,重复。
CDialog::OnTimer(nIDEvent);
}
注意在“别给我框框Dlg.cpp” 的顶部全局定义处加上 。
地图设备场景变量定义;
⋯⋯
HDC DCBak; //地图设备场景
⋯⋯
透明显示函数定义;
⋯⋯
void TransparentBlt2( HDC hdc0, // 目标DC
int nX0,int nY0,// 目标偏移
int nW0,int nH0,// 目标宽高度
HDC hdc1, // 源DC
int nX1,int nY1,// 源起点
int nW1,int nH1,// 源宽高度
UINT Tcol // 透明色,COLORREF 类型
); //透明显示
现在再编译运行程序,成了。游戏的主角就与背景自然地融合在一起了。
图4-8
到现在为止我们已经编译了三个程序了。你知道吗?在VC++中有两种不同的编译
模式。
调试模式是我们在编制一个程序时常用到的一种必不可少的模式。它的作用是,
我们可以在程序的运行中设置断点,逐步跟踪一些我们需要的数据,进行程序的错误
诊断和排除(程序调试方法见附录A)。但调试模式生成的运行程序尺寸比较大,不能
作为最终程序给用户使用。
这是我们编制并调试完成后向用户提供使用的一种运行程序的编译模式。它的特
点是,运行程序尺寸小,并且是优化了的。我们最终向用户提供的运行程序一定是
Release 发布模式。
在 VC 编程环境中,选择Build 下的Set Actve Configuration⋯进入Set Active Project
Configuration 页面,可以选择VC 的编译模式。
在 Set Active Project Configuration 页面上选择DEBUG 调试模式或Release 发布模式。
DEBUG 模式或Release 模式编译后生成的目标程序,分别在源程序当前目录下
DEBUG 或Release 目录中。有时我们需要给它们另外安排目录。
在 VC 编程环境中,选择菜单Project 下的Settings 进入到Project Settings,在其中的
Link 栏中可分别对DEBUG 调试模式、Release 发布模式的目标程序设定存贮目录。这里
我们为名为“别给我框框” 这个程序设定的 Release 模式的目标程序目录为,“../ 运
行程序/03.别给我框框.exe”
图4-10
程序流程主要分三块:程序开始执行时的初始化, 按键后设置参数和启动时钟函
数,由时钟函数和调图形函数构成的程序主循环。
注意,在程序主循环的中与上一章不同之处;一是在对象显示时调用了透明显示
函数,二是在时钟函数中加入用地图设备场景DCBak 刷屏的命令。
图4-11 主流程图
详细内容请看本章实例程序:“别给我框框”。
在这一章,我们学了以下知识和方法。
1.逻辑与运算 0*0=0;0*1=0;1*0=0;1*1=1。即见 0 为0; 全1 为1。
0 乘任何数都是0, 1 乘任何数都不变。
2.逻辑或运算 0+0=0;0+1=1;1+0=1;1+1=1。即见 1 为1; 全0 为0。
0 加任何数都不变,1 加任何数都是1。
3.学习了利用位图拷贝BitBlt()的逻辑运算生成透明位图的方法。
4.介绍了一个最简单的刷屏方法。
5.介绍了VC 的编译模式的设置方法。