C语言课程设计--坦克大战一、游戏介绍玩家坦克与敌方坦克在街道中进行巷战,玩家坦克被击中、玩家指挥部被击中或游戏时间到,一局游戏结束。二、实验目的综合应用C语言知识和设计知识开发一款小游戏。三、实验内容初始界面如下图。按下空格键后游戏开始,“空格开始”消失,载入地图,并把玩家坦克设置在指挥部左侧。游戏时间到,比如30秒,玩家坦克被敌方坦克摧毁,或者玩家指挥部被摧毁,一局游戏结束,游戏回到初始界面,并显示上一局的分数。游戏区域为下图中最内部的黑色区域,左上角坐标[-26,-22],右下角坐标为[26,22]。墙为正方形,边长为4,坦克也是正方形,比墙略小一点。玩家用WASD键控制坦克上、下、左、右运行,按J键开炮。玩家坦克碰到墙就停下来,需要调转方向才能继续前进。玩家坦克开炮,一炮就能摧毁一块墙,或者一辆敌方坦克。玩家没摧毁一辆敌方坦克,加1分。玩家指挥部被坦克或者炮弹(不管玩家还是敌方)碰上,都会被摧毁。每隔几秒钟,比如3秒,就会产生一辆敌方坦克。敌方坦克每隔一段时间,比如1秒,就自动开炮。敌方坦克遇到墙就会停下来。停下来的坦克,前方的墙如果被摧毁了,又能继续前进。每隔几秒钟,比如2秒,敌方坦克就会顺时针变换一个方向前进。四、实验准备本实验中可能用到的C语言标准库函数和FunCodeAPIStdio.h函数原型功能与返回值参数说明与应用举例intsprintf(char*buffer,constchar*format,[argument]…);把格式化的数组写入某个字符串。返回值:字符串长度charszName[128];inti=0;sprintf(szName,”feichong_%d”,i);将字符串”feichong_0”写入到szName中Math.h函数原型功能与返回值参数说明与应用举例doubleatan2(doubley,doublex);计算y/x的反正切值。返回值:以弧度表示并介于-pi到pi之间(不包括-pi)。如需使用角度,需要转换。floatftan=atan2((y1-y0),(x1-x0));计算通过点(x1,y1)到点(x0,y0)的连成的直线与X轴之间的夹角。String.h函数原型功能与返回值参数说明与应用举例externchar*strstr(char*str1,char*str2);找出str2字符串在str1字符串中第一次出现的位置(不包括str2的串结束符)。返回值:返回该位置的指针,如找不到,返回空指针。strstr(szName,“feichong”)!=NULL说明szName中包含feichongexternintstrcmp(constchar*s1,constchar*s2);比较字符串s1和s2。当s1s2时,返回值0当s1=s2时,返回值=0当s1s2时,返回值0strcmp(szName,“feichong_0”)==0说明szName与feichong_0相等FunCodeAPI函数原型功能与返回值参数说明与应用举例floatdGetScreenLeft();获取屏幕左边界值floatdGetScreenRight();获取屏幕右边界值floatdGetScreenTop();获取屏幕上边界值floatdGetScreenBottom();获取屏幕下边界值floatdGetSpritePositionX(constchar*szName);获取精灵中心点的X坐标值szName–精灵名称。所有API均相同。游戏中的精灵的名称不能相同。floatdGetSpritePositionY(constchar*szName);获取精灵中心点的Y坐标值floatdSetSpritePositionX(constchar*szName);设置精灵中心点的X坐标值floatdSetSpritePositionY(constchar*szName);设置精灵中心点的Y坐标值voiddSetSpritePosition(constchar*szName,constfloatfPosX,constfloatfPosY);设置精灵中心点的X和Y坐标值,用来将精灵放置在某个指定位置。dSetSpritePosition(“feichong_0”,0,0);将名称为”feichong_0”的精灵的中心点设置在坐标(0,0)上voiddSetSpriteLinearVelocityX(constchar*szName,constfloatfVelX);设置精灵X轴方向速度voiddSetSpriteLinearVelocityY(constchar*szName,constfloatfVelY);设置精灵Y轴方向速度voiddSetSpriteLinearVelocity(constchar*szName,constfloatfVelX,constfloatfVelY);设置精灵X轴和Y轴方向速度floatdGetSpriteRotation(constchar*szName);获取精灵的旋转角度原图的角度调整后的角度获得的旋转角度即为两张图片的角度差floatdSetSpriteRotation(constchar*szName,floatfRot);设置图片的旋转角度fRot0,图片顺时针旋转;fRot0,图片逆时针旋转。voiddSetTextValue(constchar*szName,intiVal);设置文字精灵的整数数值dSetTextValue(“score”,100);名称为score的文字精灵显示100voiddSetSpriteVisible(constchar*szName,boolbVisible);设置精灵可见或不可见bVisible为true,可见;为false,不可见。voiddShowCursor(constboolbShow);设置鼠标可见或不可见bShow为true,可见;为false,不可见。voiddDeleteSprite(constchar*szName);删除精灵booldIsPointInSprite(constchar*szName,constfloatfPosX,constfloatfPosY);判断某个坐标点(fPosX,fPosY)是否在精灵内部常用于判断一个物体是不是碰到另外一个物体booldCloneSprite(constchar*szSrcName,constchar*szTarName);复制一个精灵。返回值:1–复制成功;0–复制失败。地图中没有找到对应名称的精灵用于复制。做法:一般在地图上摆放一个精灵作为模板,并设置好各种属性。不仅复制图片,还复制属性。szSrcName–作为模板的精灵szTarName–新的精灵名称voiddSetSpriteWorldLimit(constchar*szName,constEWorldLimitLimit,constfloatfLeft,constfloatfTop,constfloatfRight,constfloatfBottom)设置精灵的世界边界,精灵碰到边界时,会激发精灵与边界的碰撞事件。因此,设置精灵位置时,考虑到精灵自身大小,最好离开边界一段距离。fLeft-左边界值fTop-上边界值fRight-右边界值fBottom-下边界值Limit-统一使用WORLD_LIMIT_NULLvoiddSpriteMoveTo(constchar*szName,constfloatfPosX,constfloatfPosY,让精灵从当前位置飞向另外一点fPosX:目标点的X坐标值fPosY:目标点的Y坐标值fSpeed:移动速度iAutoStop:移动到终点之后是constfloatfSpeed,constbooliAutoStop);否自动停止,true停止false不停止intdRandomRange(constintiMin,constintiMax);获取一个位于[iMin,iMax]之间的随机整数intd=dRandomRange[0,3]d值可能为0,1,2或3五、程序初步设计如果程序规模比较小的时候,我们习惯一上手就写代码,边写边调整。但是当程序越来越大,代码越来越多的时候,如果我们还用这种方式编程,程序写到一半的时候,你可能会恨不得重写一遍。此,我们在写代码之前,先把程序功能细化一下,并初步设计好程序结构,包括数据结构和自定义函数。有了比较清晰的思路以后,再开始开发程序。在本项目中,我们要操作的对象有6个:玩家坦克、敌方坦克、玩家子弹、敌方子弹、墙、玩家指挥部。其中,墙和指挥部都比较简单,主要是前4种,而且它们有共通性:名称、速度、位置,因此,可以考虑用一个结构体来表示。此外,我们需要用一种数据结构来管理它们。由于敌方坦克、子弹的数量都无法事先确定,所以我们选择链表而不是数组来管理它们。structWeapon{charszName[128];//精灵名称floatfPosX,fPosY;//精灵坐标floatfSpeedX,fSpeedY;//X和Y方向上速度floatfFireTime;//敌方坦克距下一次开炮的剩余时间intiHp;//生命值intiDir;//朝向:0-上方;1-右方;2-下方;3-左方intiType;//类型:0-我方坦克;1-敌方坦克;2-我方//子弹;3-敌方子弹Weapon*pNext;//指向下一个节点的指针};其中,iDir和iType用不同整数表示不同含义。如果在小程序中,我们可以在代码里直接调用这些整数,但是想象一下下面情况:如果你连续写了三小时代码,你还能清晰记得1表示什么含义吗?你时不时需要找到Weapon结构体定义查看这些数字的含义,然后再引用,出错的概率有多大?如果你一不小心,在某处搞错了,比如要处理的是敌方坦克,你却引用2,需要多少时间才能把错误找出来?因此,在做一个比较大的程序时,我们强烈建议用定义一个枚举类型,用我们熟悉的单词来表示这种数字的含义。enumDirection{UP=0,//上方RIGHT=1,//右方DOWN=2,//下方LEFT=3//左方};enumRole{MYTANK=0,//我方坦克ENEMYTANK=1,//敌方坦克MYBULLET=2,//我方子弹ENEMYBULLET=3//敌方子弹};除此之外,我们还需要定义一些全局变量来控制程序,根据程序需求,我们目前能考虑到表示游戏状态的变量、表示游戏得分的变量、游戏剩余时间变量以及距离下一辆坦克产生的时间等变量;//游戏地图,0表示此处为空,1表示此处有墙。根据游戏空间大小、墙以及坦克大小,//我们把地图分成11行,13列,每格大小刚好放一块墙。intiMap[11][13];正如前面所示,我们用枚举类型来表示一些数字的含义。出于同样的原因,我们也定义一些全局常量来存储某些数值。constfloatGAME_TIME=30.f;//一局游戏时间constfloatCREATE_TANK_TIME=5.f;//每批次生成坦克的时间间隔constfloatTANK_SPEED=5.f;//坦克速度constfloatBULLET_SPEED=8.f;//子弹速度constfloatFIRE_TIME=2.f;//坦克开炮时间间隔constfloatWORLD_LEFT=-26.f;//游戏场景边界左值constfloatWORLD_TOP=-22.f;//游戏场景边界左值constfloatWORLD_RIGHT=26.f;//游戏场景边界左值constfloatWORLD_BOTTOM=22.f;//游戏场景边界左值好处有两点:第一、跟枚举类型一样,用有具体含义的单词,在具体调用时容易记住,不会搞错;第二、如果我们需要调整这些数值,只需在全局常量初始化这里调整就可以了。比如我们要调整坦克速度,没有定义全局常量的话,我们就要找到各处代码用到坦克速度赋值的地方修改。这样,既麻烦又容易出错。程序本身由一个main.cp