如果这篇文章对你有所帮助的话,麻烦点个赞叭(◍•͈⌔•͈◍)
目录
一、问题描述
二、系统设计
2.1、功能模块设计
1、最基础变量声明
2、有关飞机的结构体变量定义
3、初始化敌机信息
4、初始化玩家飞机信息
5、加载图片资源
6、游戏画面加载
7、计时器编写
8、子弹创建模块
9、敌机创建模块
10、子弹移动模块
11、敌机移动模块
12、玩家移动模块
13、撞击模块(与子弹或玩家)
14、主程序
2.2模块流程图
三、程序源代码
为了实现飞机和敌机出现在设置的范围内,并且飞机能够发射子弹攻打敌机,敌机的出现位置不确定,玩家自己需要控制飞机来发射子弹攻打敌机。当敌机与飞机碰撞时,游戏结束,程序自动退出。
enum wh //enum枚举类型,程序简洁,方便后续对数据进行修改
{
bgwidth = 480,
bgheight = 700,
bullet_num = 15,
enemy_num = 10,
BIG,
SMALL
};
enum枚举类型,程序简洁,方便后续对数据进行修改
struct plane
{
int x; //玩家飞机的横坐标
int y; //玩家飞机的纵坐标
int width; //飞机的宽度
int height; //飞机的高度
int hp; //敌机血量
int type; //敌机类型
bool live; //布尔类型变量非0即1,用于判断飞机是否仍然存活
}player, bullet[bullet_num], enemy[enemy_num];
//每个子弹都有自己的状态,所以这里要以数组的形式定义
这里定义一个结构体,用于存储游戏中的飞机、子弹、敌机等元素的属性信息。其中结构体包括了位置(横、纵坐标)、大小(宽度、高度)、血量、类型、是否存活等。
void enemyHP(int i)
{
int flag = rand() % 10;
if (flag >= 0 && flag <= 1)
{
enemy[i].type = BIG;
enemy[i].hp = 3;
enemy[i].width = 169;
enemy[i].height = 230;
}
else
{
enemy[i].type = SMALL;
enemy[i].hp = 1;
enemy[i].width = 57;
enemy[i].height = 43;
}
}
enemyHP函数用于初始化敌机的属性信息。在游戏中,每一个敌机都需要具备不同的属性,例如血量、大小、类型等,通过调用该函数可以随机生成不同类型的敌机。
函数中首先调用 rand 函数生成一个[0, 9]的随机整数,根据这个数决定生成大型敌机还是小型敌机。如果随机数在 0-1 区间,则把敌机类型设置为 BIG,否则将敌机类型设置为 SMALL
void game_csh()
{
//初始化飞机起始位置和存活状态
player.x = bgwidth / 2 - 50;
player.y = bgheight - 120;
player.live = true;
player.width = 114;
player.height = 85;
//当然子弹也要初始化(数组以数组的方式初始化)
for (int i = 0; i < bullet_num; i++)
{
bullet[i].x = 0;
bullet[i].y = 0;
bullet[i].live = false;
}
//初始化敌机(数组)
for (int i = 0; i < enemy_num; i++)
{
enemy[i].live = false;
enemyHP(i);
}
}
game_csh函数用于在游戏开始时初始化游戏的各个元素。首先设置玩家飞机的起始位置和存活状态以及玩家飞机的高度宽度。接着,使用循环语句初始化子弹数组里的每一个子弹,将其坐标设置为 (0, 0),并将其 live 属性设置为 false,表示当前该子弹并不存在于游戏中。
然后,同样使用循环语句对敌机数组进行初始化。将每一个敌机的 live 属性设置为 false,表示当前该敌机并不存在于游戏中,并调用前面提到的 enemyHP 函数随机生成该敌机的类型和属性信息。该函数会根据一定的概率随机生成大型敌机或小型敌机。
/* IMAGE是easyX 的图像加载函数,这里定义了一个图像指针变量bg */
IMAGE bg; //背景图8
IMAGE t_player; //玩家图
IMAGE t_bullet; //子弹图
IMAGE t_enemy1; //敌机图1
IMAGE t_enemy2; //敌机图2
void loadIMAGE()
{
/*
loadimage(图片指针变量,图片名称地址)
将图片的地址赋值给指针,通过指针就可以直接调用图片了
*/
//ps:曾经想使用绝对地址
loadimage(&bg, _T("./images/background.png")); //加载背景图
loadimage(&t_player, _T("./images/me.png")); //加载玩家飞机图
loadimage(&t_bullet, _T("./images/bullet.png")); //加载子弹图
loadimage(&t_enemy1, _T("./images/enemy1.png")); //加载敌方飞机图1
loadimage(&t_enemy2, _T("./images/enemy2.png")); //加载敌方飞机图2
}
loadimage用于加载图片资源,将图片地址赋值给指针,通过指针就可以直接调用图片了。
void gamedraw() //游戏画布处理模块化
{
loadIMAGE(); //要先调用图像加载函数,不然只定义不在函数中调用也发挥不了任何作用
putimage(0, 0, &bg);
putimage(player.x, player.y, &t_player, SRCINVERT);
for (int i = 0; i < bullet_num; i++)
{
if (bullet[i].live)
{
putimage(bullet[i].x, bullet[i].y, &t_bullet, SRCINVERT);
}
}
for (int i = 0; i < enemy_num; i++)
{
if (enemy[i].type == BIG)
{
putimage(enemy[i].x, enemy[i].y, &t_enemy2, SRCINVERT);
}
else putimage(enemy[i].x, enemy[i].y, &t_enemy1, SRCINVERT);
}
/*
putimage(int x, int y, 图片地址指针)为图像输出函数格式
putimage()函数的作用是打印输出图片的函数
参数1→x为图片起始的横坐标
参数2→y为图片起始的纵坐标
参数3→指针地址对应所需的特定图片
*/
}
gamedraw函数用于游戏画布处理模块化。负责将图像贴到游戏窗口上。
首先调用 loadIMAGE 函数加载游戏所需的各种资源。然后使用 putimage 函数将图像贴到游戏窗口上第一个参数和第二个参数分别代表图像的左上角在窗口中的位置坐标,第三个参数则是对应要贴的图像的指针变量。
bool timer(int ms, int id)
{
static DWORD t[20];
if (clock() - t[id] > ms)
{
t[id] = clock(); //#include
return true;
}
return false;
} //计时器
timer函数用于控制游戏的帧率和动画效果。该函数接受两个参数,分别为计时器时间间隔和计时器 ID。
计时器使用了数组 t 来存储每个计时器的最近触发时间,其中数组下标为计时器 ID。在函数中,先计算当前时间与上次触发时间的时间差,如果大于指定的时间间隔,则将该计时器的最近触发时间更新为当前时间,并返回 true;否则返回 false。
void bullet_creat() //子弹创建模块
{
for (int i = 0; i < bullet_num; i++)
{
if (bullet[i].live == false)
{
bullet[i].x = player.x + 50;
bullet[i].y = player.y;
bullet[i].live = true;
break;
}
}
}
bullet_creat函数主要用于在游戏中创建新的子弹。该函数使用了一个循环遍历子弹数组中的所有元素,检查是否存在空闲的子弹。如果存在,将新的子弹对象的位置设置为玩家角色的位置加上50,子弹对象的状态设置为“存活”,并退出循环。
void enemy_creat()
{
for (int i = 0; i < enemy_num; i++)
{
if (!enemy[i].live)
{
enemy[i].live = true;
enemy[i].x = rand() % (bgwidth - 60);
enemy[i].y = -600;
enemyHP(i);
break;
}
}
}
enemy_creat函数主要用于在游戏中创建敌人,前面和子弹创建函数一样都是用循环遍历数组中的每个元素,检查是否存在没有生成的敌机。如果存在,则将新的敌机对象的位置设置为随机生成的水平位置和初始纵向位置,敌机对象的状态设置为“true”,并调用 enemyHP函数来初始化敌机对象的生命值。最后输出敌机对象的位置信息和状态信息,并退出循环。
void move_b(int speed)
{
if (GetAsyncKeyState(VK_SPACE) && timer(300, 1))
{
bullet_creat();
}
for (int i = 0; i < bullet_num; i++)
{
if (bullet[i].live)
{
bullet[i].y -= speed;
if (bullet[i].y < 0)
{
bullet[i].live = false;
}
}
}
}
move_b函数主要作用是子弹的移动。在函数中,首先检查玩家是否按下了空格键,并判断距离上一次发射子弹的时间是否超过 300 毫秒,如果满足条件,则调用 bullet_creat 函数创建新的子弹对象,使用一个循环遍历子弹数组中的所有存活子弹对象。对于每个存活的子弹对象,将其纵向坐标向上移动一定的距离并检查其是否超出了屏幕顶部。如果超出,则将其状态设置为“死亡”。
void move_e(int speed)
{
for (int i = 0; i < enemy_num; i++)
{
if (enemy[i].live)
{
enemy[i].y += speed;
if (enemy[i].y > bgheight)
{
enemy[i].live = false;
}
}
}
}
move_e函数的作用是敌机移动,与子弹移动基本函数基本一致,没有子弹移动函数中的空格键和时间检查,而且是超出屏幕底部敌机设置。
void move_p(int speed) /* 控制玩家移动——ps:speed为控制移动速度的变量 */
{ /*
_kbhit()函数的作用作用是判断是否有敲击键盘按键的行为
若有此行为则返回值“真”
若无此行为则返回值“假”
*/
#if 0 if(_kbhit())
{
char key = _getch();
switch (key)
{
case 'w':
case 'W':
player.y -= speed;
break;
case 's':
case 'S':
player.y += speed;
break;
case 'a':
case 'A':
player.x -= speed;
break;
case 'd':
case 'D':
player.x += speed;
break;
}
}
#elif 1
if (GetAsyncKeyState(VK_UP) || GetAsyncKeyState('W'))
{
if (player.y > 0) player.y -= speed;
}
if (GetAsyncKeyState(VK_DOWN) || GetAsyncKeyState('S'))
{
if (player.y < bgheight - 100) player.y += speed;
}
if (GetAsyncKeyState(VK_LEFT) || GetAsyncKeyState('A'))
{
if (player.x > -50) player.x -= speed;
}
if (GetAsyncKeyState(VK_RIGHT) || GetAsyncKeyState('D'))
{
if (player.x < bgwidth - 50) player.x += speed;
}
#endif
}
前半部分和后半部分都是运动函数,但是后半部分的移动效果更佳丝滑,所以我们注释掉了前半部分,只保留了第二版的移动函数,同时只对第二版的函数进行讲解:
使用 GetAsyncKeyState函数来检测特定的键盘按键,包括上下左右箭头和 W/A/S/D 键,并执行相应的移动操作。该部分代码使用了条件语句和逻辑运算符来限制玩家在屏幕边缘或其他限制区域内移动,防止玩家角色越过游戏界面的范围。
void hit()
{
for (int i = 0; i < enemy_num; i++)
{
if (!enemy[i].live)
continue;
for (int j = 0; j < bullet_num; j++)
{
if (!bullet[j].live)
continue;
if (bullet[j].x > enemy[i].x &&
bullet[j].x < enemy[i].x + enemy[i].width &&
bullet[j].y > enemy[i].y &&
bullet[j].y < enemy[i].y + enemy[i].height)
{
bullet[j].live = false;
enemy[i].hp--;
}
}
if (enemy[i].hp <= 0) enemy[i].live = false;
}
for (int i = 0; i < enemy_num; i++)
{
if (player.x + player.width >= enemy[i].x &&
player.x <= enemy[i].x + enemy[i].width &&
player.y <= enemy[i].y + enemy[i].height &&
player.y + player.height >= enemy[i].y)
{
player.live = false;
}
}
if (player.live == false)
{
exit(1);
}
}
hit函数是判断子弹与敌机,敌机与玩家角色之间的撞击,第一部分通过两个嵌套循环遍历所有敌人和子弹,对于每一个还存活的敌人,判断每个子弹是否与该敌人发生碰撞,即子弹的位置是否在敌人的位置范围内。如果某个子弹击中了敌人,则将该子弹从屏幕上删除,并减少敌人的血量。如果某个敌人的血量被减少到 0 或以下,则将其从屏幕上删除。
第二部分首先遍历所有敌人,判断玩家是否与每个敌人发生了碰撞。如果玩家碰到了敌人,则将玩家的生命值设为 false,表示玩家已死亡,则通过 exit函数退出程序,游戏结束。
int main()
{
/*控制台会自动创建,但是游戏的画面窗口范围不会自动产生,需要我们手动创建*/
initgraph(bgwidth, bgheight);
/* initgraph(int width,int height,int flag=0)为函数的具体格式
initgraph()函数的具体作用是初始化绘图窗口
参数1→width为窗口宽度
参数2→height为窗口高度
参数3→flag为窗口样式,一般为默认值不去更改
*/
game_csh();
while (1)
{
gamedraw();
move_p(2);
move_b(2);
if (timer(500, 0)) enemy_creat();
if (timer(5, 2)) move_e(1);
hit();
}
return 0;
}
这是主程序,用于整合函数,实现游戏的运行。
首先通过 initgraph函数初始化绘图窗口,并调用 game_csh 函数进行游戏初始化。之后,通过while(1)函数无限循环,不断调用 gamedraw、move_p、move_b、enemy_creat、move_e、hit 等函数,实现游戏的运行。最后,通过 EndBatchDraw函数结束绘图,退出游戏循环并返回值 0,完成游戏的运行。
如果这篇文章对你有所帮助的话,麻烦点个赞叭(◍•͈⌔•͈◍)
#include
#include
#include
#include
#include
//先安装图形库easyX,就可以使用该头文件了
//该头文件可以对图形进行直接引用快速处理
enum wh //enum枚举类型,程序简洁,方便后续对数据进行修改
{
bgwidth = 480,
bgheight = 700,
bullet_num = 15,
enemy_num = 10,
BIG,
SMALL
};
struct plane
{
int x; //玩家飞机的横坐标
int y; //玩家飞机的纵坐标
int width; //飞机的宽度
int height; //飞机的高度
int hp; //敌机血量
int type; //敌机类型
bool live; //布尔类型变量非0即1,用于判断飞机是否仍然存活
}player, bullet[bullet_num], enemy[enemy_num];
//每个子弹都有自己的状态,所以这里要以数组的形式定义
void enemyHP(int i)
{
int flag = rand() % 10;
if (flag >= 0 && flag <= 1)
{
enemy[i].type = BIG;
enemy[i].hp = 3;
enemy[i].width = 169;
enemy[i].height = 230;
}
else
{
enemy[i].type = SMALL;
enemy[i].hp = 1;
enemy[i].width = 57;
enemy[i].height = 43;
}
}
void game_csh()
{
//初始化飞机起始位置和存活状态
player.x = bgwidth / 2 - 50;
player.y = bgheight - 120;
player.live = true;
player.width = 114;
player.height = 85;
//当然子弹也要初始化(数组以数组的方式初始化)
for (int i = 0; i < bullet_num; i++)
{
bullet[i].x = 0;
bullet[i].y = 0;
bullet[i].live = false;
}
//初始化敌机(数组)
for (int i = 0; i < enemy_num; i++)
{
enemy[i].live = false;
enemyHP(i);
}
}
/* IMAGE是easyX 的图像加载函数,这里定义了一个图像指针变量bg */
IMAGE bg; //背景图8
IMAGE t_player; //玩家图
IMAGE t_bullet; //子弹图
IMAGE t_enemy1; //敌机图1
IMAGE t_enemy2; //敌机图2
void loadIMAGE()
{
/*
loadimage(图片指针变量,图片名称地址)
将图片的地址赋值给指针,通过指针就可以直接调用图片了
*/
//ps:曾经想使用绝对地址
loadimage(&bg, _T("./images/background.png")); //加载背景图
loadimage(&t_player, _T("./images/me.png")); //加载玩家飞机图
loadimage(&t_bullet, _T("./images/bullet.png")); //加载子弹图
loadimage(&t_enemy1, _T("./images/enemy1.png")); //加载敌方飞机图1
loadimage(&t_enemy2, _T("./images/enemy2.png")); //加载敌方飞机图2
}
void gamedraw() //游戏画布处理模块化
{
loadIMAGE(); //要先调用图像加载函数,不然只定义不在函数中调用也发挥不了任何作用
putimage(0, 0, &bg);
putimage(player.x, player.y, &t_player, SRCINVERT);
for (int i = 0; i < bullet_num; i++)
{
if (bullet[i].live)
{
putimage(bullet[i].x, bullet[i].y, &t_bullet, SRCINVERT);
}
}
for (int i = 0; i < enemy_num; i++)
{
if (enemy[i].type == BIG)
{
putimage(enemy[i].x, enemy[i].y, &t_enemy2, SRCINVERT);
}
else putimage(enemy[i].x, enemy[i].y, &t_enemy1, SRCINVERT);
}
/*
putimage(int x, int y, 图片地址指针)为图像输出函数格式
putimage()函数的作用是打印输出图片的函数
参数1→x为图片起始的横坐标
参数2→y为图片起始的纵坐标
参数3→指针地址对应所需的特定图片
*/
}
bool timer(int ms, int id)
{
static DWORD t[20];
if (clock() - t[id] > ms)
{
t[id] = clock(); //#include
return true;
}
return false;
} //计时器
void bullet_creat() //子弹创建模块
{
for (int i = 0; i < bullet_num; i++)
{
if (bullet[i].live == false)
{
bullet[i].x = player.x + 50;
bullet[i].y = player.y;
bullet[i].live = true;
break;
}
}
}
void enemy_creat()
{
for (int i = 0; i < enemy_num; i++)
{
if (!enemy[i].live)
{
enemy[i].live = true;
enemy[i].x = rand() % (bgwidth - 60);
enemy[i].y = -600;
enemyHP(i);
break;
}
}
}
void move_b(int speed)
{
if (GetAsyncKeyState(VK_SPACE) && timer(300, 1))
{
bullet_creat();
}
for (int i = 0; i < bullet_num; i++)
{
if (bullet[i].live)
{
bullet[i].y -= speed;
if (bullet[i].y < 0)
{
bullet[i].live = false;
}
}
}
}
void move_e(int speed)
{
for (int i = 0; i < enemy_num; i++)
{
if (enemy[i].live)
{
enemy[i].y += speed;
if (enemy[i].y > bgheight)
{
enemy[i].live = false;
}
}
}
}
void move_p(int speed) /* 控制玩家移动——ps:speed为控制移动速度的变量 */
{ /*
_kbhit()函数的作用作用是判断是否有敲击键盘按键的行为
若有此行为则返回值“真”
若无此行为则返回值“假”
*/
#if 0 if(_kbhit())
{
char key = _getch();
switch (key)
{
case 'w':
case 'W':
player.y -= speed;
break;
case 's':
case 'S':
player.y += speed;
break;
case 'a':
case 'A':
player.x -= speed;
break;
case 'd':
case 'D':
player.x += speed;
break;
}
}
#elif 1
if (GetAsyncKeyState(VK_UP) || GetAsyncKeyState('W'))
{
if (player.y > 0) player.y -= speed;
}
if (GetAsyncKeyState(VK_DOWN) || GetAsyncKeyState('S'))
{
if (player.y < bgheight - 100) player.y += speed;
}
if (GetAsyncKeyState(VK_LEFT) || GetAsyncKeyState('A'))
{
if (player.x > -50) player.x -= speed;
}
if (GetAsyncKeyState(VK_RIGHT) || GetAsyncKeyState('D'))
{
if (player.x < bgwidth - 50) player.x += speed;
}
#endif
}
void hit()
{
for (int i = 0; i < enemy_num; i++)
{
if (!enemy[i].live)
continue;
for (int j = 0; j < bullet_num; j++)
{
if (!bullet[j].live)
continue;
if (bullet[j].x > enemy[i].x &&
bullet[j].x < enemy[i].x + enemy[i].width &&
bullet[j].y > enemy[i].y &&
bullet[j].y < enemy[i].y + enemy[i].height)
{
bullet[j].live = false;
enemy[i].hp--;
}
}
if (enemy[i].hp <= 0) enemy[i].live = false;
}
for (int i = 0; i < enemy_num; i++)
{
if (player.x + player.width >= enemy[i].x &&
player.x <= enemy[i].x + enemy[i].width &&
player.y <= enemy[i].y + enemy[i].height &&
player.y + player.height >= enemy[i].y)
{
player.live = false;
}
}
if (player.live == false)
{
exit(1);
}
}
int main()
{
/*控制台会自动创建,但是游戏的画面窗口范围不会自动产生,需要我们手动创建*/
initgraph(bgwidth, bgheight);
/* initgraph(int width,int height,int flag=0)为函数的具体格式
initgraph()函数的具体作用是初始化绘图窗口
参数1→width为窗口宽度
参数2→height为窗口高度
参数3→flag为窗口样式,一般为默认值不去更改
*/
game_csh();
while (1)
{
gamedraw();
move_p(2);
move_b(2);
if (timer(500, 0)) enemy_creat();
if (timer(5, 2)) move_e(1);
hit();
}
return 0;
}
如果这篇文章对你有所帮助的话,麻烦点个赞叭(◍•͈⌔•͈◍)