课程设计三 太空战机
一、游戏介绍
太空战机是玩家用键盘控制战机移动并发射子弹,消灭敌方的战机。敌方战机从右到左
移动,同时上下浮动。同时隔一定的时间发射子弹,我方战机在受到敌方战机子弹攻击时,
战机的颜色会发生变化,生命值也在减少,当我方战机的生命值减少到 0 时,我方战机消失,
同时产生一架我方的新的战机,游戏重新开始。
二、实验目的
综合应用 C 语言的知识开发一款小游戏。
三、实验内容
在外星球上,玩家通过键盘 WSAD 键控制己方战机,消灭外星球的邪恶战机。
要求如下:
1、 游戏运行时,初始界面如下图。
2、 按下空格键,游戏开始,玩家通过 WSAD 键控制己方战机移动;己方战机不能超出
世界边界。
3、 玩家战机每隔 0.3 秒发射一发子弹;
4、 添加敌方战机,每隔 2 秒创建一架敌方战机;
5、 敌方战机每隔 0.3 秒发射一发子弹;
6、 记录游戏的最高分。
游戏初始界面
上海锐格软件有限公司
四、实验指南
实验一 游戏框架的搭建
【实验内容】
1、由于本实验比较复杂,所以我们使用多文件实现
2、添加文件
3、搭建游戏平台
4、还没有用到的函数可以先声明,在定义,函数体为空
【实验思路】
为了让游戏的代码更加清晰,我们使用多文件,一般的代码编写都是一个源程序文件对
应一个头文件,所以我们增加一个源文件程序,一个头文件程序。在本实验中,主程序
Main.cpp 主要是来显示游戏的大体框架,我们将在 LessonX.cpp 中去具体实现不同的函数,
由主程序调用。
【实验指导】
1、 导入模板“AirPlane”,将太空战机的地图初始化;
2、 由于我们这个游戏稍稍有些复杂,所以我们采用多文件的形式,那么我们先添加一
个 LessonX.cpp 和 LessonX.h 文件:
首先在 VC6.0 当中,点击新建按钮,如图红色区域
然后会出现一个空白文件,然后再空白文件中点击一下,选中空白文件,使用快捷
方式 Ctrl+s,或者使用 File 菜单栏下的 Save 选项,如下图
这样会弹出一个对话框,如下图
之后点击红色区域,返回到上一个目录:
1
选中 Src 目录,双击进入 Src 并将 Text2.txt 命名为 LessonX.cpp 点击保存即可。
同样的方式建立 LessonX.h 文件,在保存的时候放在 Hearder 目录下,并将**.txt 文
件更名为 LessonX.h;
3、 下面我们将新建的两个文件添加到我们的工程当中,
首先在 VC 中右击 Source Files,并且选中 Add Files to Folder 选项,如下图
然后弹出对话框,如下图
点击返回上一目录,得到如下对话框
双击 Src 进入到目录里面,选中 LessonX.cpp 文件,点击 OK 即可;
4、 添加 LessonX.h 文件的步骤,右击 Header Files,然后找到 Header 文件夹,双击进
入并选中 LessonX.h 文件,左击 OK 即可;
5、 我 们 来 搭 建 游 戏 的 框 架 , 我 们 在 主 函 数 中 的 while 循 环 的 最 后 添 加 一 个 函 数
GameMainLoop(fDeltaTime)的调用,该函数是游戏的中心部分,游戏就是通过它不
断的刷新数据;
6、 现在我们在 LessonX.cpp 中来实现这个 GameMainLoop 函数,游戏主循环,此函数
2
将被不停的调用,引擎每刷新一次屏幕,此函数即被调用一次用以处理游戏的开始、
进行中、结束等各种状态. 函数参数 fDeltaTime : 上次调用本函数到此次调用本函
数的时间间隔,单位:秒
上海锐格软件有限公司
fDeltaTime )
void GameMainLoop( float
{
switch( g_iGameState )
{
// 初始化游戏,清空上一局相关数据
case 1:
{
g_iGameState = 2; // 初始化之后,将游戏状态设置为进行中
GameInit();
}
break;
// 游戏进行中,处理各种游戏逻辑
case 2:
{
// 判断输赢
if( true)
{
//游戏结束。调用游戏结算函数,并把游戏状态修改为结束状态
g_iGameState = 0;
GameEnd();
}
else // 游戏未结束,继续游戏
{
GameRun( fDeltaTime );
}
}
break;
// 游戏结束/等待按空格键开始
case 0:
default:
break;
};
}
7、 以上游戏框架中 GameInit、GameRun、GameEnd 函数我们还没有定义,那么我们就
在 LessonX.cpp 中定义这三个函数;
// 注意<>和“”的区别
#include
#include “LessonX.h”
// 游戏状态的定义
g_iGameState = 0;
// 函数声明
void GameInit();
3
void GameRun(float);
void GameEnd();
// 函数定义
Void GameInit()
{
}
Void GameRun(float fDeltaTime)
{
}
Void GameEnd()
{
}
这样我们的游戏框架就搭建好了,编译一下就可以运行了,虽然有什么效果。
实验二 游戏需要的实物及分析
【实验内容】
1、分析游戏中需要的对实物
2、分析这些实物需要的变量
3、这些变量进行初始化
【实验思路】
对于本游戏,我们至少需要以下几个实物,我方战机、敌方战机、我方战机发射的子弹、
敌方战机发射的子弹、当前分数、最高分数等,关于战机我们还需要考虑到生命值、发射子
弹的时间、敌机上下浮动等变量,关于子弹我们需要考虑子弹的生命值、对敌方或者是我方
的伤害值、是由谁发射的等问题。
【实验指导】
1、为了便于对战机和子弹的管理,我们使用结构体来将战机和子弹的变量存放在一起。
战机和子弹的管理我们都放在链表中进行管理, 我们首先增加 ListX.cpp 和 List.h 这两
个文件,并且将这两个文件添加到工程当中;
2、在 LessonX.h 中添加防止头文件包含的语句:
#ifndef _LESSONX_H_
#define _LESSONX_H_
... ...
#endif
我们将代码写入到#define 和#endif 的中间
//_LESSONX_H_
3、然后我们加入头文件#include “CommonAPI.h”
4、接着我们用宏定义定义一些极限值
#define MAX_NAME_LEN 128 // 名字的最大长度
#define CONTROL_MAX_HP 1000
#define BULLET_DAMAGE_1 100
#define VER_MIN_CREATE
#define VER_MAX_CREATE
1
6
// 我方战机的最大生命值是 1000
// 子弹 1 的伤害值是 100
5、之后我们用枚举类型列出本游戏需要用的三种类型:
enum ESpriteType
{
4
SPRITE_CONTROL,
SPRITE_VER,
SPRITE_BULLET1
// 我们控制的战机
// 敌方战机
// 朝前方发射的子弹,且可以被销毁
上海锐格软件有限公司
};
6、再接下来就是我们的结构体了
struct SGameSprite
{
char szName[MAX_NAME_LEN];
int iHp;
int iScore;
int iDamage;
ESpriteType eSpriteType;
int iBulletType;
float fFireAffterCreate;
float fBulletTime;
float fFloatTime;
bool bFloatUp;
// 精灵的名字
// Health Point 生命值
// 击毁本精灵可以获得的分数值
// 自身碰到敌方可以给敌方造成的伤害
// 精灵的类型
// 该子弹类型,通过该类型判断是不是受到伤害
// 精灵被创建多长时间可以发射子弹
// 子弹 1 被发射的时间间隔
// 上下浮动的时间
// 是否上下浮动
};
这样我们的 LessonX.h 文件就已经初始化好了,接下来使用就比较方便了;
实验三 对游戏中的精灵进行链表管理
【实验内容】
1、精灵是个体,将这些个体联系在一起便于管理
2、我们要根据链表由名字获得精灵,遍历精灵链表,添加精灵到链表,根据名字删除
链表,删除所有精灵
【实验思路】
如果通过个体去处理问题,在不断有其他精灵创建的前提下,是相当麻烦的,比如战机
撞到世界边界的问题,如果不用链表遍历操作的话,通过 if-else 判断是无法想象的工作量,
其实也是无法完成的,因为你不知道到底会有多少架战机被创造出来,所以为了便于对这些
个体进行管理和操作,我们有必要为这些精灵个体建立一个链表,这样对于精灵的遍历、插
入、删除等操作就非常遍历了。
【实验指导】
//__LISTX_H_
1、在 List.h 文件中,首先要防止头文件重复包含:
#ifndef _LISTX_H_
#define _LISTX_H_
... ...
#endif
以后所有代码都写在#define 和#endif 之间;
2、接着要将头文件包含进来
#include
#include “CommonAPI.h”
#include “LessonX.h”
3、建立一个结构体的结构体
5
SpriteStruct
struct
{
SGameSprite
SpriteStruct
SpriteStruct
*pSprite;
*pNext;
*pPrev;
GList_GetListSize();
*GList_GetSpriteByIndex( const int iIndex );
*GList_GetSpriteByName( const char *szName );
};
4、之后就是声明一下操作链表的函数,具体用到的函数如下
extern int
// 根据名字获取 Sprite
extern SGameSprite
// 根据索引获取 Sprite,如果要遍历链表并删除其中的某个元素,请从后面往前面遍历
(即索引初始化为链表大小然后递减),否则必然出错
extern SGameSprite
// 添加一个 Sprite 到链表里
extern SpriteStruct *GList_AddSprite( SGameSprite *pSprite );
// 根据名字删除 Sprite. bDeleteImage : 是否删除该 Sprite 在地图上的图片显示
extern void
GList_DeleteSprite( const char *szName, bool bDeleteImage = true );
// 根据指针删除 Sprite. bDeleteImage : 是否删除该 Sprite 在地图上的图片显示
extern void
GList_DeleteSprite( SGameSprite *pSprite, bool bDeleteImage = true );
// 删除所有 Sprite. bDeleteImage : 是否删除该 Sprite 在地图上的图片显示
extern void
5、有了上面的函数声明,我们在 ListX.cpp 中给出函数的实现,在函数实现中都有注释,
我们就不在做详解,并且这部分比较难,同学可以直接抄在文件当中,之后做一下分析
就可以了
SpriteStruct
int g_iListSize = 0;
// 链表元素个数
/////////////////////////////////////////////////////////////////////////////////
int GList_GetListSize()
{
GList_DeleteAllSprite( bool bDeleteImage = true );
*g_pListHeader
=
NULL;
return g_iListSize;
}
//=========================================================================
// 根据名字获取 Sprite
SGameSprite* GList_GetSpriteByName( const char *szName )
{
SpriteStruct
*pPtr
while( NULL != pPtr )
{
=
g_pListHeader;
if( strcmp( pPtr->pSprite->szName, szName ) == 0 )
return pPtr->pSprite;
pPtr =
pPtr->pNext;
}
return NULL;
6
上海锐格软件有限公司
}
//=========================================================================
// 任务:根据索引获取 Sprite
// 提示:参考“根据名字获取 Sprite”
// 提示:如果要遍历链表并删除其中的某个元素,请从后面往前面遍历(即索引初始化
为链表大小然后递减),否则必然出错
SGameSprite *GList_GetSpriteByIndex( const int iIndex )
{
// 通过循环从链表头开始查找,当循环次数达到 iIndex 次时,得到该索引对应的元素
0;
=
iLoop
int
SpriteStruct
*pPtr
while( NULL != pPtr )
{
=
g_pListHeader;
if( iLoop == iIndex )
return pPtr->pSprite;
iLoop++;
pPtr =
}
return NULL;
pPtr->pNext;
}
//=========================================================================
// 添加一个 Sprite 到链表里
SpriteStruct *GList_AddSprite( SGameSprite *pSprite )
{
if( NULL == pSprite )
return NULL;
=
(SpriteStruct*)malloc( sizeof(SpriteStruct) );
*pPtr
SpriteStruct
pPtr->pSprite =
pPtr->pNext
pPtr->pPrev
// 插入链表表尾
if( NULL == g_pListHeader )
pSprite;
=
=
NULL;
NULL;
g_pListHeader =
pPtr;
else
{
SpriteStruct
while( NULL != pTemp->pNext )
*pTemp =
g_pListHeader;
pTemp
=
pTemp->pNext;
pPtr->pPrev
pTemp->pNext=
=
pPtr;
pTemp;
}
g_iListSize++;
7