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. 类的继承以及基类、子类。