zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C++学习之旅三我和超级玛丽有个约会

C++学习 有个 约会
2023-06-13 09:14:40 时间

首先,我说说对C++的最直观的感受吧!熟悉了.net智能提示,开始一开始发现C++根本没有提示了。后来google了一下,下载了一个visualassist这个插件,比vs自动提示强多了。然后,就是习惯了在.net中,把所有的声明和方法实现写在同一文件中。可是C++不是这么回事。他一个声明在头文件中,实现在源文件中,说实在话,一开始并怎么习惯。后来渐渐就习惯了。然后,写C++的文件就是真他妈的痛苦,他不比.net,微软已经比你封装好了,在C++中,好多东西需要自己写。 首先,一个析构函数,需要自己释放资源。而.net有一个gc自动进行垃圾回收,资源释不释放,关你鸟事。没办法,只有自己释放.做一个遵守规则的好程序员。这是我对C++最直观感受。
言归正卷,说一说这个超级玛丽的游戏。先看看,我对游戏的类结构的分类,如果有不妥的地方,恳请大家指正。

从层次结构来看,分成这几个层①图像层,②逻辑层,③结构和表。

图像层包括①图像基类MYBITMAP,②游戏背景MYBKSKYàMYBITMAP,③游戏图片MYANIOBJàMYBITMAP,④魔法攻击MYANIMAGICàMYBITMA.
逻辑层包括①游戏逻辑GAMEMAP,②时钟处理MYCLOCK,③字体处理MYFONT,④跟踪打印FILEREPORT,⑥玩家控制MYROLEàMYBITMAP。
结构和表包括①精灵结构ROLE,②物品结构MapObject,③地图信息表MAPINFO。
那每个类的结构又是那么样子的,是骡子还是马拉出来溜溜。我们在往下看一看。
图像层的结构就这样简单,逻辑层只需要确定“哪个图像,哪一帧”这两个参数,就能在屏幕上绘制出所有图片。
 说一说一个图片的基类。他的源代码的架构又是这个样子的。
今天先讲最基础的图像类MYBITMAP:
成员函数功能列表:

复制代码代码如下:

//功能根据一个位图文件,初始化图像
//入参应用程序实例句柄资源ID横向位图个数纵向位图个数
voidInit(HINSTANCEhInstance,intiResource,introw,intcol);
//功能设置环境信息
//入参目的DC(要绘制图像的DC),临时DC,要绘制区域的宽高
voidSetDevice(HDChdest,HDChsrc,intwwin,inthwin);--妈的,C++中画图需要一个hdc,设备上下文需要是一种包含有关某个设备(如显示器或打印机)的绘制属性信息的Windows数据结构。所有绘制调用都通过设备上下文对象进行,这些对象封装了用于绘制线条、形状和文本的WindowsAPI。设备上下文允许在Windows中进行与设备无关的绘制。设备上下文可用于绘制到屏幕、打印机或者图元文件。.net不需要了这个上下文对象,他有了一个.netframework 就不需要这个屁玩意了。
//功能设置图片位置1
//入参设置方法横纵坐标
voidSetPos(intistyle,intx,inty);
//功能图片显示
//入参图片显示方式
voidDraw(DWORDdwRop);
//功能图片缩放显示
//入参横纵方向缩放比例
voidStretch(intx,inty);
//功能图片缩放显示
//入参横纵方向缩放比例缩放图像ID(纵向第几个)
voidStretch(intx,inty,intid);
//功能在指定位置显示图片
//入参横纵坐标
voidShow(intx,inty);
//功能横向居中显示图片
//入参纵坐标
voidShowCenter(inty);
//功能将某个图片平铺在一个区域内
//入参左上右下边界的坐标图片ID(横向第几个)
voidShowLoop(intleft,inttop,intright,intbottom,intiframe);
//功能不规则图片显示
//入参横纵坐标图片ID(横向第几个)
voidShowNoBack(intx,inty,intiFrame);
//功能不规则图片横向平铺
//入参横纵坐标图片ID(横向第几个)平铺个数
voidShowNoBackLoop(intx,inty,intiFrame,intiNum);
 //动画播放
//功能自动播放该图片的所有帧,函数没有实现,但以后肯定要用:)
//入参无
voidShowAni();
//功能设置动画坐标
//入参横纵坐标
voidSetAni(intx,inty);
成员数据
//跟踪打印类
//FILEREPORTf;
//图像句柄
HBITMAPhBm;
//按照行列平均分成几个
intinum;
intjnum;
//按行列分割后,每个图片的宽高(显然各个图片大小一致,派生后,这里的宽高已没有使用意义)
intwidth;
intheight;
//屏幕宽高
intscreenwidth;
intscreenheight;
//要绘制图片的dc
HDChdcdest;
//用来选择图片的临时dc
HDChdcsrc;
//当前位置
intxpos;
intypos;
//是否处于动画播放中(功能没有实现)
intiStartAni;

这个基类的部分函数和变量,在这个游戏中没有使用,是从前几个游戏中保留下来的,所以看起来有些零乱.这个游戏的主要图像功能,由它的派生类完成.由于基类封装了物理层信息(dc和句柄),派生类的编写就容易一些,可以让我专注于逻辑含义.
基类的函数实现上,很简单,主要是以下几点:
1.图片初始化:
复制代码代码如下:

//根据程序实例句柄,位图文件的资源ID,导入该位图,得到位图句柄
hBm=LoadBitmap(hInstance,MAKEINTRESOURCE(iResource));
//获取该位图文件的相关信息
GetObject(hBm,sizeof(BITMAP),&bm);
//根据横纵方向的图片个数,计算出每个图片的宽高(对于超级玛丽,宽高信息由派生类处理)
width=bm.bmWidth/inum;
height=bm.bmHeight/jnum;

下面再来说一说游戏背景类MYBKSKY
类说明:这是一个专门处理游戏背景的类。在横版游戏或射击游戏中,都有一个背景画面,如山、天空、云、星空等等。这些图片一般只有1到2倍屏幕宽度,然后像一个卷轴一样循环移动,连成一片,感觉上像一张很长的图片。这个类就是专门处理这个背景的。在超级玛丽增强版中,主要关卡是3关,各有一张背景图片;从水管进去,有两关,都用一张全黑图片。共四张图。这四张图大小一致,纵向排列在一个位图文件中。MYBKSKY这个类,派生于MYBITMAP。由于背景图片只需要完成循环移动的效果,只需要实现一个功能,而无需关心其他任何问题(例如句柄、dc)。编码起来很简单,再次反映出面向对象的好处。
 实现的原理:
怎样让一张图片像卷轴一样不停移动呢?很简单,假设有一条垂直分割线,把图片分成左右两部分。先显示右边部分,再把左边部分接到图片末尾。不停移动向右移动分割线,图片就会循环地显示。
 成员函数功能列表:
 复制代码代码如下:
classMYBKSKY:publicMYBITMAP
{
public:
MYBKSKY();
~MYBKSKY();--析构函数,怎么样C++中,你有了资源自己释放。我们.net有一个终结者函数也有点析构函数的味道,但不一定自己释放资源吗。
//show
//功能显示一个背景.
//入参无
voidDrawRoll();//循环补空
//功能显示一个背景,并缩放图片
//入参横纵方向缩放比例
voidDrawRollStretch(intx,inty);
//功能指定显示某一个背景,并缩放图片,游戏中用的就是这个函数
//入参横纵方向缩放比例背景图片ID(纵向第几个)
voidDrawRollStretch(intx,inty,intid);--进行中横坐标的操作
//功能设置图片位置
//入参新的横纵坐标
voidMoveTo(intx,inty);
//功能循环移动分割线
//入参分割线移动的距离
voidMoveRoll(intx);
 //data
//分割线横坐标
intxseparate;
};

看一看图片显示类MYANIOBJ
类说明:这个类负责游戏中的图片显示。菜单背景、通关和游戏结束的提示图片,由MYBITMAP处理(大小一致的静态图片)。游戏背景由MYBKSKY处理。其余图片,也就是游戏过程中的所有图片,都是MYANIOBJ处理。
技术原理:游戏中的图片大小不一致,具体在超级玛丽中,可以分成两类:矩形图片和不规则图片。在位图文件中,都是纵向排列各个图片,横向排列各帧。用两个数组存储各个图片的宽和高。为了方便显示某一个图片,用一个数组存储各个图片的纵坐标(即位图文件中左上角的位置)。使用时,由逻辑层指定“哪个图片”的“哪一帧”,显示在“什么位置”。这样图片的显示功能就实现了。
成员函数功能列表:
复制代码代码如下:
classMYANIOBJ:publicMYBITMAP
{
public:
MYANIOBJ();
~MYANIOBJ();
//initlist
//功能初始化宽度数组高度数组纵坐标数组是否有黑白图
//入参宽度数组地址高度数组地址图片数量是否有黑白图(0没有,1有)
//(图片纵坐标信息由函数计算得出)
voidInitAniList(int*pw,int*ph,intinum,intismask);
//功能初始化一些特殊的位图,例如各图片大小一致,或者有其他规律
//入参初始化方式参数1参数2
//(留作以后扩展,目的是为了省去宽高数组的麻烦)
voidInitAniList(intstyle,inta,intb);
//show
//功能显示图片(不规则图片)
//入参横纵坐标(要显示的位置)图片id(纵向第几个),图片帧(横向第几个)
voidDrawItem(intx,inty,intid,intiframe);
//功能显示图片(矩形图片)
//入参横纵坐标(要显示的位置)图片id(纵向第几个),图片帧(横向第几个)
voidDrawItemNoMask(intx,inty,intid,intiframe);
//功能指定宽度,显示图片的一部分(矩形图片)
//入参横纵坐标(要显示的位置)图片id(纵向第几个),显示宽度图片帧(横向第几个)
voidDrawItemNoMaskWidth(intx,inty,intid,intw,intiframe);
//功能播放一个动画即循环显示各帧
//入参横纵坐标(要显示的位置)图片id(纵向第几个)
voidPlayItem(intx,inty,intid);
//宽度数组最多支持20个图片
intwlist[20];
//高度数组最多支持20个图片
inthlist[20];
//纵坐标数组最多支持20个图片
intylist[20];
//动画播放时的当前帧
intiframeplay;
};

看一看魔法攻击类MYANIMAGIC
类说明:玩家有两种攻击方式:普通攻击(子弹),魔法攻击(旋风)。这个类是专门处理旋风的。我最初的想法是用一些特殊的bitblt方法制造特效,例如或、与、异或。试了几次,都失败了。最后只能用“先与后或”的老方法。这个类可看成MYANIOBJ的一个简化版,只支持不规则图片的显示。
成员函数功能列表:
复制代码代码如下:
classMYANIMAGIC:publicMYBITMAP
{
public:
MYANIMAGIC();
~MYANIMAGIC();
//initlist
//功能初始化宽度数组高度数组纵坐标数组(必须有黑白图)
//入参宽度数组地址高度数组地址图片数量
//(图片纵坐标信息由函数计算得出)
voidInitAniList(int*pw,int*ph,intinum);
//功能设置dc
//入参显示dc临时dc(用于图片句柄选择)临时dc(用于特效实现)
voidSetDevice(HDChdest,HDChsrc,HDChtemp);
//show
//功能显示某个图片的某帧
//入参横纵坐标(显示位置)图片id(纵向第几个)帧(横向第几个)
voidDrawItem(intx,inty,intid,intiframe);
//宽度数组
intwlist[20];
//高度数组
inthlist[20];
//纵坐标数组
intylist[20];
//用于特效的临时dc,功能没有实现L
HDChdctemp;
};

这就是我的一些架构