游戏开发技术总结(经典之作)第六集 穿越丛林-----游戏角色的角色遮挡(前后关系)

 源代码下载地址---------->第六集源代码

6-1 任务


1.学习C++“ 类”的创建和使用方法。
2.在场景中加入景物。
3.解决对象的相互遮挡关系。


6-2 类文件


6-2-1 建立类文件


        打开 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: //保护类型,派生类可调用。
⋯⋯
};



6-2-2 类文件的使用

 


       这样,一个类文件就建立好了,它现在可以正确地编译了。当然现在它是个空的
类文件。
下面我们就可以将前面做的功能函复制过来。这里作为例子,我们将前面的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-2-3 初始化场景


       在这一章的第二个任务是要在场景中加入游戏角色以外的其它东西, 这里我们加
入一棵树、一块石头。
游戏开发技术总结(经典之作)第六集 穿越丛林-----游戏角色的角色遮挡(前后关系)_第1张图片
                                图6-3
1.地图文件*.dat
       在数据文件 game.dat 中安排了游戏角色、场景的初始数据。
地面.BMP
游戏开发技术总结(经典之作)第六集 穿越丛林-----游戏角色的角色遮挡(前后关系)_第2张图片
数据意义见下表。这些数据可以是直接在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); //背景位图关联到背景设备场景




6-2-4 多对象处理

 


         现在我们 游戏场景中已不只是一个对象了,树、石等也是对象,后面我们还要加
入其它动物。所以现在在一个时钟周期里,显示的就是多个对象了( 在后面的实例中,
我们将定义场景中可同时显示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);
}



6-3 对象遮挡


        解决对象 遮挡的方法有多种, 我们这里给出一种最简单但实用的方法。从上面程
序看到, 我们是在一个循环中按对象的序号来显示对象的,必然就有先显示的内容要
被后面的内容遮挡。人[0]最先显示,然后是树[1] , 石头[2]最后显示(见图6-4),结果
是先显示的被后显示的遮住了。

游戏开发技术总结(经典之作)第六集 穿越丛林-----游戏角色的角色遮挡(前后关系)_第3张图片
                                  图6-4
       事实上,我们的游戏画面是上远、下近;Y 坐标是上小、下大。这里应该是人在树
与石头之间,有人遮石,树遮人的现象出现。
我们观察对象的显示位置:石头(207, 255)、树(269 , 309)、人(234, 260) 。
从 Y 坐标看有:树(269, 309)>人(234, 260)>石(207, 255) 。
这就给我们提示了一种解决问题的方法:我们在显示对象前,按对象当前显示位
置的Y 坐标进行排序,使Y 坐标最小的对象,也就是远的对象先显示,这样就可以形
成下遮上、近遮远的效果。好,下面我们来做一个按对象的显示位置Y 坐标排序的功
能函数。



6-3-1 冒泡法排序


        排序是程序中常用的算法。冒泡法是排序算法中最简单、最好理解的一种。因为
对游戏中几百个对象排序,排序算法的效率不很重要 (在我们这个游戏中也做到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[]在前面了。


6-3-2 解决对象遮挡


       现在将 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! 视觉上符合逻辑。
游戏开发技术总结(经典之作)第六集 穿越丛林-----游戏角色的角色遮挡(前后关系)_第4张图片
                                    图6-5


6-4 游戏类库


        在这一章我们开始引入了 C++的类库,并将游戏的功能写在这个类库中。后面我们
还要在类库中增加更多的功能。所以我们在这里列出这个类库的源程序game.cpp 和它
的头文件game.h ,还有它引用的常数定义.h ,要注意它们的组成结构。


6-4-1 game.cpp


       以下 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);} //本动作完成,进行方位转换
}



6-4-2 game.h


       以下 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); //活动对象的移动


 


6-4-3 常数定义.h


       在这里我们定义了游戏类库 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 //景最大数



更详细的源程序请见本章实例:“穿越丛林”。


6-5 小结


在这一章里,我们学了以下知识和方法:
1.类文件和建立类文件的方法。
2.类文件的使用方法。
3.调入场景文件,初始化场景。
4.冒泡法排序。
5.用冒泡法排序解决对象遮挡。
6.实用的无闪烁刷屏方法。
7.加入了对象显示的校正值,使对象的显示不再跳动。

 

 

 

 

 

 

 

 

你可能感兴趣的:(游戏,bitmap,游戏开发,游戏引擎,游戏编程)