10 C语言课程设计坦克大战提高篇.docx
- 文档编号:23288655
- 上传时间:2023-05-16
- 格式:DOCX
- 页数:27
- 大小:92.53KB
10 C语言课程设计坦克大战提高篇.docx
《10 C语言课程设计坦克大战提高篇.docx》由会员分享,可在线阅读,更多相关《10 C语言课程设计坦克大战提高篇.docx(27页珍藏版)》请在冰豆网上搜索。
10C语言课程设计坦克大战提高篇
C语言课程设计--坦克大战
一、游戏介绍
玩家坦克与敌方坦克在街道中进行巷战,玩家坦克被击中、玩家指挥部被击中或游戏时间到,一局游戏结束。
二、实验目的
综合应用C语言知识和设计知识开发一款小游戏。
三、实验内容
初始界面如下图。
按下空格键后游戏开始,“空格开始”消失,载入地图,并把玩家坦克设置在指挥部左侧。
游戏时间到,比如30秒,玩家坦克被敌方坦克摧毁,或者玩家指挥部被摧毁,一局游戏结束,游戏回到初始界面,并显示上一局的分数。
游戏区域为下图中最内部的黑色区域,左上角坐标[-26,-22],右下角坐标为[26,22]。
墙为正方形,边长为4,坦克也是正方形,比墙略小一点。
玩家用WASD键控制坦克上、下、左、右运行,按J键开炮。
玩家坦克碰到墙就停下来,需要调转方向才能继续前进。
玩家坦克开炮,一炮就能摧毁一块墙,或者一辆敌方坦克。
玩家没摧毁一辆敌方坦克,加1分。
玩家指挥部被坦克或者炮弹(不管玩家还是敌方)碰上,都会被摧毁。
每隔几秒钟,比如3秒,就会产生一辆敌方坦克。
敌方坦克每隔一段时间,比如1秒,就自动开炮。
敌方坦克遇到墙就会停下来。
停下来的坦克,前方的墙如果被摧毁了,又能继续前进。
每隔几秒钟,比如2秒,敌方坦克就会顺时针变换一个方向前进。
四、实验准备
本实验中可能用到的C语言标准库函数和FunCodeAPI
Stdio.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中包含feichong
externintstrcmp(constchar*s1,constchar*s2);
比较字符串s1和s2。
当s1 当s1=s2时,返回值=0 当s1>s2时,返回值>0 strcmp(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); 设置图片的旋转角度 fRot>0,图片顺时针旋转; fRot<0,图片逆时针旋转。 voiddSetTextValue(constchar*szName,intiVal); 设置文字精灵的整数数值 dSetTextValue(“score”,100); 名称为score的文字精灵显示100 voiddSetSpriteVisible(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_NULL voiddSpriteMoveTo(constchar*szName,constfloatfPosX,constfloatfPosY,constfloatfSpeed,constbooliAutoStop); 让精灵从当前位置飞向另外一点 fPosX: 目标点的X坐标值 fPosY: 目标点的Y坐标值 fSpeed: 移动速度 iAutoStop: 移动到终点之后是否自动停止,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.cpp文件组成,包含7个函数,一个主函数WinMain和6个事件函数(键盘按下、键盘弹起、鼠标移动、鼠标点击、精灵与精灵的碰撞、精灵与世界边界的碰撞)。 我们增加两个文件,List.h和List.cpp,主要用来声明和定义结构体以及链表操作的函数。 链表操作至少包括下面四个操作: AddToList//往链表里添加一个节点; DeleteNode//从链表里删除一个节点; FindeNode//从链表里查找一个节点; DeleteList//删除整个链表。 一局游戏结束,我们需要把本局游戏中还没删除的精灵全部删除,从而保持下一局游戏的“干净”,所以往往需要删除整个链表。 载入地图、玩家坦克运动、敌方坦克的生成、坦克发射子弹,我们也可以考虑定义单独的函数来完成: LoadMap//载入地图 MoveMyTank//玩家坦克运动 CreateEnemyTanks//敌方坦克生成 OnFire//坦克发射炮弹 本游戏,大部分功能都是通过碰撞来实现的,比如玩家坦克子弹击中敌方坦克,就是玩家子弹与敌方坦克的碰撞。 子弹到了游戏界面外,就是子弹与世界边界的碰撞。 我们通过下面表格,把整个游戏中各种碰撞整理出来,表格中的响应是反应方的响应。 “无”表示不可能发生碰撞。 我们知道,要发生碰撞,碰在一起的两个精灵,必须一方具有“发送碰撞”的属性,另外一方具有“接受碰撞”的属性。 由于敌方坦克与敌方坦克、敌方子弹与敌方子弹也可能发生碰撞,所以需要同时设置“发送碰撞”和“接受碰撞”的属性 参与方方 反应 玩家坦克 发送 敌方坦克 接受 发送 玩家子弹 发送 敌方子弹 接受 发送 墙 接受 玩家指挥部 接受 世界边界 玩家坦克 发送 无 游戏结束 无 游戏结束 停止 游戏结束 停止 敌方坦克 接受 发送 停止 后面一辆调头; 对撞,都调头 删除 不处理 停止 游戏结束 顺时针调转一个方向 玩家子弹 发送 无 删除 加分 无 删除 删除 游戏结束 删除 敌方子弹 接受 发送 删除 删除 删除 均删除 删除 游戏结束 删除 墙 接受 不处理 不处理 删除 删除 无 无 无 玩家指挥部 接受 游戏结束 游戏结束 游戏结束 游戏结束 无 无 无 根据上面表格,我们可以定义5个碰撞函数 OnMyTankColOther//玩家坦克与其他精灵碰撞 OnEnemyTankColOther//敌方坦克与其他精灵碰撞 OnBulletColOther//子弹与其他精灵碰撞。 子弹碰上其他精灵,本身都是被 //删除,比较简单,因此两种子弹合并起来 OnWallColOther//墙与其他精灵碰撞 OnGoalColOther//玩家指挥部与其他精灵碰撞 其中,col是collision(碰撞)的缩写。 精灵与世界边界的碰撞,比较简单,我们直接在dOnSpriteColWorldLimit函数中完成。 我们现在对整个程序架构有了一定了解。 现在可以开始编程了,在编程的过程,我们还会根据细节进一步完善。 六、实验指南 实验一游戏开始和结束 【实验内容】 步骤一、按空格键,游戏开始,“空格开始”字样消失,设置初始时间为30。 步骤二、按WASD键,控制坦克上下左右运动。 步骤三、游戏开始后,右上角实时显示剩余时间。 步骤四、当超过30秒,游戏结束,重新显示“空格开始“字样,游戏时间设为0,坦克回到初始位置。 【实验思路】 在List.h中定义Weapon结构体和两个枚举类型。 定义全局变量和全局常量。 定义MoveMyTank函数,控制玩家坦克上下左右移动。 【实验指导】 1、创建“TankWar”工程,并且添加List.h文件,并在List.h中定义Weapon结构体和Role、Direction两个枚举类型。 参考“程序初步设计”; 2、接着我们要定义一个游戏状态的变量,依次变量来判断游戏是否开始,并且还要定义我们控制的坦克精灵的对象指针; 3、dOnKeyDown函数是处理键盘按下事件的。 一局游戏还未开始,按下的空格键,游戏开始。 此时应该“空格开始”消失,显示本局的游戏时间以及得分等信息, 4、运行程序,按下空格键,看看游戏是否按要求运行。 5、游戏开始后,开始计时。 游戏时间到,一局游戏结束,游戏恢复到初始界面。 要判断游戏时间,我们需要在WinMain的主循环中进行处理。 //游戏主循环 while(dEngineMainLoop()){ //获取游戏屏幕刷新一次的时间间距 floatfTimeDelta=dGetTimeDelta(); if(g_bStart) { g_fGameTime-=fTimeDelta; if(g_fGameTime>0.f)//一局游戏进行中 { } else//一局游戏结束 { g_bStart=false; } } else//游戏结束后处理 { } } 6、一局游戏开始后,在WinMain函数中的主循环里,通过dSetTextValue把(int)g_fGameTime的数值显示在名为“time”的文字精灵里,从而实时显示剩余时间(显示整秒的时间)。 7、一局游戏进行中,每循环一次,g_fGameTime减去一次刷屏的时间。 当g_fGameTime≤0,说明一局游戏时间已完,游戏停止,重新显示“空格开始“字样。 注意: g_iStart要改为0,否则下一局就不能正确判断游戏开始或结束。 8、我们定义一个MoveMyTank函数来处理玩家坦克的运动。 因为玩家坦克是用键盘控制的,所以需要传递两个参数,一个用来表示按下或松开了哪个键盘,一个用来表示是按下还是松开。 按下W键,设置玩家坦克向上的速度;按下A键,设置向左速度;按下S键,设置向下的速度,按下D键,设置向右速度。 松开键盘,坦克速度为0。 此外,按下键盘时,还需要相应设置坦克朝向iDir。 注意: 如果函数定义写在函数调用之后,需要在函数调用前面进行声明。 voidMoveMyTank(intiKey,intiPress) { } 9、完成函数,我们在dOnKeyDown和dOnKeyUp函数中进行调用。 //按下键盘 if(g_bStart)//一局游戏进行中 { if(iKey==KEY_W||iKey==KEY_A||iKey==KEY_S||iKey==KEY_D) { MoveMyTank(iKey,1); } } //松开键盘 if(g_bStart) { if(iKey==KEY_W||iKey==KEY_A||iKey==KEY_S||iKey==KEY_D) { MoveMyTank(iKey,0); } } 10、玩家坦克开到边界时,让它停止运动。 我们在dOnSpriteColWorldLimit函数中,设设置玩家坦克速度为0。 if(strcmp(szName,g_pMyTank->szName)==0)//玩家坦克 { dSetSpriteLinearVelocity(g_pMyTank->szName,0.f,0.f); } 11、因为增加了新功能,我们在一局游戏结束时,就必须考虑到新情况: 游戏结束时,坦克已经不在原来位置了;坦克朝向发生变化;游戏结束的时候,玩家正好按下某个控制键,给坦克设置了速度。 我们该如何处理这三种情况? 实验二玩家坦克在街道中运行 【实验内容】 步骤一、一局游戏开始时载入地图。 步骤二、一局游戏结束后删除地图。 步骤三、玩家坦克碰到墙的话,不能继续前行。 【实验思路】 利用数组的值来载入地图,值为0,说明此处为空;值为1,说明此处有墙。 游戏开始后载入地图,但只能载入一次。 如果不停载入,会占有大量资源。 游戏结束时,需要把地图中墙都删除。 创建一个新的精灵,首先需要给它命名。 有规则地给精灵命名,有利于我们对精灵进行处理。 我们按照墙生成的先后顺序,分别给精灵命名为: wall0,wall1,wall2… 设置玩家坦克的碰撞属性,并专门定义一个函数处理玩家坦克发生碰撞后,玩家坦克的响应。 【实验指导】 1、增加新的全局变量定义 intg_iWallCount=0;//记录墙的数量 intg_iMap[11][13];//地图数组 一局游戏结束后,我们需要卸载地图,也就是把墙都删除了。 因此,我们需要知道墙的数量。 2、定义LoadMap函数,用来载入地图。 定义一个局部数组并初始化,然后依次赋值给全局数组。 局部数组初始化如下: intiMap[11][13]= { {0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,1,0,1,0,1,0,1,0,1,0,1,0}, {0,1,0,1,0,1,0,1,0,1,0,1,0}, {0,1,0,1,0,1,1,1,0,1,0,1,0}, {0,1,0,1,0,1,0,1,0,1,0,1,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,1,1,1,0,0,0,0,0}, {0,1,0,1,0,0,0,0,0,1,0,1,0},
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 10 C语言课程设计坦克大战提高篇 语言 课程设计 坦克 大战 提高