目录
引言
飞机大战是一款经典的射击游戏,玩家控制一架飞机在屏幕上移动并射击敌机。本文将详细介绍如何使用C语言和EasyX图形库开发一个完整的飞机大战游戏。这个项目不仅适合C语言初学者学习游戏开发的基本概念,也展示了如何将编程基础知识应用到实际项目中。
首先看一下运行效果:
开发环境与工具准备
1. 开发环境配置
开发飞机大战游戏需要以下环境配置:
- Visual Studio:推荐使用VS2019或更高版本,它提供了强大的代码编辑、调试和项目管理功能。
- EasyX图形库:这是一个简单易用的Windows图形编程库,封装了常用图形操作函数,特别适合初学者。
- Windows SDK:安装Visual Studio时需要勾选此项。
安装EasyX图形库非常简单,只需访问EasyX官网下载适配VS版本的安装包,然后按照向导完成安装即可。
2. 资源文件准备
游戏开发需要准备以下素材文件(网上图片随便一搜就有),存放在项目目录的res文件夹中:
plane.png
- 玩家飞机图片(50x50像素)enemy.png
- 敌机图片(50x50像素)bullet.png
- 子弹图片(10x20像素)bg.jpg
- 游戏背景图片(600x700像素)boom.wav
- 爆炸音效文件
游戏设计与架构
1. 游戏核心数据结构
游戏主要使用以下数据结构:
typedef struct pos {
int x;
int y;
}POS; // 坐标类型
typedef struct plane {
POS planePos; // 飞机坐标
POS planeBullets[BULLET_NUM]; // 子弹数组
int bulletLen; // 当前子弹数量
int bulletSpeed; // 子弹移动速度
}PLANE; // 飞机类型
POS
结构体用于表示游戏对象的二维坐标,PLANE
结构体则包含了飞机的位置、子弹数组及相关属性。
2. 游戏全局变量
游戏使用以下全局变量管理状态:
PLANE myPlane; // 玩家飞机
PLANE enemyPlanes[ENEMY_NUM]; // 敌机数组
int enemyPlaneLen; // 当前敌机数量
time_t startTime, endTime; // 用于控制敌机生成时间
IMAGE img[3]; // 存储游戏图片资源
int score = 0; // 游戏得分
这些变量分别管理玩家飞机、敌机、时间控制和游戏得分等核心游戏状态。
游戏核心功能实现
1. 游戏初始化
initGame()
函数负责初始化游戏状态:
void initGame() {
initgraph(SCREEN_WIDTH, SCREEN_HEIGTH); // 初始化图形窗口
score = 0; // 重置得分
srand((unsigned)time(NULL)); // 初始化随机数种子
// 初始化玩家飞机
myPlane.bulletLen = 0;
myPlane.bulletSpeed = 3;
myPlane.planePos = {SCREEN_WIDTH/2 - PLANE_SIZE/2, SCREEN_HEIGTH - PLANE_SIZE};
enemyPlaneLen = 0; // 重置敌机数量
startTime = time(NULL); // 记录开始时间
}
该函数设置了游戏窗口、随机数种子、玩家飞机初始位置和游戏计时器等。
2. 游戏主循环
游戏采用经典的游戏循环结构:
while(1) {
drawGame(); // 渲染游戏画面
updateGame(); // 更新游戏状态
Sleep(1000/60); // 控制帧率约60FPS
}
这个循环确保游戏以大约60帧每秒的速度运行,每次循环都先绘制画面再更新游戏状态。
3. 游戏渲染
drawGame()
函数负责绘制游戏画面:
void drawGame() {
BeginBatchDraw(); // 开始批量绘制
// 绘制背景
putimage(0, 0, &img[0]);
// 绘制玩家飞机
putimage(myPlane.planePos.x - PLANE_SIZE/2,
myPlane.planePos.y - PLANE_SIZE/2,
&img[2], SRCAND);
// 绘制敌机
for(int i = 0; i < enemyPlaneLen; i++) {
putimage(enemyPlanes[i].planePos.x - PLANE_SIZE/2,
enemyPlanes[i].planePos.y - PLANE_SIZE/2,
&img[1], SRCAND);
}
// 绘制子弹
for(int i = 0; i < myPlane.bulletLen; i++) {
solidcircle(myPlane.planeBullets[i].x,
myPlane.planeBullets[i].y,
PLANE_SIZE/4);
}
// 绘制分数
RECT rect = {0, PLANE_SIZE, SCREEN_WIDTH, SCREEN_HEIGTH};
setbkmode(TRANSPARENT);
char str[30] = {0};
sprintf(str, "score:%d", score);
drawtext(str, &rect, DT_TOP | DT_CENTER);
EndBatchDraw(); // 结束批量绘制
}
该函数使用EasyX的批量绘制功能高效地绘制游戏画面,包括背景、玩家飞机、敌机、子弹和分数显示。
4. 游戏状态更新
updateGame()
函数处理游戏逻辑更新:
void updateGame() {
// 处理玩家输入
if(GetAsyncKeyState('W') & 0x8000) myPlane.planePos.y -= 4;
if(GetAsyncKeyState('S') & 0x8000) myPlane.planePos.y += 4;
if(GetAsyncKeyState('A') & 0x8000) myPlane.planePos.x -= 4;
if(GetAsyncKeyState('D') & 0x8000) myPlane.planePos.x += 4;
// 发射子弹
if(_kbhit()) {
if(_getch() == ' ') {
if(myPlane.bulletLen < BULLET_NUM) {
PlaySound("img/bullet.wav", NULL, SND_FILENAME | SND_ASYNC | SND_NOWAIT);
myPlane.planeBullets[myPlane.bulletLen] = myPlane.planePos;
myPlane.bulletLen++;
}
}
}
// 更新敌机位置
for(int i = 0; i < enemyPlaneLen; i++) {
enemyPlanes[i].planePos.y += 2;
}
// 更新子弹位置
for(int i = 0; i < myPlane.bulletLen; i++) {
myPlane.planeBullets[i].y -= myPlane.bulletSpeed;
}
// 调用其他更新函数
initEnemyPlane();
destroyEnemyPlane();
destroyBullet();
}
该函数处理玩家输入、更新游戏对象位置,并调用其他辅助函数完成敌机生成和碰撞检测等逻辑。
关键游戏机制实现
1. 敌机生成系统
initEnemyPlane()
函数控制敌机的生成:
void initEnemyPlane() {
endTime = time(NULL);
double elapsedTime = difftime(endTime, startTime);
if(elapsedTime >= ENEMY_SPEED) {
if(enemyPlaneLen < ENEMY_NUM) {
int x = (rand() % (SCREEN_WIDTH - 2*PLANE_SIZE)) + PLANE_SIZE;
int y = -PLANE_SIZE;
enemyPlanes[enemyPlaneLen].planePos.x = x;
enemyPlanes[enemyPlaneLen].planePos.y = y;
enemyPlaneLen++;
}
startTime = endTime;
}
}
该函数每隔ENEMY_SPEED
秒在屏幕顶部随机位置生成一架新敌机,确保敌机数量不超过最大限制。
2. 碰撞检测系统
游戏使用简单的矩形碰撞检测:
int areInierSecting(POS c1, POS c2, int radius) {
return abs(c1.x - c2.x) <= radius && abs(c1.y - c2.y) <= radius;
}
该函数检查两个坐标点是否在指定半径范围内相交,用于检测子弹与敌机、玩家与敌机的碰撞。
3. 敌机销毁逻辑
destroyEnemyPlane()
处理敌机销毁:
void destroyEnemyPlane() {
for(int i = 0; i < enemyPlaneLen; i++) {
// 检测玩家与敌机碰撞
if(areInierSecting(myPlane.planePos, enemyPlanes[i].planePos, PLANE_SIZE)) {
if(IDYES == MessageBox(GetHWnd(), "游戏结束,是否重新开始?", "提示", MB_YESNO)) {
initGame();
} else {
exit(0);
}
}
// 敌机飞出屏幕
if(enemyPlanes[i].planePos.y > SCREEN_HEIGTH) {
for(int j = i; j < enemyPlaneLen; j++) {
enemyPlanes[j] = enemyPlanes[j+1];
}
enemyPlaneLen--;
i--;
}
}
}
该函数检测玩家与敌机的碰撞(游戏结束)以及敌机飞出屏幕的情况(移除敌机)。
4. 子弹销毁逻辑
destroyBullet()
处理子弹销毁:
void destroyBullet() {
for(int i = 0; i < myPlane.bulletLen; i++) {
// 子弹与敌机碰撞
for(int j = 0; j < enemyPlaneLen; j++) {
if(areInierSecting(myPlane.planeBullets[i], enemyPlanes[j].planePos,
PLANE_SIZE/4 + PLANE_SIZE/2)) {
// 移除子弹和敌机
for(int x = i; x < myPlane.bulletLen; x++) {
myPlane.planeBullets[x] = myPlane.planeBullets[x+1];
}
for(int x = j; x < enemyPlaneLen; x++) {
enemyPlanes[x] = enemyPlanes[x+1];
}
enemyPlaneLen--;
myPlane.bulletLen--;
j--;
score += 100;
break;
}
}
// 子弹飞出屏幕
if(myPlane.planeBullets[i].y < 0) {
for(int x = i; x < myPlane.bulletLen; x++) {
myPlane.planeBullets[x] = myPlane.planeBullets[x+1];
}
myPlane.bulletLen--;
i--;
}
}
}
该函数处理子弹与敌机的碰撞(得分并移除双方)以及子弹飞出屏幕的情况(移除子弹)。
游戏优化与扩展
1. 性能优化技巧
- 批量绘制:使用
BeginBatchDraw()
和EndBatchDraw()
减少屏幕刷新次数。 - 帧率控制:通过
Sleep(1000/60)
将游戏帧率控制在约60FPS。 - 对象池技术:预分配游戏对象(如子弹、敌机)避免频繁内存分配。
2. 游戏功能扩展
这个基础版本可以进一步扩展:
- 多种敌机类型:添加不同大小、生命值和移动模式的敌机。
- 关卡系统:设计多个关卡,逐渐增加难度。
- 道具系统:实现火力增强、生命恢复等道具。
- 背景音乐:添加游戏背景音乐和更多音效。
- 高分记录:保存玩家最高分数。
- 难度递增:随着游戏进行逐渐提高敌机速度和生成频率。
开发经验与学习建议
1. 开发经验总结
- 模块化设计:将游戏功能划分为初始化、渲染、更新等模块,便于维护。
- 逐步实现:先实现核心功能(移动、射击),再添加额外特性。
- 调试技巧:使用Visual Studio的调试工具检查游戏状态和变量值。
2. 学习建议
- 掌握C语言基础:熟悉指针、结构体、数组等核心概念。
- 学习EasyX图形库:从简单图形绘制开始,逐步学习动画和交互。
- 分析开源项目:研究其他游戏项目的代码结构和设计思路。
- 动手实践:通过修改和扩展现有项目来巩固学习成果。
结语
通过这个飞机大战游戏项目,我们展示了如何使用C语言和EasyX图形库开发一个完整的游戏。从游戏设计、数据结构到核心功能实现,这个项目涵盖了游戏开发的关键概念和技术。
这个项目不仅适合C语言学习者巩固基础知识,也为有志于游戏开发的初学者提供了一个良好的起点。通过扩展和完善这个基础框架,你可以进一步探索游戏开发的更多可能性。
希望本文能帮助你理解游戏开发的基本原理,并激发你创造更多有趣项目的热情!完整的源代码已在文中展示,你可以直接运行或基于此进行二次开发。