源代码下载地址---------->第六集源代码
1.学习C++“ 类”的创建和使用方法。
2.在场景中加入景物。
3.解决对象的相互遮挡关系。
打开 VC 集成编程器。在菜单中选“file→new”进入新建文件。在Files 中选择“C++
Source Files” 建立一个新的源文件(.cpp) ,注意输入文件名。按OK 键后,我们就得到
了一个名为game.cpp 的一个空文件。
图6-1
在game.cpp 中加入:
#include "stdafx.h"
#include "game.h"
game::game() //构造函数
{}
game::~game() //析构函数
{}
同样打开VC 集成编程器。在菜单中选“file→new”进入新建文件。在Files 中选择
“C/C++ Header Files”建立一个新的头文件(.h),注意输入文件名。按OK 键后,我们就
得到了一个名为game.h 的一个空文件。
图6-2
在game.h 中加入
class game //类名
{public:
game(); //构造函数
virtual~game();//析构函数
public: //公有类型,外部可调用。
⋯⋯
private: //私有类型,类内部使用。
⋯⋯
protected: //保护类型,派生类可调用。
⋯⋯
};
这样,一个类文件就建立好了,它现在可以正确地编译了。当然现在它是个空的
类文件。
下面我们就可以将前面做的功能函复制过来。这里作为例子,我们将前面的loadbmp
(CString cc)调图片加入到这个类文件中。
A.game.h 头文件
在 game.h 头文件中加入函数的说明(见黑体字部分)。
class game //类名
{public:
game(); //构造函数
virtual~game();//析造函数
public: //公有,外部可调用
//定义类变量
HBITMAP bitmap; //定义位图句柄
int w,h; //对象图片宽、高
//定义类函数
loadbmp (CString cc); //调图片
private: //私有,类内部使用
};
其中:
public: 公有类型,引导说明的变量、函数可以被类外的其它程序调用。
private: 私有类型,引导说明的变量、函数只能在类内部使用。
protected:保护类型,引导说明的变量、函数可以在派生类中调用。
B.game.cpp 类文件
在 game.cpp 源文件中加入函数的主体(见黑体字部分)。
#include "stdafx.h"
#include "game.h"
game::game() //构造函数
{}
game::~game() //析构函数
{}
//**************************************************
// loadbmp(CString cc)//调BMP 图片
// 调cc 指定的图形;取得的图形在设备相关位图bit 中
// 图形的宽、高存于全局变量w,h 中
//**************************************************
BOOL game::loadbmp(CString cc)//调BMP 图片
{ DeleteObject(bitmap); //删除上次的位图内存。
bitmap=(HBITMAP)LoadImage //调入cc 指定的图形
(AfxGetInstanceHandle(),//
cc,IMAGE_BITMAP, //文件名、位图方式
0,0, //图形宽、高
LR_LOADFROMFILE|LR_CREATEDIBSECTION//方式
);
if(bitmap==NULL) return FALSE; //调图失败
DIBSECTION ds; //
BITMAPINFOHEADER &bm = ds.dsBmih;//
GetObject(bitmap,sizeof(ds),&ds);//取位图的信息→bminfo
w = bm.biWidth; //得到位图宽度值
h = bm.biHeight; //得到位图高度值
return TRUE;
}
照此方法,将前面其它几个功能函数加入到game 类中。
C.类的使用
类文件 game.cpp 中的功能函数在我们的程序的其它任何地方都可以方便地调用。
方法如下:
前面我们的程序主要是写在视图类 XXXXDlg.cpp 中的,现在我们在XXXXDlg.cpp 中
调用game.cpp 中的功能函数。
在 XXXXDlg.cpp 的头文件XXXXDlg.h 中定义类变量(见黑体字部分)。
#include "game.h" // game 类的引用说明
class CMyDlg : public CDialog
{ public:
CMyDlg(CWnd* pParent = NULL); // standard constructor
game m_game; // 定义对象变量( 调用名)
⋯⋯
}
在 XXXXDlg.cpp 的任何地方都可以用。
m_game.loadbmp ("c:\0010.bmp"); //调用game.cpp 的功能函数。
int Wie=m_game.w; //取game.cpp 中类变量的值。
详细情况见本章实例“穿越丛林”中的game.cpp 和game.h。
在这一章的第二个任务是要在场景中加入游戏角色以外的其它东西, 这里我们加
入一棵树、一块石头。
图6-3
1.地图文件*.dat
在数据文件 game.dat 中安排了游戏角色、场景的初始数据。
地面.BMP
数据意义见下表。这些数据可以是直接在game.dat 中输入,也可以是游戏的地图
编辑器编辑的结果。
背景名 地面.BMP
对象数 3
第0 个人 0, 0, 0, 1, 234, 260,
第1 个树 1, 2, 0, 31, 269, 309,
第2 个石 2, 2, 0, 8, 207, 255,
结构分量名 jisu lb js p xix xiy
中文说明 序号 类别 角色 图形号 坐标
2.loadmap(⋯) 对象初始化
我们在 game 类文件game.cpp 中建立一个loadmap(CString name) 对象初始化功能函数;读入dir 目录中game.data 数据,完成rs 个对象man[]的初始化。这是上一章已用过
的读入外部文件的程序。
loadmap(CString name) 对象初始化程序如下:
//**************************************************
// loadmap(CString name)// 对象初始化
// 调入编制好的对象场景(.dat)
// 场景文件(.dat)的格式:
// 第1 行,为背景图形
// 第2 行,对象数,后面一行是一个对象。
// 序号,类别,角色,静物图形号,x,y 位置6 个数据
//**************************************************
void game::loadmap(CString name)//调地图
1{ FILE *f;
2 f=fopen(name,"r");
3 if(f==NULL){
AfxMessageBox("没有地图文件!!!");
SetCurrentDirectory(appdir); //置当前目录
return;}
4 fscanf(f,"%s\n",mapbak); //读地图块名
5 fscanf(f,"%d\n",&rs); //读对象数
6 if(rs>SU_LIANG){ //对象数大于设定值返回
7 fclose(f);
8 SetCurrentDirectory(appdir); //置当前目录
9 return; }
10 for (int i=0;i<rs;i++)
11 {fscanf(f,"%d,",&man[i].jisu); //读序号
12 fscanf(f,"%d,",&man[i].lb); //读类别: [0 人1 兽2 景]
13 fscanf(f,"%d,",&man[i].js); //读角色:人[0 男1 女]
// 兽[0 豹1 狼2 猪3 鹿4 马5 雀6 羊]
14 fscanf(f,"%d,",&man[i].p); //读静物图形号
15 fscanf(f,"%d,",&man[i].xix); //读当前位置x
16 fscanf(f,"%d,",&man[i].xiy); //读当前位置y
17 man[i].x0=man[i].xix; //设目标位置x
18 man[i].y0=man[i].xiy; //设目标位置y
19 man[i].fw=1; //设方位: [0 南1 西南⋯⋯]
20 man[i].zs=0; //设动作:人[0 站1 走2 刺3 劈4 倒]
// 兽[0 站1 走2 跑3 斗4 倒5 尸]
21 man[i].pk =-1; //设路径长
22 man[i].fid=-1; //设路径计数
23 man[i].zd=0;
24 if(man[i].lb!=2) setman(man[i].jisu); //设置活动对象初值
25 getobjwh(i); //取对象的尺寸
}
26 fclose(f);
27 SetCurrentDirectory(appdir); //置当前目录
}
第1~ 3 行,打开名为name 的地图文件,出错返回。
第4 行,读地图块名
第 5 行,读对象数rs
第 6~ 9 行,对象数大于设定值,关闭文件返回。
第 10~25 行,读入rs 个对象的的特征值;其中第17~23 行为初始化数据,第25
行取对象的尺寸。
第 26 行,关闭文件
第 27 行,置当前目录
注意必须在 game.h 中加入函数loadmap(CString name)的定义。
⋯⋯
public://公有,外部可调用
void loadmap(CString name); //对象初始化
⋯⋯
3.在主程序中加入对象初始化
这样在 XXXXDlg.cpp 的BOOL CMyDlg::OnInitDialog()初始入口中加入以下语句就可对游戏的角色场景进行初始化了。
//C.初始化类
m_game.init();
//D.对象初始化
cc=m_game.appdir;
if(cc.Right(8)=="运行程序")
cc= "地图/game0.dat";
else cc="../运行程序/地图/game0.dat"; //地图(场景)文件路径
m_game.loadmap(cc); //对象初始化
//E.调入显示背景
cc.Format("%s%s",m_game.dir,m_game.mapbak); // mapbak 在对象初始化中赋值
m_game.loadbmp(cc); //调背景图片
SelectObject(m_game.BkDC0,m_game.bitmap); //背景位图关联到背景设备场景
现在我们 游戏场景中已不只是一个对象了,树、石等也是对象,后面我们还要加
入其它动物。所以现在在一个时钟周期里,显示的就是多个对象了( 在后面的实例中,
我们将定义场景中可同时显示500 个对象)。为了简化OnTimer()中的代码复杂性,我们
将一些功能整合在函数setobj(int q) 对象显示中,并将它也写在类文件game.cpp 里。
1.setobj(int q) 对象显示
//**************************************************
// setobj(int q) 对象显示
// 调入由man[q].lb 指出的不同的man[q].p 图片和偏移值。
// 将对象q 在各自的当前位置(x,y)上以透明方式TransparentBlt2 显示。
// 然后进行移动或方位转换的功能。
// 为防止闪烁,所有对象先显示在暂存区BkDC1,一屏完成后再翻转到主屏上。
//**************************************************
void game::setobj(int q)//对象显示
{ CString cc;
int x=man[q].xix-scrx-man[q].w/2; //x 当前位置
int y=man[q].xiy-scry-man[q].h; //y 当前位置
if(inscreer(q)) //对象q 在显示区才显示它
{if(man[q].lb==0) {cc="人";}
if(man[q].lb==1) {cc="兽";}
if(man[q].lb==2) {cc="景";}
if(getpic(cc,man[q].p)==FALSE) return; //调图形
if(man[q].lb!=2) //调角色的偏移位置
{int x0=0,y0=0;
if(man[q].lb==0) {x0=rbufx[man[q].p];y0=rbufy[man[q].p];}
if(man[q].lb==1) {x0=sbufx[man[q].p];y0=sbufy[man[q].p];}
if(man[q].fw>4) x0=w-x0; //是东北、东、东南方位
x=man[q].xix-scrx-x0;
y=man[q].xiy-scry-y0;
}
TransparentBlt2(BkDC1,x,y,w,h,MemDC,0,0,w,h,RGB(255,255,255));
mans++;
}
if(man[q].lb==2) return;
else manmove(q); //活动对象的移动
man[q].p++; //下一动作
if(man[q].p>=man[q].m1) {bianfw(q);} //本动作完成,进行方位转换
}
2.修改主程序的对应处
引入 setobj(int q) 对象显示后, 在OnTimer(⋯)中多对象(对象数为rs) 显示如下:
void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数,[类向导中定义生成]
{ CClientDC dc(this); //客户区设备环境
BitBlt(m_game.BkDC1,0,0,rect.Width(),rect.Height(),
m_game.BkDC0,0,0,SRCCOPY); //用地图刷新Bk1
for(int i=0;i<m_game.rs;i++)
m_game.setobj(i); //对象显示
BitBlt(dc.m_hDC,0,0,rect.Width(),rect.Height(),
m_game.BkDC1,0,0,SRCCOPY); //将Bk1 的内容显示在当前窗口
CDialog::OnTimer(nIDEvent);
}
由于是多对象显示,现在我们需要将按左键OnLButtonDown(… )略改一下;使按
左键时只对游戏的主角起作用(这是我们这个游戏定义所要求的)。
void CMyDlg::OnLButtonDown(UINT nFlags, CPoint point)
{ int x=point.x,y=point.y;
for(int i=0;i<m_game.rs;i++)
if(m_game.man[i].jisu==0) //只对主角
{m_game.man[i].x0=x; //获得目标位置x
m_game.man[i].y0=y; //获得目标位置y
m_game.man[i].p=m_game.man[i].m1-1; //中止当前动作
break;
}
CDialog::OnLButtonDown(nFlags, point);
}
解决对象 遮挡的方法有多种, 我们这里给出一种最简单但实用的方法。从上面程
序看到, 我们是在一个循环中按对象的序号来显示对象的,必然就有先显示的内容要
被后面的内容遮挡。人[0]最先显示,然后是树[1] , 石头[2]最后显示(见图6-4),结果
是先显示的被后显示的遮住了。
图6-4
事实上,我们的游戏画面是上远、下近;Y 坐标是上小、下大。这里应该是人在树
与石头之间,有人遮石,树遮人的现象出现。
我们观察对象的显示位置:石头(207, 255)、树(269 , 309)、人(234, 260) 。
从 Y 坐标看有:树(269, 309)>人(234, 260)>石(207, 255) 。
这就给我们提示了一种解决问题的方法:我们在显示对象前,按对象当前显示位
置的Y 坐标进行排序,使Y 坐标最小的对象,也就是远的对象先显示,这样就可以形
成下遮上、近遮远的效果。好,下面我们来做一个按对象的显示位置Y 坐标排序的功
能函数。
排序是程序中常用的算法。冒泡法是排序算法中最简单、最好理解的一种。因为
对游戏中几百个对象排序,排序算法的效率不很重要 (在我们这个游戏中也做到500 个
对象,这已是不少的了) ,所以用冒泡法排序也就够了。
下面是将 q 个对象序列对Y 位置进行排序的功能函数sort(int q),我们也把它写在
game 类中。
注:这是一个标准的冒泡法排序程序,在本例中的特别点是比较、交换的数据。
//**********************************************************
// sort() 排序(冒泡法)
// 将rs 个对象序列对Y 位置进行排序,用于在显示时分出前后位置。
//**********************************************************
void game::sort()//排序(冒泡法)
1{ MAN man0;
2 for(int a=0;a<rs-1;a++)
3 for(int b=a+1;b<rs;b++)
4 if(man[a].xiy>man[b].xiy) //前数大于后数
5 {man0=man[a]; //两数交换
6 man[a]=man[b];
7 man[b]=man0;
8 } //排序结果,当前位置Y 小的在前。
9 mans=0; //显示区对象数置0
}
同样在game.h 中要加入sort() 的定义。
冒泡法排序的基本思想是:
在第 2、3 行for 的双循环中进行排序。
第 4 行将第一个数a=0 与后面的所有数进行比较,如果大于某个数,就与它们的位
置进行交换(5、6、7 行)。如果这个数是排序序列中的最大数,则交换的结果是它将沉
到最底下(如果是小于比较数,最小的数将冒到最上面,所以称冒泡法)。
第一个数完后又将第二个数与后面的数同样处理,直到倒数第二个数q-1。我们这
时交换的数不只是Y 坐标,而是对象的整个数据,好在VC++可以对结构进行简单的赋
值运算(man[a]=man[b] 是 man[a] 的所有分量等于man[b]的对应分量)。
排序结果是当前位置 Y 小的对象man[]在前面了。
现在将 sort()排序加入到用于对象显示的时钟函数中。在显示前,先对对象位置的
Y 坐标排序,让Y 坐标小的(远的、上面的)先显示。
void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数,[类向导中定义生成]
{ CClientDC dc(this); //客户区设备环境
BitBlt(m_game.BkDC1,0,0,rect.Width(),rect.Height(),m_game.BkDC0,0,0,SRCCOPY);
//用地图刷新Bk1
m_game.sort(); //对对象的Y 坐标排序
for(int i=0;i<m_game.rs;i++)
m_game.setobj(i); //对象显示
BitBlt(dc.m_hDC,0,0,rect.Width(),rect.Height(),m_game.BkDC1,0,0,SRCCOPY);
//将Bk1 的内容显示在当前窗口
CDialog::OnTimer(nIDEvent);
}
结果为图6-5。所示画面上为人遮石、树遮人;但人只要再向下走, 就人遮树了。
OK! 视觉上符合逻辑。
图6-5
在这一章我们开始引入了 C++的类库,并将游戏的功能写在这个类库中。后面我们
还要在类库中增加更多的功能。所以我们在这里列出这个类库的源程序game.cpp 和它
的头文件game.h ,还有它引用的常数定义.h ,要注意它们的组成结构。
以下 game.cpp 是一个完整的程序,结构顺序上是连续的。它包含了1 个常数定义
和14 个功能函数。为了更清楚地表示这个程序,我们将它们隔开,加上了小标题(加
灰的部份是本章未用的)。
1.常数定义
#include "stdafx.h"
#include "game.h"
JCDZ zjdz[6]={0,5,40,10,120,10,200,10,280,10,360,5};//人动作结构400 幅
//0 站,1 走, 2 刺, 3 劈, 4 倒 5 尸
JCDZ zjdw[6]={0,5,40,10,120,10,200,10,280,10,360,5};//兽动作结构400 幅
//0 站,1 走, 2 跑, 3 斗, 4 倒, 5 尸
unsigned short dw[7][6]={0,1,1,3,4,5, //0 豹,无跑
0,1,2,3,4,5, //1 狼,
0,1,1,3,4,5, //2 猪,无跑
0,1,2,3,4,5, //3 鹿,
0,2,2,2,4,5, //4 马,无走、斗
0,1,1,1,4,5, //5 羊,无跑、斗
0,1,1,3,4,5 //6 雀,无跑
};
game:: game(){} //构造函数
game::~game(){} //析构函数
2.init()初始化
//****************************************************************
// init() 初始化
//1.建立图形处理环境
// BkDC0-装载背景地面
// BkDC1-在此生成即时场景,在1 个时钟周期后翻转到当前显示屏。
// MemDC-调入的对象图片。
//2.设置图片目录
//3.初始一些变量
//****************************************************************
BOOL game::init()//初始化[6 章]
{//1.建立图形处理环境
hScrDC=CreateDC("DISPLAY", NULL, NULL, NULL); //创建屏幕设备场景
BkDC0 =CreateCompatibleDC(hScrDC); //创建地图设备场景
BkDC1 =CreateCompatibleDC(hScrDC); //创建暂存设备场景
bit0 =CreateCompatibleBitmap(hScrDC,WIDTH,HEIGHT);//创建暂存位图0
bit1 =CreateCompatibleBitmap(hScrDC,WIDTH,HEIGHT);//创建暂存位图1
SelectObject(BkDC1,bit1); //暂存位图1 与暂存设备关
联
OldMak=(HBITMAP)SelectObject(BkDC0,bit0); //暂存位图0 与地图设备关
联
MemDC =CreateCompatibleDC(hScrDC); //创建对象设备场景
//2.设置图片目录
GetCurrentDirectory(256,appdir); //取当前目录
dir=appdir;
if(dir.Right(8)=="运行程序")
dir="图片/";
else dir="../运行程序/图片/"; //图片路径
//3.初始一些变量
sys=getsys(); //取当前系统,( Win2000 、
Win98)
scrx=0;scry=0; //
rs=0; //对象数置0
return TRUE;
}
3.exit()退出
//**************************************************
// exit() 退出, 删除图形处理环境
//**************************************************
void game::exit()//退出
{ DeleteObject(bit0); //删除暂存位图内存
DeleteObject(bit1); //删除暂存位图内存
DeleteDC(BkDC0); //删除地图设备场景
DeleteDC(BkDC1); //删除暂存设备场景
DeleteDC(MemDC); //删除对象设备场景
DeleteDC(hScrDC); //删除屏幕设备场景
}
4.getsys()识别操作系统
//**************************************************** // getsys()//识别操作系统 // 使用TransparentBlt()透明显示的需要; // 是WIN2000,直接调用TransparentBlt(); // 否则调用自编的TransparentBlt2()。 // 返回系统类型编号。 //**************************************************** int game::getsys()//识别操作系统 { OSVERSIONINFO stOSVI ; ZeroMemory(&stOSVI , sizeof ( OSVERSIONINFO )) ; stOSVI.dwOSVersionInfoSize = sizeof ( OSVERSIONINFO ) ; GetVersionEx ( &stOSVI); int a=0; if (stOSVI.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS &&(stOSVI.dwMajorVersion>4||(stOSVI.dwMajorVersion==4 && stOSVI.dwMinorVersion>0))) a=1; // "Windows98"; if (VER_PLATFORM_WIN32_NT==stOSVI.dwPlatformId&&stOSVI.dwMajorVersion>=5) a=2; // "Windows2000"; if (VER_PLATFORM_WIN32_NT==stOSVI.dwPlatformId&&stOSVI.dwMajorVersion==4) a=3; // "WindowsNT4.0"; if (VER_PLATFORM_WIN32_NT==stOSVI.dwPlatformId) a=4; // "WindowsNT"; return a; }
5.inscreer(int i)判断在显示区
//********************************************************** // inscreer(int i)在显示区? // 判断对象是否在显示区 //********************************************************** BOOL game::inscreer(int i)//在显示区? { int xs=man[i].xix-scrx-man[i].w/2; //x 当前位置 int ys=man[i].xiy-scry-man[i].h; //y 当前位置 if(xs>(-man[i].w+2)&&xs<WIDTH&&ys>(-man[i].h+10)&&ys<HEIGHT) return TRUE; else return FALSE; }
6.sort() 排序(冒泡法)
//********************************************************** // sort() 排序(冒泡法) // 将rs 个对象序列对Y 位置进行排序;用于在显示时分出前后位置。 //********************************************************** void game::sort()//排序(冒泡法) { MAN man0; for(int a=0;a<rs-1;a++) for(int b=a+1;b<rs;b++) if(man[a].xiy>man[b].xiy)//前数大于后数 {man0=man[a]; //两数交换 man[a]=man[b]; man[b]=man0; } //排序结果,当前位置Y 小的在前。 mans=0; //显示区对象数置0 }
7.getobjwh(int q) 取对象的尺寸
//************************************************** // getobjwh(int q) 取对象的尺寸 // 取序号q 对象的尺寸(宽man[q].w 高man[q].h)。 //************************************************** void game::getobjwh(int q)//取对象的尺寸 { CString cc; if(man[q].lb==0) {cc="人";} if(man[q].lb==1) {cc="兽";} if(man[q].lb==2) {cc="景";} if(getpic(cc,man[q].p)==FALSE) return; man[q].w=w; man[q].h=h; //对象的尺寸 }
8.TransparentBlt2 (......)透明显示
//************************************************** // TransparentBlt2 (......)透明显示 // 根据关键色,将hdc1 中的图形在hdc0 中自动生成掩模,并生成透明图形。 //************************************************** void game::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 章] {//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); }
9. setman(int q) 设置活动对象初值
//************************************************** // setman(int q) 设置活动对象初值 // 由给定的对象、方位、动作计算出图形的位置和数量。 //************************************************** void game::setman(int q)//设置活动对象初值 { int a=400; if(man[q].lb==0) //是人 {man[q].m0=man[q].js*a+zjdz[man[q].zs].qi +man[q].fw*zjdz[man[q].zs].bc; //位置初值 man[q].m1=zjdz[man[q].zs].bc+man[q].m0; //位置终值 man[q].p=man[q].m0; //数量计数 } if(man[q].lb==1) //是兽 {man[q].m0=man[q].js*a+zjdw[man[q].zs].qi +man[q].fw*zjdw[man[q].zs].bc; //位置初值 man[q].m1=zjdw[man[q].zs].bc+man[q].m0; //位置终值 man[q].p=man[q].m0; //数量计数 } man[q].zd=0; }
10.manmove(int i)活动对象的移动
//************************************************** // manmove(int i) 活动对象的移动 // 由当前、目标位置的差,计算当前位置向不同方位的改变。 //************************************************** void game::manmove(int i)//活动对象的移动 { if(man[i].lb==2) return; //是静物返回 int stx,sty,qx,qy; switch(man[i].zs) {case 2: {stx=9;sty=6;break;} //跑步长 case 1: {stx=4;sty=2;break;} //走步长 default:{stx=2;sty=1;break;} } qx=man[i].x0-man[i].xix; //x 当前、目标位置差 qy=man[i].y0-man[i].xiy; //y 当前、目标位置差 if (qx==0&&qy==0) return ; //到达返回 int qxa=abs(qx); //x 位置差绝对值 int qya=abs(qy); //y 位置差绝对值 if(qxa<stx) stx=qxa; //位置差不足步长,步长为差 if(qya<sty) sty=qya; // if(qx!=0) man[i].xix+=qxa/qx*stx; //当前位置加步长 if(qy!=0) man[i].xiy+=qya/qy*sty; //[qya/qy]单位绝对值 }
11.bianfw(int q)方位转换
//************************************************** // bianfw(int q)//方位转换 // 由当前、目标位置的差,计算活动图形的方向取向。 //************************************************** void game::bianfw(int q)//方位转换 { int qx=man[q].x0-man[q].xix; //x 当前目标位置差 int qy=man[q].y0-man[q].xiy; //y 当前目标位置差 if(qx==0&&qy==0) {if(man[q].zd==0) man[q].zs=0; //为0,动作为站,方位保留 goto aa; } if(man[q].zd==0) //没打 {int a=rand()%2+1; //随机产生走、跑 if(man[q].lb==0) man[q].zs=1; //是人设为走 if(man[q].lb==1) man[q].zs=dw[man[q].js][a];//是兽 } if(qx<0&&qy>0) {man[q].fw=1;goto aa;} //取西南向 if(qx<0&&qy<0) {man[q].fw=3;goto aa;} //取西北向 if(qx>0&&qy<0) {man[q].fw=5;goto aa;} //取东北向 if(qx>0&&qy>0) {man[q].fw=7;goto aa;} //取东南向 if (qy>0) {man[q].fw=0;goto aa;} //取南向 if(qx<0) {man[q].fw=2;goto aa;} //取西向 if (qy<0) {man[q].fw=4;goto aa;} //取北向 if(qx>0) {man[q].fw=6;goto aa;} //取东向 aa: setman(q); //设置活动对象初值 if(man[q].zs==0) man[q].p=man[q].p+rand()%3; //避免动作一致 }
12.loadmap(CString name) 对象初始化
//************************************************** // loadmap(CString name)//对象初始化 // 调入编制好的对象场景(.dat) // 场景文件(.dat)的格式: // 第1 行,为背景图形 // 第2 行,对象数,后面一行是一个对象。 // 序号、类别、角色、静物图形号x,y 位置6 个数据 //************************************************** void game::loadmap(CString name)//调地图 { FILE *f; f=fopen(name,"r"); if(f==NULL) {AfxMessageBox("没有地图文件!!!"); SetCurrentDirectory(appdir); //置当前目录 return; } fscanf(f,"%s\n",mapbak); //读地图块名 fscanf(f,"%d\n",&rs); //读对象数 if(rs>SU_LIANG) //对象数大于设定值返回 {fclose(f); SetCurrentDirectory(appdir); //置当前目录 return; } for (int i=0;i<rs;i++) {fscanf(f,"%d,",&man[i].jisu); //读序号 fscanf(f,"%d,",&man[i].lb); //读类别:[0 人1 兽2 景] fscanf(f,"%d,",&man[i].js); //读角色:人[0 男1 女] // 兽[0 豹1 狼2 猪3 鹿4 马5 雀6 羊] fscanf(f,"%d,",&man[i].p); //读静物图形号 fscanf(f,"%d,",&man[i].xix); //读当前位置x fscanf(f,"%d,",&man[i].xiy); //读当前位置y man[i].x0=man[i].xix; //设目标位置x man[i].y0=man[i].xiy; //设目标位置y man[i].fw=1; //设方位:[0 南1 西南2 西3 西北⋯] man[i].zs=0; //设动作:人[0 站1 走2 刺3 劈4 倒] // 兽[0 站1 走2 跑3 斗4 倒5 尸] man[i].pk =-1; //设路径长 man[i].fid=-1; //设路径计数 man[i].zd=0; if(man[i].lb!=2) setman(man[i].jisu); //设置活动对象初值 getobjwh(i); //取对象的尺寸 } fclose(f); SetCurrentDirectory(appdir); //置当前目录 }
13.getpic(CString cc,int p) 调图片到相关位图
//************************************************** // getpic(CString cc,int p) 调图片到相关位图 // 由p 得到将调的图形文件名。 // 在指定目录中调入图形到相关位图bit //************************************************** BOOL game::getpic(CString cc,int p)//调图片到相关位图 { char name[256]; if(stmp==NULL) //没有压缩资源 {SetCurrentDirectory(appdir); //置当前目录 //A.调cc 指定的图形 sprintf(name,"%s%s/c%05d.bmp",dir,cc,p); //生成将调的图形文件名 if(!loadbmp(name)) return FALSE; //调BMP 图片 SelectObject(MemDC,bitmap); //B.调cc 指定的图形的偏移值。 sprintf(name,"%s%s/c%05d.txt",dir,cc,p); FILE *f; f=fopen(name,"r"); if(f!=NULL) {if(cc=="人") fscanf(f,"%d,%d",&rbufx[p],&rbufy[p]); //人的偏移量 if(cc=="兽") fscanf(f,"%d,%d",&sbufx[p],&sbufy[p]); //兽的偏移量 fclose(f); return TRUE; } } else {if(getpic0(cc,p)) return TRUE; //调压缩图片。 else return FALSE; } }
14.loadbmp(CString cc) 调BMP 图片
//************************************************** // loadbmp(CString cc) 调BMP 图片 // 调cc 指定的图形;取得的图形在设备相关位图bit 中 // 图形的宽、高存于全局变量w,h 中 //************************************************** BOOL game::loadbmp(CString cc)//调BMP 图片 { DeleteObject(bitmap); //删除上次的位图内存。 bitmap=(HBITMAP)LoadImage //调入cc 指定的图形 (AfxGetInstanceHandle(),// cc,IMAGE_BITMAP, //文件名,位图方式 0,0, //图形宽,高 LR_LOADFROMFILE|LR_CREATEDIBSECTION//方式 ); if(bitmap==NULL) return FALSE; //调图失败 DIBSECTION ds; // BITMAPINFOHEADER &bm = ds.dsBmih; // GetObject(bitmap,sizeof(ds),&ds); //取位图的信息→bminfo w = bm.biWidth; //得到位图宽度值 h = bm.biHeight; //得到位图高度值 return TRUE; }
15.setobj(int q) 对象显示
//************************************************** // setobj(int q) 对象显示 // 调入由man[q].lb 指出的、不同的man[q].p 图片和偏移值。 // 将对象q 在各自的当前位置(x,y)上以透明方式TransparentBlt2 显示。 // 然后进行移动或方位转换的功能。 // 为防止闪烁,所有对象先显示在暂存区BkDC1,一屏完成后再翻转到主屏上。 //************************************************** void game::setobj(int q)//对象显示 { CString cc; int x=man[q].xix-scrx-man[q].w/2; //x 当前位置 int y=man[q].xiy-scry-man[q].h; //y 当前位置 if(inscreer(q))//在显示区? {if(man[q].lb==0) {cc="人";} if(man[q].lb==1) {cc="兽";} if(man[q].lb==2) {cc="景";} if(getpic(cc,man[q].p)==FALSE) return; if(man[q].lb!=2)//调角色的偏移位置 {int x0=0,y0=0; if(man[q].lb==0) {x0=rbufx[man[q].p];y0=rbufy[man[q].p];} if(man[q].lb==1) {x0=sbufx[man[q].p];y0=sbufy[man[q].p];} if(man[q].fw>4) x0=w-x0; //是东北、东、东南方位 x=man[q].xix-scrx-x0; y=man[q].xiy-scry-y0; } TransparentBlt2(BkDC1,x,y,w,h,MemDC,0,0,w,h,RGB(255,255,255)); mans++; } if(man[q].lb==2) return; else manmove(q); //活动对象的移动 man[q].p++; //下一动作 if(man[q].p>=man[q].m1) {bianfw(q);} //本动作完成,进行方位转换 }
以下 game.h 结构在顺序上是连续的。为了更清楚地表示,我们将它们隔断,并加
上了小标题(加灰的部份是本章未用的)。
1.定义结构
#include "常数定义.h" //定义结构 typedef struct { short int x; short int y; } PATHN;//搜索整理路径 typedef struct { short int qi; //动作起点 short int bc; //动作步数 } JCDZ; //人或兽的动作结构 typedef struct { short int jisu; //序号 short int xix,xiy;//角色坐标 short int x0,y0; //目标位置 short int w,h; //对象尺寸 short int lb; //类别: [0 人1 兽2 景] short int p; //计数 //以下人、动物使用 short int m0,m1; //位置初值、终值 short int zs; //动作:人[0 站1 走2 刺3 劈4 倒] // 兽[0 站1 走2 跑3 斗4 倒5 尸] short int js; //角色:人[0 男1 女] // 兽[0 豹1 狼2 猪3 鹿4 马5 雀6 羊] short int fw; //方位:[0 南1 西南2 西3 西北4 北5 东北6 东7 东南] short int zd; //当前状态[0,1 打斗] //以下搜索使用 PATHN ph[250];//搜索的路径 short int pk; //路径长 short int fid; //路径计数 short int fx,fy; //保留目标点 } MAN; //对象结构
2.定义类
//定义类
class game {public: game(); //构造函数 virtual~game(); //析构函数
3.定义变量
//变量定义
public://公有,外部可调用 MAN man[SU_LIANG]; //定义对象变量,数量为SU_LIANG HDC BkDC0; //地图设备场景 HDC BkDC1; //暂存设备场景 HDC MemDC; //对象设备场景 HBITMAP OldMak; HDC hScrDC; //屏幕设备场景 HBITMAP bitmap; //地图位图内存 CString dir; //数据路径 short int rs; //当前对象数 short int scrx,scry; //地图移动量 CString mapbak; //地图块名 int w,h; //对象图片宽、高 char appdir[256]; //当前目录 short int mans; //显示区对象数 short int sbufx [SBUF]; //兽的偏移量x short int sbufy [SBUF]; //兽的偏移量y short int rbufx [RBUF]; //人的偏移量x short int rbufy [RBUF]; //人的偏移量y private://私有,类内部使用 HBITMAP bit0; //暂存位图内存 HBITMAP bit1; //暂存位图内存 short int sys; //当前运行系统
4.定义函数
//函数定义
public://公有,外部可调用。 BOOL init(); //初始化 void exit(); //退出 void loadmap(CString name); //对象初始化 BOOL getpic(CString cc,int p); //调图片到相关位图 BOOL loadbmp(CString cc); //调BMP 图片 void setobj (int q); //对象显示 void setman (int q); //设置活动对象初值 void getobjwh(int q); //取对象的尺寸 void sort(); //排序(冒泡法) 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); //透明显示 private://私有,类内部使用 BOOL inscreer(int i); //在显示区? int getsys(); //识别系统 void bianfw (int q); //方位转换 void manmove(int i); //活动对象的移动
在这里我们定义了游戏类库 game 要用到的常数,以及一些API 函数必需的头文件
定义和LIB 连接库。
#include <mmsystem.h> #pragma comment (lib,"winmm.lib") //时间函数库 #include <wingdi.h> #pragma comment (lib,"gdi32.lib") //图形库 #pragma comment (lib,"msimg32.lib")//透明显示库 #include <vfw.h> #pragma comment (lib,"vfw32.lib") // //定义常数 #define SU_LIANG 500 //定义对象数 #define TIMER 150 //主时钟周期 #define WIDTH 640 //游戏显示区宽 #define HEIGHT 480 //游戏显示区高 #define SCRWI 800 //程序界面宽 #define SCRHE 600 //程序界面高 #define GX 40 //地图格宽 #define GY 30 //地图格高 #define SCRP 12 //地图最大为显示区的12 倍 #define SBUF 2801 //兽最大数 #define RBUF 801 //人最大数 #define JBUF 100 //景最大数
更详细的源程序请见本章实例:“穿越丛林”。
在这一章里,我们学了以下知识和方法:
1.类文件和建立类文件的方法。
2.类文件的使用方法。
3.调入场景文件,初始化场景。
4.冒泡法排序。
5.用冒泡法排序解决对象遮挡。
6.实用的无闪烁刷屏方法。
7.加入了对象显示的校正值,使对象的显示不再跳动。