【C++】飞机大战项目记录

马肤
这是懒羊羊

【C++】飞机大战项目记录,在这里插入图片描述,词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,操作,进入,li,第1张

源代码与图片参考自《你好编程》的飞机大战项目,这里不进行展示。

本项目是仅供学习使用的项目


飞机大战项目记录

  • 飞机大战设计报告
    • 1 项目框架分析
      • 1.1 敌机设计:
      • 1.2 玩家飞机控制:
      • 1.3 子弹发射:
      • 1.4 游戏界面与互动:
      • 1.5 游戏逻辑:
      • 2 开始打造项目
        • 2.1 图片素材准备
        • 2.2 设计精灵对象
          • 位置坐标:
          • 大小宽度:
          • draw方法:
          • update方法:
          • 执行机制:
          • 代码
          • 2.3 设计英雄飞机
            • 结构体设计
            • 初始化
            • 绘制与更新
            • 资源回收
            • 关键技术点
            • 代码(只展示头文件)
            • 2.4 游戏场景设置
              • 游戏背景
              • 游戏循环函数
              • 代码
              • 2.5 设计子弹与敌机
                • 子弹设计模块
                  • 结构体设计
                  • 初始化
                  • 绘制与更新
                  • 资源回收
                  • 关键技术点
                  • 代码(只展示头文件)
                  • 敌机设计模块
                    • 结构体设计
                    • 初始化
                    • 绘制与更新
                    • 交互操作
                    • 资源回收
                    • 代码(只展示头文件)
                    • 敌机实例化
                    • 2.6 设计击毁与碰撞逻辑
                      • 子弹与敌机碰撞检测
                      • 英雄飞机与敌机碰撞检测
                      • 关键技术点
                      • 2.7 计分板
                      • 2.8 游戏菜单
                        • 结构体设计
                        • 功能方法
                        • 交互逻辑
                        • 关键技术点
                        • 2.9 音乐设计
                          • 背景音乐
                          • 击毁音效
                            • 结构体设计
                            • 功能方法
                            • 关键技术点
                            • 项目效果展示
                            • Thanks♪(・ω・)ノ谢谢阅读!!!
                            • 下一篇文章见!!!

                              飞机大战设计报告

                              源代码与图片参考自《你好编程》

                              1 项目框架分析

                              根据飞机大战的游戏特性,首先可以确定的是游戏的基本玩法和规则。对于本软件项目,游戏的核心机制是使用鼠标控制一架飞机在屏幕上移动,同时飞机会自动发射子弹来击败敌机。我们将通过Easyx来实现该项目!

                              1.1 敌机设计:

                              设计三种不同类型的敌机,每种敌机具有不同的生命值和外观。

                              小型敌机:生命值低,移动速度快,外观较小。

                              中型敌机:生命值和大小适中,速度适中。

                              大型敌机:生命值高,移动速度慢,外观较大。

                              1.2 玩家飞机控制:

                              使用鼠标控制飞机的上下左右移动,飞机的位置随鼠标位置变化。

                              飞机在屏幕中任意移动,给与玩家充足飞行体验,提高游戏沉浸感。

                              1.3 子弹发射:

                              飞机自动连续射击,子弹直线向上移动。

                              可以设计不同的子弹类型或升级系统,提高游戏的可玩性和策略性。

                              1.4 游戏界面与互动:

                              设计一个直观的用户界面,进入游戏可以见到排行榜与开始游戏。开始游戏后,屏幕显示当前得分、生命值。

                              敌机被击中或击毁时有相应的动画和音效,增强游戏体验。

                              1.5 游戏逻辑:

                              敌机从屏幕顶部随机位置出现,向下移动。

                              玩家需要避免敌机的攻击,同时尽可能多地击落敌机。

                              【C++】飞机大战项目记录,在这里插入图片描述,词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,操作,进入,li,第2张

                              2 开始打造项目

                              有了大致的游戏设计思路,现在我们可以来逐步实现飞机大战的各个模块。

                              2.1 图片素材准备

                              一个好的项目离不开美观的图案,所以这里我准备了一下图片(放在项目代码的路径下):

                              1. 子弹(对应图片和图片掩码)

                              2. 敌机有三种,都有对应正常飞行状态的图片,以及爆炸销毁的图片组,而且都有对应的掩码,保证图片的边框。

                              3. 英雄飞机正常飞行状态有两种,模拟飞行中喷射火焰前进,以及爆炸销毁的图片组。

                              4. 菜单图片与背景图片

                              【C++】飞机大战项目记录,在这里插入图片描述,词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,操作,进入,li,第3张

                              2.2 设计精灵对象

                              精灵对象是游戏开发中一个常见的概念,通常用于表示屏幕上的各种动态元素。飞机大战项目中,精灵对象可以被用来作为基类,敌机和飞机都会继承这个基类。以下是精灵对象的一些基本特征和功能:

                              位置坐标:

                              每个精灵对象都有自己的位置坐标,通常包括x和y坐标,用于确定对象在游戏界面上的位置。

                              大小宽度:

                              对象的大小通常由宽度和高度来定义,这决定了精灵在屏幕上的占用空间和碰撞检测的范围。

                              draw方法:

                              这个方法负责将精灵绘制到游戏窗口。通常,这包括调用图形库(如Pygame的blit方法)来在正确的位置和尺寸绘制精灵的图像。

                              update方法:

                              update方法用于更新精灵的状态。这可能包括移动位置、改变速度、检测碰撞、更新生命值等。这个方法每一帧都会被调用,以保持游戏逻辑的持续运行和响应。

                              执行机制:

                              在游戏的主循环中,每一帧都会对所有精灵对象执行draw和update方法。update方法首先运行,以处理逻辑和状态的变更,然后是draw方法,以反映这些更新在屏幕上。

                              通过继承精灵对象,敌机和玩家的飞机可以复用大量的代码,使得管理游戏中的各种对象更加方便和高效。每个对象都能独立地更新自己的状态并在屏幕上表现出来,而无需每个对象单独编写大量重复的代码。这样的设计也方便了后续的扩展和维护。

                              代码
                              //每个精灵对象的脚步
                              //通过继承来实现使用
                              struct sprite {
                              public:
                              	//绘制画面  每帧执行
                              	void (*draw)(struct sprite*);
                              	//更新数据  每帧执行
                              	void (*update)(struct sprite*);
                              	//坐标
                              	int x;
                              	int y;
                              	//尺寸
                              	int width;
                              	int height;
                              };
                              

                              2.3 设计英雄飞机

                              结构体设计

                              plane 结构体继承自 sprite 类,包含以下元素:

                              IMAGE* imgArrPlane[6]:存储飞机图像。

                              IMAGE* imgArrPlaneMask[6]:存储飞机图像的掩码,用于在游戏中处理透明和重叠部分。

                              enum planeStatus status:表示飞机当前的状态(正常飞行,摧毁爆炸等)。

                              int life:飞机的生命值。

                              int planeUpdateCnt:更新计数器,用于控制状态更新频率。

                              bool wasHit:标记飞机是否被击中。

                              初始化

                              planeInit 函数负责初始化飞机对象:

                              设定绘制(draw)和更新(update)方法指向对应的函数。

                              初始状态设置为 normal0,生命值为预设常量。

                              初始化飞机的位置坐标。

                              加载飞机状态对应的图像及其掩码。

                              绘制与更新

                              planeDraw 函数控制飞机在屏幕上的绘制,根据当前状态选择对应的图像和掩码。

                              planeUpdate 函数每帧调用一次,处理飞机的状态转换:

                              • 如果飞机生命值大于零,交替在正常状态之间切换以模拟飞行动画。
                              • 如果飞机生命值为零,按序播放被击落动画直至完全摧毁。
                                资源回收

                                planeDestroy 函数清理所有动态分配的资源,防止内存泄漏。

                                关键技术点
                                1. 状态管理:通过枚举管理飞机的不同状态,使得状态转换清晰易管理。
                                2. 动态资源管理:使用动态分配的图像资源,并在对象销毁时释放,确保资源使用的正确性。
                                3. 帧更新控制:通过 planeUpdateCnt 控制状态更新的频率,优化动画表现。

                                该模块充分展示了面向对象设计的优势,通过继承和多态简化了代码的复杂性,同时提高了代码的可维护性和扩展性。

                                代码(只展示头文件)
                                #pragma once
                                #include
                                #include"sprite.h"
                                #include
                                //枚举类型 进行1 - 7 形态转化
                                //其中一一对应一张照片
                                typedef enum planeStatus {
                                	normal0,
                                	normal1,
                                	down0,
                                	down1,
                                	down2,
                                	down3,
                                	destroy
                                }planeStatus;
                                //记录从正常到销毁的各个状态的顺序
                                struct plane {
                                	struct sprite super;
                                	IMAGE* imgArrPlane[6];
                                	IMAGE* imgArrPlaneMask[6];
                                	//记录目前飞机状态
                                	enum planeStatus status;
                                	//记录英雄生命值
                                	int life;
                                	//更新计数器
                                	int planeUpdateCnt;
                                	bool wasHit;
                                };
                                void planeInit(struct plane* h);
                                void planeDestroy(struct plane* h);
                                void planeDraw(struct plane* h);
                                void planeUpdate(struct plane* h);
                                

                                2.4 游戏场景设置

                                游戏背景

                                游戏的背景是一张图片,为了模拟飞行的向前移动,可以将两张图片进行一个拼接,不断移动该合成图片。

                                每次图片移动一个像素,如果超出范围,那么对图片进行复位

                                #include"sprite.h"
                                #include
                                struct background {
                                	struct sprite super;
                                	//两张图片的y坐标
                                	//因为只需要前后移动 
                                	int yA;
                                	int yB;
                                	IMAGE* imgBackground;
                                };
                                //初始化背景
                                void backgroundInit(struct background* );
                                //销毁背景
                                void backgroundDestory(struct background* );
                                
                                游戏循环函数

                                在这个循环里,我们可以每一帧对需要渲染的对象进行绘制与更新,做到画面的实时更新。其中游戏场景中有许多共性,我们可以提取出来作为一个精灵对象。

                                #include
                                //场景基础对象
                                //提供继承方法
                                struct scene {
                                	//四类方法
                                	//绘制场景中的所有精灵
                                	void (*draw)(struct scene*);
                                	//用于更新场景中的所有精灵
                                	void(*update)(struct scene*);
                                	//获取鼠标 或 键盘消息
                                	//进而控制场景中的精灵
                                	void(*control)(struct scene*, ExMessage* msg);
                                	//指示该场景是否结束
                                	bool(*isQuit)(struct scene*);
                                };
                                
                                1. 初始化中我们对英雄飞机,敌机,游戏背景进行初始化(通过各自的初始化函数)。
                                2. 销毁函数中依次调用各个对象的销毁函数即可。
                                3. draw方法中 通过vector中记录的结构体指针来调用每个对象的draw方法,完成绘制任务。
                                4. update方法中通过vector中记录的结构体指针来调用每个对象的update方法,完成更新任务。
                                5. control方法中获取鼠标信息,检测是否移动,然后更新英雄飞机位置。
                                6. isQuit方法检查是否需要退出。
                                代码
                                #include 
                                #include
                                #include "sprite.h"
                                #include
                                #include"gameloop.h"
                                #include
                                //精灵对象指针 和 帧率
                                void gameloop(struct scene* s, int fps) {
                                	//设置精度为1毫秒 画面更流畅
                                	timeBeginPeriod(1);
                                    //设置 开始时间、结束时间、频率F
                                    LARGE_INTEGER startCount, endCount, F;
                                    //  获取频率F
                                    QueryPerformanceFrequency(&F);
                                    BeginBatchDraw();
                                    //检测暂停
                                    while (1) {
                                        //空格暂停
                                        bool isPaused = false;
                                        if (GetAsyncKeyState(VK_SPACE) & 0x8000) {
                                            // 切换暂停状态
                                            isPaused = !isPaused;
                                            // 防止连续触发
                                            while (GetAsyncKeyState(VK_SPACE) & 0x8000) {
                                                Sleep(10);
                                            }
                                        }
                                        if (!isPaused) {
                                            //计算时间
                                            // 开始无限循环,这是游戏循环的核心部分。
                                            //记录当前的性能计数器值到startCount中,这代表了这一帧开始的时间点。
                                            QueryPerformanceCounter(&startCount);
                                            //清空画面
                                            cleardevice();
                                            //调用场景对象的draw绘制画面
                                            s->draw(s);
                                            //调用场景对象的update方法更新画面
                                            s->update(s); // 每帧会更新状态
                                            //如果失败 ,退出游戏循环
                                            if (s->isQuit(s)) break;
                                            //再次调用QueryPerformanceCounter获取当前性能计数器值
                                            //并存入endCount中,
                                            QueryPerformanceCounter(&endCount);
                                            //设置流逝时间
                                            long long elapse = (endCount.QuadPart - startCount.QuadPart)
                                                / F.QuadPart * 1000000;
                                            //利用帧率计算每一帧时差
                                            while (elapse control(s, &msg);
                                                }
                                                QueryPerformanceCounter(&endCount);
                                                //  更新时差
                                                elapse = (endCount.QuadPart - startCount.QuadPart)
                                                    * 1000000 / F.QuadPart;
                                            }
                                            FlushBatchDraw();
                                            timeEndPeriod(1);
                                        }
                                        else {
                                            int countdown = 5;
                                            // 游戏暂停时的逻辑
                                            while (isPaused && countdown > 0) {
                                                // 清除屏幕
                                                //cleardevice();
                                                // 绘制倒计时
                                                TCHAR s[20];
                                                setfillcolor(0xAAAAAA);
                                                solidrectangle(80, 300, 380, 345);
                                                _stprintf_s(s, _T("暂停中... %d s"), countdown);
                                                outtextxy(100, 300, s);//422, 750
                                                FlushBatchDraw();
                                                // 等待一秒
                                                Sleep(1000);
                                                // 更新倒计时
                                                countdown--;
                                                // 如果倒计时结束,自动恢复游戏
                                                if (countdown 
                                                    isPaused = false;
                                                }
                                            }
                                        }
                                    }
                                }
                                
                                	//依旧继承sprite对象
                                	//draw update 
                                	struct sprite super;
                                	IMAGE* imgBullet; //子弹图片
                                	IMAGE* imgBulletMask;//掩码图片
                                };
                                void bulletInit(struct bullet*);
                                void bulletDestroy(struct bullet*);
                                
                                	enemyType0,//小
                                	enemyType1,//中
                                	enemyType2 //大
                                };
                                //中 小敌机 五张图片
                                //大敌机7张
                                enum enemyStatus {
                                	enemy_normal,//正常状态
                                	enemy_down0,
                                	enemy_down1,
                                	enemy_down2,
                                	enemy_down3,
                                	enemy_down4,
                                	enemy_down5,
                                	enemy_destroy
                                };
                                struct enemy {
                                	struct sprite super;
                                	//敌机被击中处理
                                	void (*hited)(struct enemy*);
                                	//销毁敌机
                                	void (*destroy)(struct enemy*);
                                	//敌机图片和掩码图片
                                	IMAGE** imgArrEnemy;
                                	IMAGE** imgArrEnemyMask;
                                	//敌机种类
                                	enum enemyType enemyType;
                                	
                                	double v; // 移动速度
                                	int life;//生命值
                                	int enemyDownCnt;//爆炸状态计数器
                                	int status;//敌机状态
                                	int lastStatusBeforeDestroy;//销毁前最后一个状态
                                };
                                void enemyInit(struct enemy* e);
                                void enemyDraw(struct enemy* e);
                                
                                	//  遍历所有子弹碰撞检测
                                	for (int i = 0; i vecBullets.get(&s->vecBullets, i);
                                		POINT bulletPoint;
                                		bulletPoint.x = pBullet->super.x + 6 / 2 ;//头部中心点
                                		bulletPoint.y = pBullet->super.y;
                                		//  检查每一颗子弹是否碰撞到任意敌机
                                		for (int j = 0; j vecEnemy.size; j++)
                                		{
                                			struct enemy* pEnemy = (struct enemy*)s->vecEnemy.get(&s->vecEnemy, j);
                                			//  敌机的宽度与高度
                                			int width, height;
                                			width = pEnemy->super.width;
                                			height = pEnemy->super.height;
                                			//  敌机矩形区域
                                			int left, top, right, bottom;
                                			left = pEnemy->super.x;
                                			top = pEnemy->super.y;
                                			right = left + width;
                                			bottom = top + height;
                                			//  检查子弹是否在敌机矩形区域内
                                			if (bulletPoint.x > left && bulletPoint.x  top && bulletPoint.y life != 0)
                                				{
                                					//  子弹撞击到敌机后,销毁子弹
                                					bulletDestroy(pBullet);
                                					free(pBullet);
                                					s->vecBullets.remove(&s->vecBullets, i);
                                					i--;
                                					//  敌机击中
                                					pEnemy->hited(pEnemy);
                                					//播放击毁音效
                                					if (pEnemy->life == 0)
                                					{
                                						s->enemyDownSoundMgr.play(&s->enemyDownSoundMgr);
                                					}
                                					break;
                                				}
                                			}
                                		}
                                	}
                                }
                                
                                英雄飞机与敌机碰撞检测

                                heroHitEnemyCheck 函数检查主角飞机与每个敌机是否发生重叠。

                                使用飞机和敌机的矩形碰撞框进行碰撞检测。只有当飞机处于正常飞行状态时,才进行碰撞检测。

                                如果检测到重叠,返回真值表示飞机受到攻击。

                                bool heroHitEnemyCheck(struct mainScene* s)
                                {
                                	//  plane矩形区域(比飞机实际区域较小)
                                	RECT rectHero;
                                	rectHero.left = s->plane->super.x + 16;
                                	rectHero.top = s->plane->super.y + 10;
                                	rectHero.right = s->plane->super.x + 16 * 3;
                                	rectHero.bottom = s->plane->super.y + 62;
                                	for (int i = 0; i vecEnemy.size; i++)
                                	{
                                		struct enemy* pEnemy = (struct enemy*)s->vecEnemy.get(&s->vecEnemy, i);
                                		int enemyWidth = 0, enemyHeight = 0;
                                		if (pEnemy->status != enemy_normal)
                                			continue;
                                		//  敌机矩形区域
                                		RECT rectEnemy;
                                		rectEnemy.left = pEnemy->super.x;
                                		rectEnemy.top = pEnemy->super.y;
                                		rectEnemy.right = pEnemy->super.x + pEnemy->super.width;
                                		rectEnemy.bottom = pEnemy->super.y + pEnemy->super.height;
                                		//  两区域是否重叠
                                		if (rectHero.left = rectEnemy.left &&
                                			rectHero.top = rectEnemy.top)
                                		{
                                			if (s->plane->status == normal0 || s->plane->status == normal1)
                                				return true;
                                		}
                                	}
                                	return false;
                                }
                                
                                关键技术点
                                1. 碰撞准确性:通过精确定义子弹的头部位置和飞机与敌机的具体矩形区域,提高碰撞检测的准确性。
                                2. 资源管理:在检测到碰撞时,及时销毁子弹并从列表中移除,优化内存使用和计算性能。
                                3. 游戏互动性增强:碰撞检测是增强游戏互动性的关键组成部分,使得游戏过程充满挑战性和反应需求。

                                2.7 计分板

                                计分的环节很简单:

                                1. 小敌机 - 10
                                2. 中敌机 - 20
                                3. 大敌机 - 50

                                击毁敌机后 进行一个分数的叠加(mainscene中有对应mark变量来记录分数)即可

                                计分版的绘制也要加入到mainscene中的绘制里,每帧都进行更新。

                                //打印分数
                                char buff[30];
                                sprintf(buff, "得分:%d  生命值:%d", s->mark,s->plane->life );
                                outtextxy(0, 0, buff);
                                

                                2.8 游戏菜单

                                结构体设计

                                menuScene 结构体继承自 scene 类,增加了特定的功能和属性来处理菜单操作

                                IMAGE* bk:背景图片。

                                RECT rectStartGame, rectEndGame:开始游戏和结束游戏按钮的矩形区域。

                                bool isStartGameHover, isEndGameHover:标记鼠标是否悬停在对应的按钮上。

                                bool isQuit:标记是否退出菜单场景。

                                功能方法

                                menuSceneInit:初始化菜单场景,设置按钮的位置和大小,加载背景图像。

                                menuSceneDraw:绘制菜单背景和按钮。根据鼠标是否悬停在按钮上改变按钮文字颜色。

                                menuSceneUpdate:一个空函数,因为菜单界面可能不需要在每帧都更新数据。

                                menuSceneControl:处理菜单的交互逻辑,包括鼠标移动和点击事件:

                                如果鼠标悬停或离开按钮区域,更新悬停状态。

                                点击开始游戏按钮时,设置退出标志。

                                点击排行榜按钮时,读取并显示排行榜信息(打印到控制台)。

                                menuSceneIsQuit:返回是否退出菜单的状态。

                                交互逻辑

                                根据用户的输入(鼠标移动和点击),更新界面显示和状态。这包括悬停效果和响应按钮点击。

                                关键技术点

                                事件驱动:菜单的交互完全基于事件,如鼠标移动和点击,允许响应式更新。

                                图形用户界面(GUI)管理:使用矩形框来管理按钮的位置和大小,易于调整和管理。

                                资源管理:加载并显示图像,以及在适当时机销毁资源,防止内存泄漏。

                                2.9 音乐设计

                                背景音乐

                                音乐设计是比较简单的,我们在mainscene调用音乐文件即可:

                                //敌机音乐初始化
                                soundManagerInit(&s->enemyDownSoundMgr, "sounds/enemy_down.wma");
                                mciSendString("open sounds/background.mp3", NULL, 0, NULL);
                                mciSendString("play sounds/background.mp3 repeat", NULL, 0, NULL);
                                

                                退出后要在mainSceneDestroy关闭音乐文件

                                	//  停止背景音乐
                                	mciSendString("close sounds/background.wma", NULL, 0, NULL);
                                	//  停止英雄爆炸音乐
                                	mciSendString("close sounds/hero_down.wma", NULL, 0, NULL);
                                	//  停止敌机爆炸音效
                                	soundManagerDestroy(&s->enemyDownSoundMgr);
                                
                                击毁音效

                                检测到碰撞就进行播放,每个击毁声音j结构体使用vector容器进行储存,使其可以同步播放。

                                结构体设计

                                soundManager 结构体包括以下主要成员:

                                vector vecSoundAlias:存储音频别名的向量,用于跟踪和管理多个音频实例。

                                char soundPath[100]:存储音频文件的路径。

                                函数指针 play 和 close:分别用于播放音频和关闭音频。

                                功能方法

                                soundPlay:启动音频播放。使用 mciSendString 函数根据音频路径和动态生成的别名来打开和播放音频。

                                soundClose:根据指定的时间间隔检查并关闭已完成播放的音频实例。这通过比较当前时间和音频开始播放的时间来决定是否关闭音频。

                                soundManagerInit:初始化音频管理器,设置路径和函数指针,并初始化音频别名向量。

                                soundManagerDestroy:销毁音频管理器,关闭所有音频实例并释放资源。

                                关键技术点
                                1. 动态资源管理:通过动态分配的别名来管理音频资源,确保每个音频实例都可以独立控制和释放。
                                2. 时间驱动的资源释放:使用系统的当前时间来判断音频是否播放完毕,并根据结果关闭音频实例,有效管理内存和系统资源。
                                3. 复杂的音频处理:允许同时处理多个音频播放,提高游戏的多任务处理能力和用户体验。

                                项目效果展示

                                通过上面的设计,我们实现来看飞机大战的主要功能

                                Thanks♪(・ω・)ノ谢谢阅读!!!

                                下一篇文章见!!!


文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复:表情:
评论列表 (暂无评论,0人围观)

还没有评论,来说两句吧...

目录[+]

取消
微信二维码
微信二维码
支付宝二维码