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

news2024/11/17 21:46:58

在这里插入图片描述

源代码与图片参考自《你好编程》的飞机大战项目,这里不进行展示。
本项目是仅供学习使用的项目


飞机大战项目记录

  • 飞机大战设计报告
    • 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 游戏逻辑:

敌机从屏幕顶部随机位置出现,向下移动。
玩家需要避免敌机的攻击,同时尽可能多地击落敌机。

在这里插入图片描述

2 开始打造项目

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

2.1 图片素材准备

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

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

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

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

  4. 菜单图片与背景图片

在这里插入图片描述

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<easyx.h>
#include"sprite.h"
#include<cstdio>

//枚举类型 进行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<easyx.h>

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

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

#include<easyx.h>
//场景基础对象
//提供继承方法
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 <easyx.h>
#include<iostream>
#include "sprite.h"
#include<Windows.h>
#include"gameloop.h"
#include<conio.h>

//精灵对象指针 和 帧率
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 < 1000000 / fps) {
                Sleep(1);
                ExMessage msg;
                //创建一个信息对象 用来接收鼠标信息
                bool isOK = peekmessage(&msg, EX_MOUSE);

                //如果有信息,进行control操作
                if (isOK == true) {
                    s->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 <= 0) {
                    isPaused = false;
                }

            }

        }
    }
}

2.5 设计子弹与敌机

子弹设计模块
结构体设计

bullet 结构体继承自 sprite 类,包含以下元素:
IMAGE* imgBullet:子弹的图像。
IMAGE* imgBulletMask:子弹图像的掩码,用于在游戏中处理透明和重叠部分。

初始化

bulletInit 函数负责初始化子弹对象:
设定绘制(draw)和更新(update)方法指向对应的函数。
载入子弹的图像和掩码,准备用于绘制。

绘制与更新

bulletDraw 函数控制子弹在屏幕上的绘制,使用子弹的图像和掩码。
bulletUpdate 函数每帧调用一次,处理子弹的移动逻辑:
子弹向上移动,移动速度通过常量 bulletSpeed 控制。

资源回收

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

关键技术点
  1. 图像处理:通过使用掩码图像,子弹的绘制可以适应各种背景,使得子弹与游戏环境的融合更自然。
  2. 性能优化:子弹的更新逻辑简单(单一的向上移动),这有助于在屏幕上同时处理大量子弹时保持游戏性能。
  3. 资源管理:使用动态分配的图像资源,并在对象销毁时释放,确保资源使用的正确性。
代码(只展示头文件)
#include<easyx.h>
#include"sprite.h"
//子弹对象
struct bullet {
	//依旧继承sprite对象
	//draw update 
	struct sprite super;
	IMAGE* imgBullet; //子弹图片
	IMAGE* imgBulletMask;//掩码图片
};

void bulletInit(struct bullet*);
void bulletDestroy(struct bullet*);
敌机设计模块

这里我创建了三种敌机,使用枚举变量分别用0 , 1 ,2 来表示。每种敌机都有对应的状态枚举变量。我们可以提取出共性来创建一个敌机精灵对象。所有敌机均继承与这个敌机对象。

结构体设计

enemy 结构体继承自 sprite 类,包含以下字段:
IMAGE** imgArrEnemyIMAGE** imgArrEnemyMask:数组,存储敌机的图像和掩码,用于不同状态下的绘制。
enum enemyType enemyType:敌机种类,定义敌机的基本属性如大小和生命值。
double v:敌机的移动速度。
int life:敌机的生命值。
int enemyDownCnt:敌机爆炸状态的计数器,用于控制爆炸动画的播放速度。
int status:当前敌机的状态,从正常飞行到被击落的不同阶段。
int lastStatusBeforeDestroy:记录销毁前的最后一个状态,用于动画过渡。

初始化

enemyInit 函数负责初始化敌机对象:
设置绘制(draw)和更新(update)方法指向对应的函数。
初始化敌机的状态为 enemy_normal
设置敌机的随机移动速度。
加载敌机状态对应的图像及其掩码。

绘制与更新

enemyDraw 函数控制敌机在屏幕上的绘制,使用敌机的当前状态对应的图像和掩码。
enemyUpdate 函数每帧调用一次,处理敌机的移动和状态转换:
敌机向下移动,速度由 v 控制(随机值控制)。
当生命值为零时,敌机进入爆炸状态,逐渐播放爆炸动画直到完全摧毁。

交互操作

enemyHited 函数处理敌机被子弹击中的情况:
生命值递减。
生命值为零时开始播放爆炸动画。

资源回收

destroy 方法(未提供完整实现)应负责清理动态分配的图像资源,防止内存泄漏。

代码(只展示头文件)
#pragma once
#include<easyx.h>
#include"sprite.h"
//该文件为敌机的共性文件

//每种代表一种敌机
enum enemyType {
	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);
敌机实例化

通过上面的敌机共性,我们就可以绘制产生三种不同的敌机,同过对其中元素的修改就可以完成对应的功能。加载对应图片,绘制到相应位置。然后将敌机的绘制更新方法移动到mainscene的绘制更新中。

我们需要一个vector容器来容纳敌机。

  1. 敌机的产生逻辑是:通过随机数来确定产生那一种敌机(可以调整概率来改变敌机出现的种类数量),然后调用对应的初始化化函数,并储 在对应vector容器中

  2. 敌机的销毁逻辑是:判断是否出界和判断是否被子弹击中。每次检查直接遍历容器中所有的敌机,移出应该被销毁的敌机并回收对应资源。

2.6 设计击毁与碰撞逻辑

预期情况下,子弹击中敌机,敌机应该被销毁,英雄飞机撞击到敌机,英雄飞机应该被销毁。

为了检查子弹是否击中敌机,我们增添一个bulletEnemyCheck函数。

子弹与敌机碰撞检测

bulletHitEnemyCheck 函数遍历所有子弹和敌机,检查每颗子弹是否与敌机的碰撞框发生重叠。
子弹抽象为其头部的一个点进行精确检测

  1. 如果子弹的位置在敌机的矩形区域内,触发敌机的 hited 函数,处理击中逻辑(生命值减少,状态改变)。

  2. 如果击中敌机,子弹会被销毁,同时移除子弹列表中的该子弹项,防止重复检测。
    敌机生命值减为零时,触发播放击毁音效。

//检测子弹是否撞击
void bulletHitEnemyCheck(struct mainScene* s)
{
	//  遍历所有子弹碰撞检测
	for (int i = 0; i < s->vecBullets.size; i++)
	{
		//  将子弹抽象为头部的一个点
		struct bullet* pBullet = (struct bullet*)s->vecBullets.get(&s->vecBullets, i);
		POINT bulletPoint;
		bulletPoint.x = pBullet->super.x + 6 / 2 ;//头部中心点
		bulletPoint.y = pBullet->super.y;

		//  检查每一颗子弹是否碰撞到任意敌机
		for (int j = 0; j < s->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 < right &&
				bulletPoint.y > top && bulletPoint.y < bottom
				)
			{
				if (pEnemy->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 < s->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.right && rectHero.right >= rectEnemy.left &&
			rectHero.top <= rectEnemy.bottom && rectHero.bottom >= 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]:存储音频文件的路径。
函数指针 playclose:分别用于播放音频和关闭音频。

功能方法

soundPlay:启动音频播放。使用 mciSendString 函数根据音频路径和动态生成的别名来打开和播放音频。
soundClose:根据指定的时间间隔检查并关闭已完成播放的音频实例。这通过比较当前时间和音频开始播放的时间来决定是否关闭音频。
soundManagerInit:初始化音频管理器,设置路径和函数指针,并初始化音频别名向量。
soundManagerDestroy:销毁音频管理器,关闭所有音频实例并释放资源。

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

项目效果展示

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

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

下一篇文章见!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1610734.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文本美学:text-image打造视觉吸引力

当我最近浏览 GitHub 时&#xff0c;偶然发现了一个项目&#xff0c;它能够将文字、图片和视频转化为文本&#xff0c;我觉得非常有趣。于是我就花了一些时间了解了一下&#xff0c;发现它的使用也非常简单方便。今天我打算和家人们分享这个发现。 项目介绍 话不多说&#xf…

OpenHarmony 上传和下载(API 10)教程~

介绍 本示例使用ohos.request接口创建上传和下载任务&#xff0c;实现上传、下载功能&#xff0c;hfs作为服务器&#xff0c;实现了文件的上传和下载和任务的查询功能。 效果预览 使用说明 1.本示例功能需要先配置服务器环境后使用&#xff0c;具体配置见上传下载服务配置。…

百度AI大会发布的APP Builder和Agent Builder有什么区别

百度在AI大会发布了三款AI工具&#xff0c;包括智能体开发工具AgentBuilder、AI原生应用开发工具AppBuilder、各种尺寸的模型定制工具ModelBuilder 有很多人就问&#xff0c;APP Builder和Agent Builder有什么不一样&#xff0c;怎么那么多builder? 你们就这么理解&#xff…

大语言模型隐私防泄漏:差分隐私、参数高效化

大语言模型隐私防泄漏&#xff1a;差分隐私、参数高效化 写在最前面题目6&#xff1a;大语言模型隐私防泄漏Differentially Private Fine-tuning of Language Models其他初步和之前的基线微调模型1微调模型2通过低秩自适应进行微调&#xff08; 实例化元框架1&#xff09; 在隐…

MySQL面试题 3

问题1&#xff1a;char、varchar的区别是什么&#xff1f; varchar是变长而char的长度是固定的。如果你的内容是固定大小的&#xff0c;你会得到更好的性能。 问题2: TRUNCATE和DELETE的区别是什么&#xff1f; DELETE命令从一个表中删除某一行&#xff0c;或多行&#xff0…

机器学习系统的设计

1.混淆矩阵 混淆矩阵作用就是看一看在测试集样本集中&#xff1a; 真实值是 正例 的样本中&#xff0c;被分类为 正例 的样本数量有多少&#xff0c;这部分样本叫做真正例&#xff08;TP&#xff0c;True Positive&#xff09;&#xff0c;预测为真&#xff0c;实际为真真实值…

Java反射(reflection)java很多框架的底层都需要用到反射,至于API使用的话,还算简单,主要是类加载过程和反射机制的一个底层机制要了解一下

十六、反射&#xff08;reflection&#xff09; 反射可以通过外部文件配置&#xff0c;在不修改源码的情况下来控制程序&#xff0c;符合设计模式中的OCP原则&#xff08;开闭原则&#xff1a;不修改源码&#xff0c;扩容功能&#xff09;。 1、反射机制 &#xff08;1&…

SpringCloud系列(7)--Eureka服务端的安装与配置

前言&#xff1a;上一章节我们介绍了Eureka的基础&#xff0c;本章节则介绍Eureka服务端的安装与配置 Eureka架构原理图 1、创建Eureka Server端服务注册中心模块 (1)在父工程下新建模块 (2)选择模块的项目类型为Maven并选择模块要使用的JDK版本 (3)填写子模块的名称&#xf…

【C++】:函数重载,引用,内联函数,auto关键字,基于范围的for循环,nullptr关键字

目录 一&#xff0c;函数重载1.1 函数重载的定义1.1.1.形参的类型不同1.1.2参数的个数不同1.1.3.参数的顺序不同1.1.4.有一个是缺省参数构成重载。但是调用时存在歧义1.1.5.返回值不同&#xff0c;不构成重载。因为返回值可接收&#xff0c;可不接受&#xff0c;调用函数产生歧…

如何设计单元测试用例?

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 最近一些大公司在进行去测试化的操作&#xff0c;这一切的根源大概可以从几年前微软一刀切砍掉所…

【深度学习实战(10)】图像推理之预处理

一、预处理流程 在把一张图像送入模型进行推理时&#xff0c;需要先进行预处理&#xff0c;预处理流程包括&#xff1a; &#xff08;1&#xff09;读取图像 &#xff08;2&#xff09;尺寸调整&#xff0c;letter_box&#xff08;不失真&#xff09; &#xff08;3&#xff0…

MATLAB绘制圆锥曲线:抛物线,双曲线,椭圆

MATLAB绘制圆锥曲线:抛物线,双曲线,椭圆 clc;close all;clear all;warning off;%清除变量x linspace(-10, 10, 1000); % 创建一个x值的向量&#xff0c;范围从-10到10&#xff0c;共1000个点 y x.^2; % 计算每个x值对应的y值% 使用plot函数绘制图形 figure; % 创建一个新的图…

排序 “壹” 之插入排序

目录 ​编辑 一、排序的概念 1、排序&#xff1a; 2、稳定性&#xff1a; 3、内部排序&#xff1a; 4、外部排序&#xff1a; 二、排序的运用 三、插入排序算法实现 3.1 基本思想 3.2 直接插入排序 3.2.1 排序过程&#xff1a; 3.2.2 代码示例&#xff1a; 3.2.3…

使用Spring进行文件的上传和下载

概览 使用Spring进行文件的上传和下载Spring上传文件接口设计dubbo接口设计上传文件流的RPC的接口设计 Spring文件下载接口设计dubbo接口设计下载文件流的RPC的接口设计 spring上传文件大小控制 使用Spring进行文件的上传和下载 本文主要介绍在Spring框架下面调用微服务的dubb…

YOLOv9改进策略 | 添加注意力篇 | 利用ILSVRC冠军得主SENetV1改善网络模型特征提取能力

一、本文介绍 本文给大家带来的改进机制是SENet&#xff08;Squeeze-and-Excitation Networks&#xff09;其是一种通过调整卷积网络中的通道关系来提升性能的网络结构。SENet并不是一个独立的网络模型&#xff0c;而是一个可以和现有的任何一个模型相结合的模块(可以看作是一…

项目实践:贪吃蛇

引言 贪吃蛇作为一项经典的游戏&#xff0c;想必大家应该玩过。贪吃蛇所涉及的知识也不是很难&#xff0c;涉及到一些C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等。这里我会介绍贪吃蛇的一些思路。以及源代码也会给大家放到文章末尾。 我们最终的…

【Ne4j图数据库入门笔记1】图形数据建模初识

1.1 图形建模指南 图形数据建模是用户将任意域描述为节点的连接图以及与属性和标签关系的过程。Neo4j 图数据模型旨在以 Cypher 查询的形式回答问题&#xff0c;并通过组织图数据库的数据结构来解决业务和技术问题。 1.1.1 图形数据模型介绍 图形数据模型通常被称为对白板友…

【Gradle如何安装配置及使用的教程】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

双链表的实现

我们知道链表其实有很多种&#xff0c;什么带头&#xff0c;什么双向啊&#xff0c;我们今天来介绍双向带头循环链表&#xff0c;了解了这个其他种类的链表就很简单了。冲冲冲&#xff01;&#xff01;&#xff01; 链表的简单分类 链表有很多种&#xff0c;什么带头循环链表&…

tcp-learner 数据包分析 20240420

输入输出&#xff1a; 数据包分析&#xff1a; learner和Adapter建立连接。 Learner让Adapter发送RST Adapter没有从SUT抓到任何回复&#xff0c;于是向learner发送timeout learner给adapter发送reset命令&#xff0c;让SUT重置。 这是第一次初始化&#xff0c;由于Adapter和…