zoukankan      html  css  js  c++  java
  • [置顶] 游戏开发技术总结(经典之作)第七集 广阔天地游戏大地图的形成方法的地图移动

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

  • 相关阅读:
    多重背包POJ1276不要求恰好装满 poj1014多重背包恰好装满
    哈理工1053完全背包
    求最小公倍数与最大公约数的函数
    Bus Pass ZOJ 2913 BFS 最大中取最小的
    POJ 3624 charm bracelet 01背包 不要求装满
    HavelHakimi定理(判断一个序列是否可图)
    z0j1008Gnome Tetravex
    ZOJ 1136 Multiple BFS 取模 POJ 1465
    01背包 擎天柱 恰好装满 zjut(浙江工业大学OJ) 1355
    zoj 2412 水田灌溉,求连通分支个数
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3067757.html
Copyright © 2011-2022 走看看