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

  • 相关阅读:
    JavaScript之正则表达式(2)
    JavaScript之正则表达式(1)
    交换两个变量的值,不借助第三个变量的 三种方法(JS实现)
    网络基础之 OSI七层模型
    jq获取被选中的option的值。jq获取被选中的单选按钮radio的值。
    常见的XSS攻击代码
    php缓存模块apc可能导致php-fpm终止
    Flex布局:实现左右两列自伸缩撑满效果的
    linux系统莫名被黑的诡异经历
    谈谈我对php通信的理解及人生小感
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3067757.html
Copyright © 2011-2022 走看看