c语言程序设计坦克大战提高篇funcode环境.docx

上传人:b****8 文档编号:10229421 上传时间:2023-02-09 格式:DOCX 页数:27 大小:91.54KB
下载 相关 举报
c语言程序设计坦克大战提高篇funcode环境.docx_第1页
第1页 / 共27页
c语言程序设计坦克大战提高篇funcode环境.docx_第2页
第2页 / 共27页
c语言程序设计坦克大战提高篇funcode环境.docx_第3页
第3页 / 共27页
c语言程序设计坦克大战提高篇funcode环境.docx_第4页
第4页 / 共27页
c语言程序设计坦克大战提高篇funcode环境.docx_第5页
第5页 / 共27页
点击查看更多>>
下载资源
资源描述

c语言程序设计坦克大战提高篇funcode环境.docx

《c语言程序设计坦克大战提高篇funcode环境.docx》由会员分享,可在线阅读,更多相关《c语言程序设计坦克大战提高篇funcode环境.docx(27页珍藏版)》请在冰豆网上搜索。

c语言程序设计坦克大战提高篇funcode环境.docx

c语言程序设计坦克大战提高篇funcode环境

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},

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 能源化工

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1