logo资料库

funcode 坦克大战代码.doc

第1页 / 共22页
第2页 / 共22页
第3页 / 共22页
第4页 / 共22页
第5页 / 共22页
第6页 / 共22页
第7页 / 共22页
第8页 / 共22页
资料共22页,剩余部分请下载后查看
C语言课程设计四 坦克大战
一、游戏介绍
二、实验目的
三、实验内容
四、实验准备
五、程序初步设计
六、实验指南
实验一 游戏开始和结束
实验二 玩家坦克在街道中运行
实验三 玩家坦克射击
实验四 敌方坦克产生与运动
实验五 坦克对战
C 语言课程设计四 坦克大战 一、游戏介绍 玩家坦克与敌方坦克在街道中进行巷战,玩家坦克被击中、玩家指挥部被击中或游戏时 间到,一局游戏结束。 二、实验目的 综合应用 C 语言知识和设计知识开发一款小游戏。 三、实验内容 初始界面如下图。 按下空格键后游戏开始,“空格开始”消失,载入地图,并把玩家坦克设置在指挥部左 侧。 游戏时间到,比如 30 秒,玩家坦克被敌方坦克摧毁,或者玩家指挥部被摧毁,一局游 戏结束,游戏回到初始界面,并显示上一局的分数。 游戏区域为下图中最内部的黑色区域,左上角坐标[-26, -22],右下角坐标为[26, 22]。墙 为正方形,边长为 4,坦克也是正方形,比墙略小一点。 玩家用 WASD 键控制坦克上、下、左、右运行,按 J 键开炮。玩家坦克碰到墙就停下来, 需要调转方向才能继续前进。玩家坦克开炮,一炮就能摧毁一块墙,或者一辆敌方坦克。玩 家没摧毁一辆敌方坦克,加 1 分。 玩家指挥部被坦克或者炮弹(不管玩家还是敌方)碰上,都会被摧毁。 每隔几秒钟,比如 3 秒,就会产生一辆敌方坦克。敌方坦克每隔一段时间,比如 1 秒, 就自动开炮。敌方坦克遇到墙就会停下来。停下来的坦克,前方的墙如果被摧毁了,又能继 续前进。每隔几秒钟,比如 2 秒,敌方坦克就会顺时针变换一个方向前进。
四、实验准备 本实验中可能用到的 C 语言标准库函数和 FunCode API Stdio.h 函数原型 int sprintf( char *buffer, const char *format, [ argument] … ) ; 功能与返回值 把格式化的数组写入某个字 符串。 返回值:字符串长度 参数说明与应用举例 char szName[128]; int i=0; sprintf(szName, ”feichong_%d”, i); 将 字 符 串 ”feichong_0” 写 入 到 szName 中 Math.h 函数原型 double atan2( double y, double x ); String.h 函数原型 extern char *strstr(char *str1, char *str2); 功能与返回值 计算 y/x 的反正切值。 返回值:以弧度表示并介于 -pi 到 pi 之 间 ( 不 包 括 -pi)。如需使用角度,需要 转换。 = atan2( ftan 参数说明与应用举例 float (x1-x0) ); 计算通过点(x1,y1)到点(x0,y0)的 连成的直线与 X 轴之间的夹角。 (y1-y0), 功能与返回值 找出 str2 字符串在 str1 字 符 串 中 第 一 次 出 现 的 位 置 (不包括 str2 的串结束符)。 返回值:返回该位置的指针, 如找不到,返回空指针。 参数说明与应用举例 strstr(szName, NULL 说明 szName 中包含 feichong “feichong”) !=
extern int strcmp(const char *s1,const char * s2); 比较字符串 s1 和 s2。 当 s1s2 时,返回值>0 “feichong_0”) strcmp(szName, == 0 说明 szName 与 feichong_0 相等 FunCode API 函数原型 float dGetScreenLeft(); float dGetScreenRight(); float dGetScreenTop(); float dGetScreenBottom(); float dGetSpritePositionX(const char* szName); float dGetSpritePositionY(const char* szName); float dSetSpritePositionX(const char* szName); float dSetSpritePositionY(const char* szName); void char* const fPosX, const float fPosY); dSetSpritePosition(const szName, float szName, void dSetSpriteLinearVelocityX(cons t char* szName, const float fVelX); void dSetSpriteLinearVelocityY(const char* float fVelY); void dSetSpriteLinearVelocity(const char* float const fVelX, const float fVelY); float dGetSpriteRotation(const char* szName); const szName, 功能与返回值 参数说明与应用举例 获取屏幕左边界值 获取屏幕右边界值 获取屏幕上边界值 获取屏幕下边界值 获 取 精 灵 中 心 点 的 X 坐标值 获 取 精 灵 中 心 点 的 Y 坐标值 设 置 精 灵 中 心 点 的 X 坐标值 设 置 精 灵 中 心 点 的 Y 坐标值 设 置 精 灵 中 心 点 的 X 和 Y 坐标值,用来将精 灵放置在某个指定位 置。 设置精灵 X 轴方向速 度 设置精灵 Y 轴方向速 度 设置精灵 X 轴和 Y 轴方 向速度 获取精灵的旋转角度 szName – 精 灵 名 称 。所 有 API 均相同。 游 戏 中 的 精 灵 的 名 称 不 能 相 同。 dSetSpritePosition(“feichong_0”, 0, 0); 将名称为”feichong_0”的精灵的 中心点设置在坐标(0, 0)上 原图的角度 调整后的角度 获得的旋转角度即为两张图片的 角度差
设置文字精灵的整数 数值 设置图片的旋转角度 fRot>0,图片顺时针旋转; fRot<0,图片逆时针旋转。 dSetTextValue(“score”, 100); 名 称 为 score 的 文 字 精 灵 显 示 100 bVisible 为 true,可见; 为 false,不可见。 bShow 为 true,可见; 为 false,不可见。 设置精灵可见或不可 见 设置鼠标可见或不可 见 删除精灵 判 断 某 个 坐 标 点 (fPosX, fPosY) 是 否 在精灵内部 复制一个精灵。 返回值:1 – 复制成 功; 0 – 复制失败。地图 中没有找到对应名称 的精灵用于复制。 设置精灵的世界边界, 精灵碰到边界时,会激 发精灵与边界的碰撞 事件。因此,设置精灵 位置时,考虑到精灵自 身大小,最好离开边界 一段距离。 让精灵从当前位置飞 向另外一点 常用 于判 断一 个物 体是 不是 碰 到另外一个物体 做法 :一 般在 地图 上摆 放一 个 精灵 作为 模板 ,并 设置 好各 种 属性 。不 仅复 制图 片, 还复 制 属性。 szSrcName – 作为模板的精灵 szTarName – 新的精灵名称 - 左边界值 fLeft - 上边界值 fTop - 右边界值 fRight fBottom- 下边界值 Limit - 统一使用 WORLD_LIMIT_NULL fPosX:目标点的 X 坐标值 fPosY:目标点的 Y 坐标值 fSpeed:移动速度 iAutoStop: 移 动 到 终 点 之 后 是 否 自 动 停 止 , true 停 止 false 不停止 int d = dRandomRange[0,3] d 值可能为 0, 1, 2 或 3 float dSetSpriteRotation(const char* szName, float fRot); void dSetTextValue(const char* szName, int iVal); dSetSpriteVisible(const void char* szName, bool bVisible); void dShowCursor(const bool bShow); void dDeleteSprite(const char* szName); bool const char* fPosX, const float fPosY); bool dCloneSprite(const char* szSrcName, char* szTarName); dIsPointInSprite(const float szName, const float void dSetSpriteWorldLimit(const const char*szName, EWorldLimit Limit, const float fTop, const fLeft, const float float fBottom) void dSpriteMoveTo(const char *szName, fPosX, const float fSpeed, const bool iAutoStop ); const fPosY, const fRight, const float float dRandomRange(const int iMin, const int iMax); int 获取 一个 位于 [iMin, iMax]之间的随机整数 五、程序初步设计 如果程序规模比较小的时候,我们习惯一上手就写代码,边写边调整。但是当程序越来 越大,代码越来越多的时候,如果我们还用这种方式编程,程序写到一半的时候,你可能会 恨不得重写一遍。 此,我们在写代码之前,先把程序功能细化一下,并初步设计好程序结构,包括数据结 构和自定义函数。有了比较清晰的思路以后,再开始开发程序。
在本项目中,我们要操作的对象有 6 个:玩家坦克、敌方坦克、玩家子弹、敌方子弹、 墙、玩家指挥部。 其中,墙和指挥部都比较简单,主要是前 4 种,而且它们有共通性:名称、速度、位置, 因此,可以考虑用一个结构体来表示。此外,我们需要用一种数据结构来管理它们。由于敌 方坦克、子弹的数量都无法事先确定,所以我们选择链表而不是数组来管理它们。 struct Weapon{ char float float float int int int fSpeedY; szName[128]; fPosX, fPosY; fSpeedX, fFireTime; iHp; iDir; iType; Weapon* pNext; // 精灵名称 // 精灵坐标 // X 和 Y 方向上速度 // 敌方坦克距下一次开炮的剩余时间 // 生命值 // 朝向:0 - 上方;1 - 右方;2 - 下方;3 - 左方 // 类型: 0 - 我方坦克;1 - 敌方坦克;2 - 我方 // 子弹; 3 - 敌方子弹 // 指向下一个节点的指针 }; 其中,iDir 和 iType 用不同整数表示不同含义。如果在小程序中,我们可以在代码里直 接调用这些整数,但是想象一下下面情况: 如果你连续写了三小时代码,你还能清晰记得 1 表示什么含义吗? 你时不时需要找到 Weapon 结构体定义查看这些数字的含义,然后再引用,出错的概率 有多大? 如果你一不小心,在某处搞错了,比如要处理的是敌方坦克,你却引用 2,需要多少时 间才能把错误找出来? 因此,在做一个比较大的程序时,我们强烈建议用定义一个枚举类型,用我们熟悉的单 词来表示这种数字的含义。 enum Direction{ = UP RIGHT = DOWN = LEFT = 0, 1, 2, 3 // 上方 // 右方 // 下方 // 左方 }; enum Role { MYTANK = ENEMYTANK = MYBULLET = ENEMYBULLET 0, 1, 2, = 3 // 我方坦克 // 敌方坦克 // 我方子弹 // 敌方子弹 }; 除此之外,我们还需要定义一些全局变量来控制程序,根据程序需求,我们目前能考虑 到的: bool int float g_bStart; g_iScore; g_fGameTime; // 标识一局游戏开始还是结束 // 一局游戏得分 // 一局游戏的剩余时间
g_fCreateTank; // 距离生成下一批敌方坦克的剩余时间 float // 游戏地图,0 表示此处为空,1 表示此处有墙。根据游戏空间大小、墙以及坦克大小, // 我们把地图分成 11 行,13 列,每格大小刚好放一块墙。 i nt iMap[11][13]; 正如前面所示,我们用枚举类型来表示一些数字的含义。出于同样的原因,我们也定义 一些全局常量来存储某些数值。 const const const const const float GAME_TIME float CREATE_TANK_TIME = float TANK_SPEED float BULLET_SPEED float FIRE_TIME = 30.f; 5.f; = 5.f; = 8.f; = 2.f; // 一局游戏时间 // 每批次生成坦克的时间间隔 // 坦克速度 // 子弹速度 // 坦克开炮时间间隔 -26.f; = = -22.f; = 26.f; 22.f; float WORLD_LEFT float WORLD_TOP float WORLD_RIGHT float WORLD_BOTTOM = const const const const 好处有两点:第一、跟枚举类型一样,用有具体含义的单词,在具体调用时容易记住, 不会搞错;第二、如果我们需要调整这些数值,只需在全局常量初始化这里调整就可以了。 比如我们要调整坦克速度,没有定义全局常量的话,我们就要找到各处代码用到坦克速度赋 值的地方修改。这样,既麻烦又容易出错。 // 游戏场景边界左值 // 游戏场景边界左值 // 游戏场景边界左值 // 游戏场景边界左值 程序本身由一个 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、 打开 FunCode,在菜单“文件”->“打开项目…”中找到项目“坦克运动”的工程 文件,打开项目; 2、 点击按钮“设置启动 VC 工程”,设置相关版本 VC 作为启动工具; 3、 点击按钮“启动 VC 工程”,打开 VC++工程; 4、 在 List.h 中定义 Weapon 结构体和 Role、Direction 两个枚举类型。参考“程序初步 设计”。 5、 定义一个全局变量和全局常量,用来标识游戏的开始和结束。 Weapon* bool float int const float const float = g_pMyTank g_bStart = g_fGameTime = g_iScore = GAME_TIME = TANK_SPEED = NULL; false; 0.f; 0; 30.f; 5.f; // 我方坦克 // 控制一局游戏开始 true 与结束 false // 一局游戏的剩余时间 // 一局游戏得分 // 一局游戏的时间 // 坦克速度 6、 dOnKeyDown 函数是处理键盘按下事件的。一局游戏还未开始,按下的空格键,游 戏开始。在该函数中完成步骤一的代码。注意两点: if(iKey == KEY_SPACE && g_bStart == false) // 游戏未开始,按下空格键 { g_bStart = true; g_fGameTime = GAME_TIME; dSetSpriteVisible("kaishi", false); dSetTextValue("time", (int)g_fGameTime); dSetTextValue("score", g_iScore); } 7、 运行程序,按下空格键,看看游戏是否按要求运行。 8、 游戏开始后,开始计时。游戏时间到,一局游戏结束,游戏恢复到初始界面。要判 断游戏时间,我们需要在 WinMain 的主循环中进行处理。 // 游戏主循环 while( dEngineMainLoop() ) { // 获取游戏屏幕刷新一次的时间间距 floatfTimeDelta dGetTimeDelta(); = if(g_bStart) { g_fGameTime -= fTimeDelta; if(g_fGameTime > 0.f) // 一局游戏进行中
分享到:
收藏