C语言基于EasyX图形界面的飞机大战项目
这是很早之前用C语言写的基于EasyX图形界面的飞机大战项目,当时只是觉得这个项目挺有意思的。但是一直都没能去整理这个项目的代码,今天趁着空闲时间,稍微整理一下这个代码的代码:
/*
文件名称:planegame
项目描述:实现飞机大战游戏的效果
*/
#include <stdio.h>
#include <conio.h> // 检测键盘操作有关的头文件
#include <graphics.h> // EasyX第三方图形界面库
#include <stdlib.h> // C语言自己的标准库,动态内存分配的头文件。
#include <time.h> // 种下随机种子的相关头文件
#include <mmsystem.h> // 添加音乐库
#pragma comment(lib,"winmm.lib")
//#pragma warning(disable:4996);
//宏定义区域
#define WIN_WIDTH 600 // 飞机大战界面的宽度为600
#define WIN_HEIGHT 800 // 飞机大战界面的高度为800
//定义结构体类型,注意类型不需要分配空间的。
typedef struct Node // 结构体类型适用于自己的飞机、敌机和子弹的
{
int x; // 飞机(自己和敌人)和子弹的坐标
int y;
struct Node* pNext;
}NODE; // NODE是结构体类型,是一种特殊的数据类型
typedef NODE* Point_Node; // 给指针NODE*起一个别名Point_Node
/*
1、自己的飞机只有一个
2、敌机有很多个(每个敌机都需要一块内存,很多敌机就需要一个链表来存储啦)
3、子弹也有很多个(和敌机一样,很多子弹也需要一个链表来存储)
*/
Point_Node pBullet = NULL; // 定义子弹链表的头指针 -->子弹有很多个,比较复杂!
Point_Node pEnemyPlane = NULL; // 定义敌机链表的头指针 -->敌机有很多,比较复杂!
Point_Node pMinePlane = NULL; // 定义自己飞机链表的头指针-->自己的飞机只有一个,简单!
//链表的操作(增删查改)+项目 -->理论结合实际项目
/*
1、敌机有很多个(敌机的大小为宽50 高30),敌机初始坐标:x随机,y = 0 -->敌机从上往下移动
2、自己的飞机只有一个(自己飞机宽80 高60),初始坐标是x=260,y=740 -->由用户操作移动
3、子弹很多个,先考虑简单的情况,只是由自己的飞机发射出来 -->子弹从下到上移动
*/
//创建链表
void CreateList() // 从内存中拉一个个内存出来
{
// 创建敌机的链表
pEnemyPlane = (Point_Node)malloc(sizeof(NODE)); // 在内存中动态开辟能够存储结构体的空间(以字节为单位)
// malloc函数返回值是void*地址,需要强制转换
pEnemyPlane->pNext = NULL; // 防止野指针
// 创建自己飞机的链表,并初始化自己飞机的xy坐标
pMinePlane = (Point_Node)malloc(sizeof(NODE));
// 自己飞机宽80,高60,初始坐标为x=260,y=740
pMinePlane->x = 260;
pMinePlane->y = 740;
pMinePlane->pNext = NULL;
// 创建存储子弹的链表,开始子弹的坐标不用管。
pBullet = (Point_Node)malloc(sizeof(NODE));
pBullet->pNext = NULL;
}
// 增加一个节点
void AddNode(int flag) // flag==0表示增肌子弹 flag==1表示增加敌机
{
// 增加一个敌机,具体可以画图来理解
/*Point_Node pNew = (Point_Node)malloc(sizeof(NODE));
pNew->pNext = NULL;
pEnemyPlane->pNext = pNew;*/
//不带头节点的头插法(很经典的数据结构算法)
Point_Node pNew;
//开辟内存空间
pNew = (Point_Node)malloc(sizeof(NODE)); // 从内存来看是增加一个节点,实际项目中是增加一个敌机。
if (flag == 1) // 增加的是敌机
{
/*填充数据*/
pNew->x = rand() % (WIN_WIDTH - 50); // 减去敌机的宽度,防止越出窗口边界
pNew->y = 0;
/*连接链表,这里就是头插法的关键啦!*/
pNew->pNext = pEnemyPlane->pNext;
pEnemyPlane->pNext = pNew;
}
else if (flag == 0) // 子弹
{
/*填充数据*/
//设置子弹的宽度为10,高度为15.
pNew->x = pMinePlane->x + 35;
pNew->y = pMinePlane->y - 20; // 子弹在自己飞机正前方5出现
/*连接链表*/
pNew->pNext = pBullet->pNext;
pBullet->pNext = pNew;
}
}
// 键盘按键检测函数
void IsPressKey()
{
if (kbhit()) // 如果有按键按下
{
char key;
key = getch(); // 获取键盘信息(按下的是哪个键)
switch (key)
{
case 72: // 上方向键
pMinePlane->y -= 8;
// printf("上方向键的ASCII是%d", key);
// Sleep(1500);
break;
case 80: // 下方向键
pMinePlane->y += 8;
break;
case 75: // 左方向键
pMinePlane->x -= 6;
break;
case 77: // 右方向键
pMinePlane->x += 6;
break;
default:
break;
}
}
}
// 碰撞检测,子弹发射出去干掉敌机
void Shoot()
{
// 定义敌机的两个临时指针,为了删除被子弹打中的敌机。(实际上就是释放相应的内存)
Point_Node pDj = pEnemyPlane->pNext; // pDj开始指向敌机链表中的第二个敌机
Point_Node pDjPre = pEnemyPlane; // pDjPre开始指向第一敌机
// 定义子弹的两个临时指针
Point_Node pZd = pBullet->pNext; // 同上面的敌机
Point_Node pZdPre = pBullet;
// 遍历所有的敌机和所有子弹,看看两者是否有碰撞?
while (pDj != NULL) // 遍历所有的敌机
{
pZd = pBullet->pNext; // 初始化子弹的两个临时指针
pZdPre = pBullet;
while (pZd != NULL) // 遍历所有的子弹
{
// 判断敌机和子弹是否碰撞到(需要画出二维图形来理解)
if (pZd->x >= (pDj->x-10) && pZd->x <= (pDj->x+50) &&
pZd->y >= (pDj->y-15) && pZd->y <= (pDj->y+30)
)
{
// 碰撞条件满足的话干掉敌机,同时子弹也消失了。
// 干掉敌机(其实是干掉敌机链表中的第二个节点,第一个节点不动,碰撞后依次干掉后面的敌机)
pDjPre->pNext = pDj->pNext;
free(pDj);
// 干掉子弹(注释同敌机)
pZdPre->pNext = pZd->pNext;
free(pZd);
//mciSendString(TEXT("open ./boom2.mp3 alias music"), 0, 0, 0);
// mciSendString(L"play music repeat", 0, 0, 0); //播放音乐
// 重新寻找敌机
pDj = pEnemyPlane->pNext;
pDjPre = pEnemyPlane;
break; // 一个子弹干掉一个敌机以后,跳出循环,等待下一次的碰撞检测。
}
else // 这颗子弹没击中敌机,就换别的子弹来碰撞敌机
{
// 寻找下一颗子弹,继续和敌机进行碰撞检测。
pZd = pZd->pNext;
pZdPre = pZdPre->pNext;
}
}
// 子弹用尽或者一个敌机被干掉以后,下一个敌机继续来和子弹进行碰撞检测。
pDj = pDj->pNext;
pDjPre = pDjPre->pNext;
}
}
int main()
{
initgraph(WIN_WIDTH, WIN_HEIGHT,SHOWCONSOLE); // 创建600*800的窗口同时显示控制台窗口
//PlaySound(L"TheMass.mp3",NULL,SND_FILENAME|SND_LOOP|SND_ASYNC);
//窗口加载瞬间就播放音乐
//mciSendString(TEXT("open ./shoot.mp3 alias music2"), 0, 0, 0);
//mciSendString(L"play music2", 0, 0, 0);
IMAGE planeimg; // 定义保存敌机图片的对象
IMAGE Mineplaneimg; // 定义保存自己飞机图片的对象
Point_Node p; // 定义指针变量p
DWORD t1, t2; // 控制敌机的速度
DWORD tt1,tt2; // 控制子弹的速度
//加载敌机图片,敌机大小为宽50,高30。
loadimage(&planeimg,L"./plane.jpg",50,30);
//加载自己飞机图片,自己的飞机大小是宽80,高60。
loadimage(&Mineplaneimg, L"./MinePlane.jpg", 80, 60);
//种下随机数种子
srand((unsigned int)time(NULL));// 种下产生随机数的种子
//创建链表的第一个节点
CreateList();
//在内存中增加4个节点,就是在实际的项目中随机增加4个飞机
/* for (int i = 0; i < 4; i++)
{
AddNode();
}*/
t1 = GetTickCount(); // 给敌机一个初始时间
tt1 = GetTickCount(); // 给子弹一个初始时间
while (1)
{
t2 = GetTickCount(); // 敌机间隔一段时间
if (t2 - t1 >= 1000) // 间隔1000ms增加一个敌机
{
AddNode(1); // 1000ms产生一个敌机
t1 = t2; // 为了下次增加敌机做时间差的准备
}
tt2 = GetTickCount(); // 子弹间隔一段时间
if (tt2 - tt1 >= 200) // 间隔300ms增加一个子弹
{
AddNode(0); // 300ms产生一个子弹
// mciSendString(TEXT("open ./shoot.mp3 alias music"), 0, 0, 0);//mci(media control interface) Send(发送) String(字符串)
// mciSendString(L"play music repeat", 0, 0, 0); //播放音乐
tt1 = tt2; // 为下次增加子弹做时间差的准备
}
//Sleep(5);
BeginBatchDraw(); //这个函数用于开始绘图,执行后,任何绘图操作都将暂时不输出到屏幕上面
//直到执行到FlushBatchDraw 或 EndBatchDraw才将之前的绘图输出。
cleardevice(); //这个函数用于清除屏幕内容。具体的,是用当前背景色清空屏幕,并将当前点移至 (0, 0)。
//在x=260,y=740位置显示自己的飞机
putimage(pMinePlane->x,pMinePlane->y,&Mineplaneimg);
//显示自己的子弹
p = pBullet->pNext;
while (p != NULL)
{
//在控制台打印数据
printf("x = %d y = %d\n", p->x, p->y);
/*在easyx图形界面中使用控制台数据,画一个矩形。*/
roundrect(p->x, p->y, p->x+10, p->y+15, 5, 5);
p->y--; // 子弹向上移动
p = p->pNext; // 继续访问子弹链表中下一个子弹
}
//显示敌机
p = pEnemyPlane->pNext;
while (p != NULL)
{
//在控制台打印数据
printf("x = %d y = %d\n", p->x, p->y);
/*在easyx图形界面中使用控制台数据,画一个矩形。*/
//roundrect(p->x, p->y, p->x+30, p->y+20, 5, 5);
/*在easyx图形界面中使用控制台的数据,输出飞机图片*/
putimage(p->x, p->y, &planeimg);
p->y++; // 飞机向下移动
//Sleep(100); // 加上这句话后飞机只剩下一个了
p = p->pNext; // 继续访问飞机链表中的下一个飞机
}
Sleep(5);
Shoot(); // 碰撞检测,子弹是否击中敌机
EndBatchDraw(); // 显示之前所有的绘图操作
IsPressKey(); // 键盘按键检测函数
}
getchar(); // 等待键盘输入字符,起到暂停窗口作用。
return 0;
}
实际运行起来的效果如下: