[置顶] 游戏开发技术总结(经典之作)第七集 广阔天地-----游戏大地图的形成方法的地图移动

 

7-1 任务

                                                           图7-1
    在这一章里我们将学习游戏的大场面表示和实现的方法。有以下具体内容:
1.首先要有一个大背景和生成大地图的方法。
2.用鼠标移动大地图的方法。
3.为了纵观全局我们还需要一个微缩地图。


7-2 单块地图无缝延伸法


7-2-1 大地图的拼接


       大场面的大背景可以是一幅完整 BMP 图形(这样计算机内存的消耗可就大了) ,也
可以由很多小图形构成(像《传奇》、《魔力宝贝》就是采用拼块地图) 。我们这里介绍一种简单的方法,我们将这种方法称为“单块地图无缝延伸法”。这个方法很简单,适合初学者理解和掌握,至于效果嘛,看了你就知道了(还是不错的)。
拼图原理 :以下三幅地面图形是同一幅图,它的特点是上、下、左、右边缘是相
同的,可以用它无限拼接出大地图。


                                                        图7-2


7-2-2 拼接地图的移动


       地图拼接解决了 ,但大地图不能全部显示在计算机屏幕上, 任何时候有限的屏幕
只能显示地图的一部分;所以必须按我们的要求能够移动计算机内部的那个大地图。


1.大地图移动方法


      我们看下面示意图,该示意图假定地图是以屏幕1/3 宽度来横向移动的。现在来看
当前屏幕(方框内)上的图形变化情况。
设:屏幕位置(X,Y)、图形尺寸(W,H)、移动距离(DX,DY),原图在设备场景HBK0
中;屏幕设备场景HBK2。
在计算机的整数运算中除运算有两种:
整除,只有结果的整数部分, 余数舍去了,例A/B,(10/3=3)。
取余,结果就是除运算的余数,例A%B,(10%3=1)。
下面公式 DX=X%W,DX 是X/W 的余数。
例: W=300;X={0,100,200,300,400,500,600… … }
当 X<=W 时0%300=0、200%300=0;300%300=0;DX=0;
① 没有移动,DX=0
当 X>W 时,
X=400;400%300=100;② 地图左移 1/3 屏幕宽度,DX=100=W/3
X=500;500%300=200;③ 地图左移 2/3 屏幕宽度,DX=200=W*2/3
X=600;600%300=0; ④ 地图左移 3/3 屏幕宽度, DX=0(这就是取余运算的结果)
X=700;700%300=100; ② 地图左移 1/3 屏幕宽度,DX=100=W/3( 又重复②的操作)


2.大地图移动图示


计算公式:DX=X%W
BitBlt(HBK2,0,0,W-DX,H, HBK0, DX,0,SRCCOPY);
BitBlt(HBK2,W-DX,0, DX,H, HBK0,0,0,SRCCOPY);
① 初始时,没有移动,DX=0
1.BitBlt(HBK2,0,0,W-0,H, HBK0, 0,0,SRCCOPY);
2.BitBlt(HBK2,W-0,0, 0,H, HBK0,0,0,SRCCOPY);
原图两次原样拷贝到屏幕。

                    图7-3-1

 


② 地图左移1/3 屏幕宽度,DX=W/3
3.BitBlt(HBK2,0,0,W-W/3,H, HBK0,W/3,0,SRCCOPY);
原图从1/3 处拷贝2/3 宽的图形到屏幕左端。
4.BitBlt(HBK2,W-W/3,0,W/3,H,HBK0,0,0,SRCCOPY);
原图从左端拷贝1/3 宽的图形到屏幕2/3 处。

                       图7-3-2


③ 地图左移2/3 屏幕宽度,DX=W*2/3
5.BitBlt(HBK2,0,0,W-W*2/3,H, HBK0,W*2/3,0,SRCCOPY);
原图从2/3 处拷贝1/3 宽的图形到屏幕左端。
6.BitBlt(HBK2,W-W*2/3,0, W*2/3,H,HBK0,0,0,SRCCOPY);
原图从左端拷贝2/3 宽的图形到屏幕1/3 处。

                         图7-3-3


④ 地图左移3/3 屏幕宽度,DX=0(这就是取余运算的结果)
7.BitBlt(HBK2,0,0,W-0,H, HBK0,0,0,SRCCOPY);
8.BitBlt(HBK2,W-0,0, 0,H,HBK0,0,0,SRCCOPY);
原图两次原样拷贝到屏幕。

                        图7-3-4

 


纵向移动方法相同,DY=Y%H。需要说明的是横向、纵向的移动应该分别进行。


3.大地图移动算法


具体方法为:
原图在设备场景 HBK0 中,横向移动到设备场景HBK1。
再从设备场景 HBK1 中纵向移动到屏幕设备场景HBK2。算法如下:

DX=X%W;
DY=Y%H;
BitBlt( HBK1 ,0 ,0 ,W-DX,H ,HBK0 ,DX,0 ,SRCCOPY); //横向移动第1 步
BitBlt( HBK1 ,W-DX ,0 ,DX ,H ,HBK0 ,0 ,0 ,SRCCOPY); //横向移动第2 步
BitBlt( HBK2 ,0 ,0 ,W ,H-DY,HBK1 ,0 ,DY,SRCCOPY); //纵向移动第1 步
BitBlt( HBK2 ,0 ,H-DY ,W ,DY ,HBK1 ,0 ,0 ,SRCCOPY); //纵向移动第2 步
// 目标 ,X0 ,Y0 ,宽 ,高 ,源 ,X1,Y1,拷贝方式


 


7-3 微缩地图


       大场面的游戏常常都有一个微缩地图 ,它的作用是让玩家能够纵观游戏的全局。
同样在调试游戏时这个微缩地图对我们也是有用的,所以我们先完成微缩地图的编制。
微缩地图又分为生成微缩地图和显示微缩地图。


7-3-1 生成微缩地图


       在游戏程序开始运行时就将背景图片调入内存,拼接成需要的大小, 再将游戏初
始化时的景物(树、石、房子等等)图形调入到相应的位置。拼接、景物调入都与微缩同
时进行,否则我们的设备资源的开销就太大了。生成微缩地图分以下三步进行:
A.调地面块到地图设备场景BkDC0,这是前面学过的方法。

if(!getpic(dir+"地面.bmp",1)) return ;//调地面块
HBITMAP OldMak=(HBITMAP)SelectObject(MemDC,bitmap);
BitBlt(BkDC0,0,0,w,h,MemDC,0,0,SRCCOPY);
SelectObject(MemDC, OldMak);



B.拼接地图并缩成小地图
设地图的大小为一屏的 SCRP*SCRP 倍(见图7-4);
微缩地图的宽 mapw、高maph。
用以下算法将 BkDC0 中的一屏的地图宽、高分别压缩到mapw/SCRP、maph/SCRP,
并拼接成微缩地图存放在小地图设备场景SMAP 中。这里用TransparentBlt2 透明函数来
压缩,原因是TransparentBlt2 透明函数能自动处理256 色位图的调色板。


                                        图7-4

      以下是在双重循环下将(WIDTH * HEIGHT)显示区的地图(这里是640*480),压缩成(mapw/SCRP * maph/SCRP)小地图块,再将SCRP* SCRP 个(这里是25 个)这样的小地图块拼接成(mapw * maph)微缩地图。

for(int i=0;i<mapw;i=i+mapw/SCRP)
for(int j=0;j<maph;j=j+maph/SCRP)
TransparentBlt2(SMAP ,i ,j ,mapw/SCRP ,maph/SCRP,
BkDC0 ,0 ,0 ,WIDTH ,HEIGHT, RGB(0,0,0)
);



C.全地图景物微缩
这一步是将初始化表中指定的景物, 按地图块的透明压缩方法微缩在小地图设备
场景SMAP 里。

int wi=WIDTH*SCRP, he=HEIGHT*SCRP;
for(int q=0;q<rs;q++)
if(man[q].lb==2) //是静物
{cc.Format("%s/树石/b%04d.bmp",dir,man[q].p);//取图片名
if(getpic(cc,1)==FALSE) return; //读取位图文件
int x=(man[q].xix-w/4)*mapw; //x 当前位置
int y=(man[q].xiy-h)*maph; //y 当前位置
HBITMAP OldMak=(HBITMAP)SelectObject(MemDC, bitmap);
TransparentBlt2(SMAP ,x/wi ,y/he ,(w*mapw)/wi ,(h*maph)/he,
MemDC ,0 ,0 ,w ,h, RGB(0,0,0));
SelectObject(MemDC, OldMak);
}



为了方便使用,我们也将生成小地图编成game 类中的函数。

//**************************************************
//getsmap(CString dir,CString cc)//生成小地图
// A.调地面块到地图设备场景BkDC0
// B.全地图缩成小地图
// C.全地图景物微缩
//**************************************************
void game::getsmap(CString dir,CString cc)//生成小地图
{//A.调地面块到地图设备场景BkDC0
if(!getpic(dir+cc,1)) return ; //调地面块
HBITMAP OldMak=(HBITMAP)SelectObject(MemDC,bitmap);
BitBlt(BkDC0,0,0,w,h,MemDC,0,0,SRCCOPY);
SelectObject(MemDC, OldMak);
//B.全地图缩成小地图
int i,j;
for(i=0;i<mapw;i=i+mapw/SCRP)
for(j=0;j<maph;j=j+maph/SCRP)
TransparentBlt2 (SMAP,i,j,mapw/SCRP,maph/SCRP,
BkDC0,0,0,WIDTH,HEIGHT, RGB(0,0,0));
//C.全地图景物微缩
int wi=WIDTH*SCRP,he=HEIGHT*SCRP;
for(int q=0;q<rs;q++)
if(man[q].lb==2) //是静物
{cc.Format("%s/树石/b%04d.bmp",dir,man[q].p); //取图片名
if(getpic(cc,1)==FALSE) return; //读取位图文件cc
int x=(man[q].xix-w/4)*mapw; //x 当前位置
int y=(man[q].xiy-h)*maph; //y 当前位置
HBITMAP OldMak=(HBITMAP)SelectObject(MemDC, bitmap);
TransparentBlt2(SMAP,x/wi,y/he,(w*mapw)/wi,(h*maph)/he,
MemDC,0,0,w,h, RGB(0,0,0));
SelectObject(MemDC, OldMak);
}
}


 


7-3-2 显示微缩地图


       显示微缩地图很简单,只要在移动地图时,将微缩地图设备场景SMAP 里的图形数
据拷贝到屏幕上的对应位置就行了。
BitBlt(hdc,mapl,mapt,mapw,maph,SMAP,0,0,SRCCOPY);// 微缩地图刷新
这里 hdc 是屏幕设备场景。
mapl 是微缩地图显示的左边位置,mapt 是微缩地图显示的上边位置。
在微缩地图上我们还要加上一个动态的矩形框,来表示当前主显示区在大地图中
的位置。
画矩形是用 MoveToEx(… … )定点和LineTo (… )连线的API 函数实现的(见图7-4)。

//**************************************************
//smlmap(HDC hdc)//显示微缩地图
//**************************************************
void game::smlmap(HDC hdc)//显示微缩地图
{ BitBlt(hdc,mapl,mapt,mapw,maph,SMAP,0,0,SRCCOPY);// 微缩地图刷新
//以下显示微缩地图中的白色矩形框
POINT Point; Point.x=0;Point.y=0; //定义点(0,0)
SelectObject(hdc,pen0); //设置画笔
int mapw0=mapw/SCRP,maph0=maph/SCRP; //矩形宽、高
int scrx0=(scrx*mapw)/(WIDTH*SCRP); //算矩形左上坐标X
int scry0=(scry*maph)/(HEIGHT*SCRP); //算矩形左上坐标Y
MoveToEx(hdc,mapl+scrx0,mapt+scry0,&Point); //定矩形左上点
LineTo(hdc,mapl+scrx0+mapw0,mapt+scry0); //画矩形上边线
LineTo(hdc,mapl+scrx0+mapw0,mapt+scry0+maph0);//画矩形右边线
LineTo(hdc,mapl+scrx0,mapt+scry0+maph0); //画矩形下边线
LineTo(hdc,mapl+scrx0,mapt+scry0); //画矩形左边线
}


 


7-4 移动地图


      各种游戏移动地图的方式不同。有游戏角色移动到当前显示区边缘时,地图移动(如
帝国、传奇) ;也有鼠标超过显示区边缘时地图移动(如红警、星际争霸)。我们这里采
用第二种方式,当鼠标超过显示区边缘时,地图进行相应的移动。
MFC 中捕获鼠标移动消息的有,窗体鼠标移动消息函数OnMouseMove(… … )。
但是这个函数只是针对窗体的 。超过本窗体,或窗体上有其它控件都会使它失效。
而我们移动地图的方法是鼠标超过显示区边缘时地图移动, 所以不能用
OnMouseMove(… … )。
在 MFC 中有一个通用消息截获函数PreTranslateMessage(MSG* pMsg) ,它不针对某
一个窗体,在程序运行时可以截获程序的所有消息(鼠标、按键等等)。我们下面就用它
来检测鼠标超过显示区边缘的消息。
当检测到鼠标在哪个方向超过边界时,主屏的显示位置(scrx,scry) 就通过移动地图
的功能函数movesmap(⋯)产生相应变化。


7-4-1 建立消息截获函数


       在类向导中选择 PreTranslateMessage, 双击后,在成员功能栏(Member functions)可以看到已生成的消息截获函数PreTranslateMessage(MSG* pMsg) 。再按编辑代码(Edit Code),就进入到消息截获函数PreTranslateMessage(MSG* pMsg)中了。


                                                      图7-5

程序运行中,只要鼠标一有动静, 程序就会执行消息截获函数。
在 PreTranslateMessage(MSG* pMsg)中,pMsg->pt 就是鼠标在当前屏幕的位置。它
有x,y 两个分量(pMsg->pt.x,pMsg->pt.y) ,分别是鼠标在当前屏幕的的位置坐标。
用法如下:

BOOL CMyDlg::PreTranslateMessage(MSG* pMsg)
1{ CRect lpRect0;
2 GetWindowRect(&lpRect0); // 获取当前窗口坐标
3 int x0=pMsg->pt.x-lpRect0.left;// 针对当前窗口的鼠标位置
4 int y0=pMsg->pt.y-lpRect0.top; //
5 CClientDC dc(this); //客户区设备环境
6 m_game.movesmap(x0,y0); //移动地图
7 m_game.smlmap(dc.m_hDC); //显示小地图
return CDialog::PreTranslateMessage(pMsg);
}



第1 行,定义矩形变量lpRect0。
第 2 行,获取当前窗口相对屏幕的坐标,( lpRect0.left, lpRect0.top)为当前窗口在
屏幕中的左上角坐标。
第 3、4 行,将鼠标相对屏幕的坐标(pMsg->pt.x, pMsg->pt.y)变换为相对当前窗口
的(x0,y0)位置


                                      图7-6

第5 行,客户区设备环境。
第6 行,带鼠标位置(x0,y0)调移动地图函数。
第 7 行, 带窗口的显示句柄调显示小地图函数。


7-4-2 移动地图功能函数


       地图每次移动的纵横单位为地图格宽、高(GX=40,GY=30)。地图格是我们在后面
地图障碍中要用到的概念。

//**************************************************
//movesmap(int x0,int y0)// 移动地图功能函数
//GX、GY 是地图移动的步长。
//**************************************************
void game::movesmap(int x0,int y0)//移动地图
{ if(x0>800) // 超过右边界
{scrx=scrx+GX; // 地图右移GX 个点
if(scrx>WIDTH*(SCRP-1)) scrx=WIDTH*(SCRP-1); //边界检测
}
if(x0<2) // 超过左边界
{scrx=scrx-GX; // 地图左移GX 个点
if(scrx<0) scrx=0; // 边界检测
}
if(y0>600) // 超过下边界
{scry=scry+GY; // 地图下移GY 个点
if(scry>HEIGHT*(SCRP-1)) scry=HEIGHT*(SCRP-1);//边界检测
}
if(y0<10) // 超过上边界
{scry=scry-GY; // 地图上移GY 个点
if(scry<0) scry=0; // 边界检测
}
}



       注意其中的边界检测。有时移到边界时,往往要移的量不足GX 或GY;处理方法
是判断到这种情况后,直接取边界的值。


7-4-3 快速定位地图


         在小地图上,还有一个很实用的功能。当我们用鼠标在小地图上点击时,可以使
表示主屏的方框快速移动,从而导至主显屏上的地图快速移动。这个功能的实现方法
见下面程序。

//**************************************************
// BOOL dingweimap(int x,int y)//定位地图
// 这是由按鼠标左键调用的。
// 根据按键位置(x,y),改变(scrx,scry)使主显屏快速移动
//**************************************************
BOOL gamemap::dingweimap(int x,int y)//定位地图
{ if(y>mapt&&y<mapb&&x>mapl&&x<mapr)//在小地图上按键
{scrx=(x-mapl)*WIDTH *SCRP0/mapw-mapw*2;
scry=(y-mapt)*HEIGHT*SCRP0/maph-maph*2;
scrx=(scrx/GX)*GX;
scry=(scry/GY)*GY;
//进行边界检测。
if(scrx>WIDTH*(SCRP0-1)) scrx=WIDTH*(SCRP0-1);
if(scrx<0) scrx=0;
if(scry>HEIGHT*(SCRP0-1)) scry=HEIGHT*(SCRP0-1);
if(scry<0) scry=0;
return TRUE;
}
return FALSE;
}


 


7-5 对主程序的修改


本章加入大地图的功能后, 必然要求原来的主程序“广阔天地Dlg.cpp”上有一些
改动;使新的功能加入到主程序上。需要改动的地方介绍如下。


7-5-1 OnTimer(⋯)时钟函数中的改动


现在我们在 OnTimer(⋯)中安排了三个不同周期的中断。
A.游戏的主屏幕刷新周期。这是我们前面一直用的屏幕刷新周期。
B.显示信息。每秒钟显示一次游戏的一些相关信息。
C.启动延时。在OnInitDialog()中, 首先启动这个时钟中断SetTimer(3,100,NULL) ,
在这里,我们调入地图文件和生成微缩地图,再启动1、2 两个时钟中断。

void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数,[类向导中定义生成]
{//A.游戏的主时钟周期
if(nIDEvent==1) // 屏幕刷新
{tim=timeGetTime(); //开始时间
CClientDC dc(this); //客户区设备环境
m_game.mlmap(); //地图块移动拼接
for(int i=0;i<m_game.rs;i++)
m_game.setobj(i); //对象显示
BitBlt(dc.m_hDC,2,10,WIDTH,HEIGHT,m_game.BkDC1,0,0,SRCCOPY);//用Bk1 刷新窗口
SetTextColor(dc.m_hDC,RGB(0,0,255)); //字色
SetBkMode(dc.m_hDC,TRANSPARENT); //字透明
TextOut(dc.m_hDC,200,30,"我还是崂山道士,可以穿墙而过。",30);
if(m_game.rs>1) m_game.smlmap(dc.m_hDC); //显示小地图
tim=timeGetTime()-tim;//显示时间=结束时间-开始时间
}
//B.显示信息
if(nIDEvent==2) //显示信息
{char cc[255];
int q=m_game.mann;
sprintf(cc,"地图[X:%4d Y:%4d] 人[x:%4d y:%4d]",
m_game.scrx,m_game.scry,m_game.man[q].xix,m_game.man[q].xiy);
SetDlgItemText(IDC_STATIC5, cc);
sprintf(cc,"[显示区对象数:%3d] [%3dms/屏] [CPU 占用%3d%]",
m_game.mans,tim,tim*100/TIMER);
SetDlgItemText(IDC_STATIC4, cc);
sprintf(cc,"地图%dX%d",
WIDTH*m_game.SCRP0,HEIGHT*m_game.SCRP0);
SetDlgItemText(IDC_STATIC3, cc);
}
//C.启动延时
if(nIDEvent==3)//启动延时
{KillTimer(3); //关闭第三时钟中断
m_game.loadmap("地图/5X5.dat"); //调入地图
m_game.SCRP0=5; //大地图行列数
m_game.getsmap(); //生成小地图
SetTimer(1,TIMER,NULL); //设定屏幕刷新TIMER 毫秒
SetTimer(2,1000,NULL); //信息显示周期为1 秒
}
CDialog::OnTimer(nIDEvent);
}


 


7-5-2 OnLButtonDown(⋯)中的改动


在 OnLButtonDown(⋯)中加入了快速定位地图的功能。

void CMyDlg::OnLButtonDown(UINT nFlags, CPoint point)
//取针对主角的目标位置,[类向导中定义生成]
{ int x=point.x,y=point.y;
CClientDC dc(this); //客户区设备环境
if(m_game.dingweimap(x,y)) //定位地图
m_game.smlmap(dc.m_hDC); //显示小地图
if(x>WIDTH+2||y>HEIGHT+10) return; //不在显示区返回
for(int i=0;i<m_game.rs;i++)
{if(m_game.man[i].jisu==0) //只对主角
{m_game.man[i].x0=x+m_game.scrx; //获得目标位置x
m_game.man[i].y0=y+m_game.scry; //获得目标位置y
m_game.man[i].p=m_game.man[i].m1-1; //中止当前动作
break;
}
}
CDialog::OnLButtonDown(nFlags, point);
}


 


7-5-3 OnCancel()退出确认


      为了使我们的游戏逐步规范化 ,在这章节的程序退出中,我们加入了程序的退出
选择功能。

void CMyDlg::OnCancel() //退出,[类向导中定义生成]
{ KillTimer(1); //关闭时钟1。
if( ::MessageBox(GetSafeHwnd(), "退出程序吗?","请您确定!",
MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 ) == IDYES )
{m_game.exit(); //退出类
CDialog::OnCancel(); //退出
}
else //不退出
SetTimer(1,TIMER,NULL); //打开时钟1。
}


 


7-6 类的继承和地图类


        这一章我们又加入了地图的拼接、移动和微缩地图等等新的功能函数。这些新加
的函数放在哪里呢?当然可以继续放在game.cpp 中,那么game.cpp 将会很冗长,因为
我们后面还有一些功能要编制。比较好的办法是,我们还是将它建立一个类文件game_
地图.cpp,类名为gamemap。
我们注意到,在本章加入的新功能函数里有许多地方需调出game.cpp 的函数和变
量,当然我们可以在类game_地图.cpp 中再调用类game.cpp。但是在C++里有一个更好
的方法可供我们使用。那就是—— 类的继承。


7-6-1 类的继承— 子类


       我们在新定义一个类时,它可以是一个基类;也可以是某一个类的子类。某个类
的子类可以继承这个基类的所有特征,即它所有定义成public(公有)的函数和变量。
例如:game.cpp 类game 是定义的一个基类,在game.h 中可以看到。

//定义类
class game
{public: game(); //构造函数
virtual~game(); //析构函数
⋯⋯
}
而我们新定义的类gamemap,在game_地图.h 中我们写成;
class gamemap : public game //继承game 类
{public: gamemap(); //构造函数
virtual~gamemap(); //析构函数
⋯⋯
}



它们的区别在于两个的第1 行。
在 game_地图.h 的第1 行我们除了说明class gamemap 外,在后面多加了public
game。这样就说明,我们定义的类gamemap 是game 的子类,也叫game 的继承类。于
是game 类的所有定义成public 的函数、变量,在gamemap 类中都可以自由使用,就像
使用自己的类函数、变量一样。


7-6-2 继承类(子类)的调用


在第六章的“穿越丛林Dlg.” h 里我们定义的类变量为game m_game。调用方法
为m_game.XXX。
#include "../游戏类库/game.h"
class CMyDlg : public CDialog
{ public:
⋯⋯
game m_game; //定义对象名
现在我们定义的新类为gamemap;
#include "../游戏类库/game_地图.h"
class CMyDlg : public CDialog
{ public:
⋯⋯
gamemap m_game; //定义对象名
在“广阔天地Dlg.h” 中我们定义的对象变量应为 gamemap m_game;调用方法同
样为m_game.XXX。所不同的是现在的m_game.XXX;既可以是类文件game_地图.cpp 中的函数和变量,也可以是类文件game.cpp 中的函数和变量,因为儿子身上可以反映父亲的血统嘛。


7-6-3 game_地图.cpp


       以下“game_地图.cpp”是一个完整的类文件,结构顺序上是连续的。它包含了9
个功能函数,为了更清楚地表示,我们将它们隔开, 加上了小标题。加灰的部份是本
章没用到的。
1. initmap()初始化地图参数

#include "stdafx.h"
#include "game_地图.h"
gamemap:: gamemap() //构造函数
{}
gamemap::~gamemap() //析构函数
{DeleteObject(pen1); //删除主角寻路画笔
}
//***********************************************
// initmap()//初始化地图参数
// A.设置小地图的位置、尺寸
// B.定义图形环境,SMAP-装载小地图
// 定义4 个画笔pen0、pen1;pen、penz 编辑用
// C.设置几个初始量。
//***********************************************
void gamemap::initmap()//初始化地图参数
{ mapt=10,mapb=120; //小地图上下
maph=mapb-mapt; //小地图高
mapl=WIDTH+6; //小地图左
mapr=mapl+maph*WIDTH/HEIGHT; //小地图右边按显示区比例取得
mapw=mapr-mapl; //小地图宽
hScrDC=CreateDC("DISPLAY", NULL, NULL, NULL); //创建屏幕设备场景
SMAP =CreateCompatibleDC(hScrDC); //创建小地图设备场景
mapbit=CreateCompatibleBitmap(hScrDC,mapw,maph);//创建小地图位图内存
SelectObject(SMAP,mapbit); //小地图位图内存与小地图设备场景关联
pen0.CreatePen(PS_SOLID,1,RGB(0x0,0xf0,0xf0)); //小地图中的方格画笔
pen1.CreatePen(PS_SOLID,1,RGB(0x60,0x60,0x60)); //主角寻路画笔
DeleteDC(hScrDC); //删除屏幕设备场景
fls=0; //闪烁标志
movemap=0; //地图移动否?
init();
}



2. exitmap()退出

void gamemap::exitmap()//退出
{ DeleteObject(mapbit);//删除小地图位图内存
DeleteDC(SMAP); //删除小地图设备场景
DeleteObject(pen0); //删除小地图中的方格画笔
exit(); //调game 父类的结束
}



3. mlmap()地图块移动拼接

//**************************************************
// mlmap()//地图块移动拼接
// 这里使用的是单地图无缝拼接移动算法。
//**************************************************
void gamemap::mlmap()//地图块移动拼接
{ if( movemap==0) //地图移动否?
BitBlt(BkDC1,0,0,WIDTH,HEIGHT,BkDC0,0,0,SRCCOPY);
else
{int gx=scrx%WIDTH,gy=scry%HEIGHT;
BitBlt(BK,0,0,WIDTH-gx,HEIGHT,BkDC0,gx,0,SRCCOPY); //地图横向移动
BitBlt(BK,WIDTH-gx,0,gx,HEIGHT,BkDC0,0,0, SRCCOPY);
BitBlt(BkDC1,0,0,WIDTH,HEIGHT-gy,BK,0,gy,SRCCOPY); //地图纵向移动
BitBlt(BkDC1,0,HEIGHT-gy,WIDTH,gy,BK,0,0,SRCCOPY);
}
sort(); //按Y 坐标排序,用于在显示时分出前后位置
movemap=0; //地图移动标志复位
}



4. getsmap()生成小地图

//**************************************************
// getsmap()//生成小地图
// A、调地面块到BkDC0 地图设备场景
// B、全地图缩成小地图到SMAP
// C、全地图景物微缩到SMAP
//**************************************************
void gamemap::getsmap()//生成小地图
{//A.调地面块到BkDC0 地图设备场景
char name[256];
sprintf(name,"%s%s",dir,mapbak);
loadbmp(name); //调BMP 图片
OldMak=(HBITMAP)SelectObject(BkDC0,bitmap);
//B.全地图缩成小地图
int i,j;
COLORREF col=RGB(255,255,255);
for(i=0;i<mapw;i=i+mapw/SCRP0)
for(j=0;j<maph;j=j+maph/SCRP0)
TransparentBlt2
(SMAP,i,j,mapw/SCRP0,maph/SCRP0,
BkDC0,0,0,WIDTH,HEIGHT,col); //透明显示
//C.全地图景物微缩
int wi=WIDTH*SCRP0,he=HEIGHT*SCRP0;
for(int q=0;q<rs;q++)
if(man[q].lb==2) //是静物
{if(getpic("景",man[q].p)==FALSE) continue; //读取位图文件
int x=(man[q].xix-w/4)*mapw; //x 当前位置
int y=(man[q].xiy-h)*maph; //y 当前位置
TransparentBlt2
(SMAP,x/wi,y/he,(w*mapw)/(wi*2/3),(h*maph)/(he*2/3),
MemDC,0,0,w,h,col); //透明显示
}
}



5. movesmap(⋯)移动地图

加入变量a=3 的作用是,使移动步长小一些,使移动更精确一些。
//**************************************************
// movesmap(int x0,int y0)//移动地图
// 鼠标超过边界时,改变(scrx,scry)使主显屏产生相应的移动。
// 每个方向的移动都进行超界检测。
//**************************************************
void gamemap::movesmap(int x0,int y0)//移动地图
{ int a=3;
if(edi==1) a=1; //编辑时使算法简单。
if(x0>SCRWI-10) //鼠标超过右边界
{scrx=scrx+GX/a;if(scrx>WIDTH*(SCRP0-1)) scrx=WIDTH*(SCRP0-1);}
if(x0<10) //鼠标超过左边界
{scrx=scrx-GX/a;if(scrx<0) scrx=0;}
if(y0>SCRHE-10) //鼠标超过下边界
{scry=scry+GY/a;if(scry>HEIGHT*(SCRP0-1)) scry=HEIGHT*(SCRP0-1);}
if(y0<10) //鼠标超过上边界
{scry=scry-GY/a;if(scry<0) scry=0;}
movemap=1; //地图移动否?
}



6. dingweimap(⋯) 定位地图

//**************************************************
// dingweimap(int x,int y)//定位地图
// 这是由按鼠标左键调用的。
// 根据按键位置,改变(scrx,scry)使主显屏快速移动
//**************************************************
BOOL gamemap::dingweimap(int x,int y)//定位地图
{ if(y>mapt&&y<mapb&&x>mapl&&x<mapr)//在小地图上按键
{scrx=(x-mapl)*WIDTH *SCRP0/mapw-mapw*2;
scry=(y-mapt)*HEIGHT*SCRP0/maph-maph*2;
scrx=(scrx/GX)*GX;
scry=(scry/GY)*GY;
//进行边界检测。
if(scrx>WIDTH*(SCRP0-1)) scrx=WIDTH*(SCRP0-1);
if(scrx<0) scrx=0;
if(scry>HEIGHT*(SCRP0-1)) scry=HEIGHT*(SCRP0-1);
if(scry<0) scry=0;
return TRUE;
}
return FALSE;
}



7. smlmap(⋯) 显示小地图

//**************************************************
// smlmap(HDC hdc)//显示小地图
// A.小地图刷新
// B.在小地图上显示主屏框
// C.显示主角在小地图的移动
// D.在小地图上显示主角寻路的路径
//**************************************************
void gamemap::smlmap(HDC dc0)//显示小地图
{ if(rs<1||SCRP0<1) return;
CDC* dc = CDC::FromHandle (dc0);
//A.小地图刷新
BitBlt(dc0,mapl,mapt,mapw,maph,SMAP,0,0,SRCCOPY);//小地图刷新
//B.在小地图上显示主屏框
CPen *old;
old=dc->SelectObject(&pen0); //调白色画笔
int mapw0=mapw/SCRP0,maph0=maph/SCRP0-1;
int scrx0=mapl+(scrx*mapw)/(WIDTH*SCRP0);
int scry0=mapt+(scry*maph)/(HEIGHT*SCRP0);
//用线画方框
dc->MoveTo(scrx0,scry0); //左上点
dc->LineTo(scrx0+mapw0,scry0); //右上点
dc->LineTo(scrx0+mapw0,scry0+maph0); //右下点
dc->LineTo(scrx0,scry0+maph0); //左下点
dc->LineTo(scrx0,scry0); //左上点
//C.显示对象在小地图的移动
for(int q=0;q<rs;q++)
{ COLORREF col=RGB(0x0,0x0,0x0);//
scrx0=(man[q].xix*mapw)/(WIDTH*SCRP0)-1;
scry0=(man[q].xiy*maph)/(HEIGHT*SCRP0)-2;
if(scrx0<1||scry0<1) continue;
if(man[q].jisu==0)
{mann=q; //取动态的主角下标
if(fls==0){fls=1;col=RGB(0xf0,0xf0,0xf0);} //白闪烁
else {fls=0;col=RGB(0xf0,0x0,0x0);} //红
dc->SetPixel(mapl+scrx0+1,mapt+scry0, col);
dc->SetPixel(mapl+scrx0+1,mapt+scry0+1,col);
}
if(man[q].lb==2) continue;
dc->SetPixel(mapl+scrx0,mapt+scry0, col);
dc->SetPixel(mapl+scrx0,mapt+scry0+1,col);
}
//D.在小地图上显示主角寻路的路径
setfind(dc,old); //有寻路时用,显示主角寻路的路径
dc->SelectObject(old);
CDC::DeleteTempMap( );
}



8. leftdown(⋯) 按左键

//**************************************************
// leftdown(HDC hdc,int x,int y)//按左键
// 这是由按鼠标左键调用的。
// A. 在显示区按键,给出主角的目标位置,调A*算法寻路
// B. 在小地图区按键,调定位地图。
// 若是寻路,返回寻路的时间。
//**************************************************
int gamemap::leftdown(HDC hdc,int x,int y)//按左键
{ int fidtim=0;
if(x>0&&x<WIDTH&&y>0&&y<HEIGHT&&edi==0) //在显示区,非编辑态
{int i=mann; //只对主角取目标点
man[i].x0=x-2+scrx;
man[i].y0=y-10+scry;
man[i].p=man[i].m1-1; //中止当前动作
}
if(dingweimap(x,y)==TRUE) //在小地图上点左键,调定位地图
smlmap(hdc); //显示小地图
return fidtim;
}



9. setfind(⋯) 显示主角寻路的路径(有寻路时用)

//////////////////////////////////////////////////////////////////////
void gamemap::setfind(CDC* dc,CPen *old)//有寻路时用,显示主角寻路的路径
{//在小地图上显示主角寻路的路径
int i=mann; //取主角
dc->SelectObject(old);
if(man[i].pk<1)
{ CDC::DeleteTempMap( );
return;
}
int x,y,x0,y0;
x=man[i].ph[0].x*GX;
y=man[i].ph[0].y*GY;
x0=mapl+(x*mapw)/(WIDTH*SCRP0);
y0=mapt+(y*maph)/(HEIGHT*SCRP0);
old=dc->SelectObject(&pen1); //调红色画笔
for (int j=1;j<man[i].pk;j++)
{dc->MoveTo(x0,y0);
x=man[i].ph[j].x*GX;
y=man[i].ph[j].y*GY;
x0=mapl+(x*mapw)/(WIDTH*SCRP0);
y0=mapt+(y*maph)/(HEIGHT*SCRP0);
dc->LineTo(x0,y0);
}
x0=mapl+(man[i].fx*mapw)/(WIDTH*SCRP0);
y0=mapt+(man[i].fy*maph)/(HEIGHT*SCRP0);
dc->LineTo(x0,y0);
}


 


7-6-4 game_地图.h


       以下 game_地图.h 结构顺序上是连续的。为了更清楚地表示,我们将它们隔断,
加上了小标题。加灰的部份是本章没用到的。
1.定义类

#include "game.h"
class gamemap : public game //继承game 类
{public: gamemap(); //构造函数
virtual~gamemap(); //析构函数



2.定义变量

public://公有,外部可调用
HDC SMAP; //小地图设备场景
HBITMAP mapbit; //小地图位图内存
CPen pen0,pen1; //画笔句柄
short int fls; //小地图闪烁
short int mann; //主角下标
short int mapt,mapb; //小地图上下
short int mapl,mapr; //小地图左右
short int mapw,maph; //小地图高宽
short int movemap; //地图移动否?
short int SCRP0; //实际地图倍数



3.定义函数

void initmap(); //初始化地图参数
void exitmap(); //退出
BOOL dingweimap(int x,int y); //定位地图
void getsmap(); //生成小地图
void smlmap(HDC dc); //显示小地图
void movesmap(int x0,int y0); //移动地图
void mlmap(); //地图块移动拼接
int leftdown(HDC hdc,int x,int y); //按左键
void setfind(CDC* dc,CPen *old); //有寻路时用,显示主角寻路的路径
//编辑功能的变量
short int edi;
};



更详细的程序,请看本章实例程序:“广阔天地”。


7-7 小结


在这一章里,我们学了以下知识和方法:
1. 使用了大地图,介绍了单块地图无缝延伸法。
2. 介绍单地图拼接的上下左右移动方法。
3. 引入了微缩地图,在微缩地图上可以快速定位场景。
4. 在场景中加入其它活动对象(加入了动物,现在我还没有叫它们跑)。
5. 类的继承以及基类、子类。

 

 

 

 

posted @ 2013-05-08 20:42  javawebsoa  Views(548)  Comments(0Edit  收藏  举报