消灭星星游戏程序设计【连载十一】——在线程中解决音效卡顿问题

news2024/12/25 14:29:29

消灭星星游戏程序设计【连载十一】——在线程中解决音效卡顿问题

大家每次都可以在页面中下载本节内容的实现代码,一步一步从简单开始,逐步完成游戏的各种功能,如果大家有任何问题也欢迎留言交流。

游戏整体效果展示:

在这里插入图片描述

1、本节要达到的效果

前面的章节,我们添加了小星星爆炸时的音效,游戏效果得到了极大的提升。但是我们现在又发现一个新的问题,每次在播放爆炸音效时,小星星在移动时都会出现卡顿的情况。而且这种卡顿的情况影响比较严重,计算机在图片的显示上显卡处理的速度非常快不会有问题,问题就出在音效的播放上,所以这个声音播放的卡顿问题亟待解决。

2、用线程去解决音效卡顿

这里我们就到线程的概念,因为我们每次声音的播放只存在刚刚开始时0.0几秒的卡顿,从耳朵听是听不出来差别,但从视觉上就会感觉到图像动画不连续的问题。因此,我们在线程中处理声音,声音播放卡顿是在线程中卡顿,卡顿的0.0几秒它不会影响到游戏效果,最主要的主程序中图像效果不会受到声音播放卡顿的影响,可以完美解决问题。

3、线程的具体操作

要使用线程,我们需要做的就是在主程序中创建一个线程,这个线程我们需要通过创建函数告诉需要程序,在线程中运行的一个自定义循环操作函数,然后我们根据自己的需求,在这个循环函数中完成声音的操作。


//定义全局线程对象,用以管理、操作音效资源

HANDLE		hMusicThread=NULL;

//定义全局线程同步对象,用以使用线程时确保音效资源数组有效操作

HANDLE		hMusicMutex=NULL;

	case WM_CREATE:
	
		......

		//在线程中处理音效的加载、播放、设置、删除

		hMusicThread=CreateThread(0,0,(LPTHREAD_START_ROUTINE)GameMusicThread,NULL,0,0);

		//创建线程同步对象,使用互斥对象保证同一时刻只允许一个线程操作音效资源数组

		hMusicMutex=CreateMutex(NULL,FALSE,NULL);

		......
		

同时需要自定义一个循环操作函数,并在这个循环体内处理我们需要音乐的操作。这里我们要注意一个问题,加入线程后,所有的声音操作,有声音的加载、播放、暂停、卸载操作必须要全部在线程中处理。不能出现在主程序中加载声音,在线程中播放声音的操作,这样的操作也是播放不出来声音的。因此我们对声音的实质性操作必须放在线程中。简单一点说,音乐的加载、播放、停止操作必须在这个线程函数中处理。这里使用了CreateMutex和WaitForSingleObject和ReleaseMutex函数,主要是为了解决不同线程中避免同时对某些变量进行操作而导致的操作冲突。


//在线程中处理音效的加载、播放、设置、删除

void	GameMusicThread()
{

	//进入处理音效的循环

	while(true)
	{

		//锁定音效资源数组操作

		WaitForSingleObject(hMusicMutex,INFINITE);

		//在线程中自动处理音效的各种操作,包括加载、播放、停止等操作

		......

		//关闭锁定音效资源数组操作

		ReleaseMutex(hMusicMutex);

		//防止系统过载运行

		Sleep(1);

	}

}

4、线程中音效资源的操作

为了能够统一操作音效资源,我们采用一个指针数组来存储所有的Music类指针,方便统一操作。同时在Music类的构造函数中自动将指针保存到指针数组中,省去了后期逐个添加音效指针到数组中的重复操作。

//设置所有的音乐类的最大个数

#define		MUSICSAVERMAXNUM	30

//保存所有的音乐类,便于统一管理

Music		*ptMusicSaver[MUSICSAVERMAXNUM];

同时取消在原构造函数中加载音效资源的操作,后边将改到线程函数中加载资源。

Music::Music(char *szTempFilename,int iTempTrackMaxNum,int iTempVolume)
{

	....

	for(int i=0;i<MUSICSAVERMAXNUM;i++)
	{
		
		if(ptMusicSaver[i]==NULL)
		{
			
			ptMusicSaver[i]=this;
			
			break;
			
		}
		
	}

	//初始化音乐分量,并命名音乐分量
	
	//LoadFromFile();

	//设置音量大小

	//SetVolume(iTempVolume);

}

接下来我们修改线程操作函数GameMusicThread(),这里我们使用一个全局变量tagAlreadyLoadMusicResource来标记资源是否已经被加载,如果音效加载状态为false则加载资源;如果状态为true则资源已被加载则不要重复加载,直接进行后续的音效处理即可。


//标记是否已经记载音效资源

bool		tagAlreadyLoadMusicResource=false;

//在线程中处理音效的加载、播放、设置、删除

void	GameMusicThread()
{

	//进入处理音效的循环

	while(true)
	{

		//锁定音效资源数组操作

		WaitForSingleObject(hMusicMutex,INFINITE);

		//在线程中自动处理音效的各种操作

		if(tagAlreadyLoadMusicResource==false)
		{

			//标记已经加载音效资源状态

			tagAlreadyLoadMusicResource=true;
			
			//在线程中加载音效资源
			
			for(int i=0;i<MUSICSAVERMAXNUM;i++)
			{
				
				if(ptMusicSaver[i]!=NULL)
				{

					if(strlen(ptMusicSaver[i]->szFilename)!=0)
					{
					
						ptMusicSaver[i]->LoadFromFile();

					}
						
				}
				
			}

		}

		//关闭锁定音效资源数组操作

		ReleaseMutex(hMusicMutex);

		//防止系统过载运行

		Sleep(1);

	}

}

这样就可以将音效资源在线程中完成加载,游戏进入主界面也不会有卡顿的情况出现。

5、怎样在主线程中播放线程的音效呢?

我们刚刚说过,不能在主程序中直接播放线程中加载的音效资源,那如果主程序中想尝试播放或停止声音,又该怎么做呢?我们可以采用在主程序中定义声音的状态变量,当主程序需要播放或停止声音时,就改变这个状态变量的值;另一方面,在线程中不断检测状态信息的变化情况,当状态信息发生改变,就立即按照状态信息内容进行相应的播放或停止操作,并及时重置状态信息。我们可以对音乐类添加以下用于指令控制的状态变量字符串:

class Music
{

	......
	
public:

	char	szThreadCommand[1024];			//用于在线程中操作的指令
	
	......

public:

	......

	//在线程中播放音效,参数为是否循环播放

	void	PlayInThread(bool tagLoop=false);

	//在线程中停止音效

	void	StopInThread();
	
	......

};

void	Music::PlayInThread(bool tagLoop)
{

	//锁定音效资源数组操作

	WaitForSingleObject(hMusicMutex,INFINITE);

	//设置不同的命令,以便在线程中去进行相应的操作

	if(tagLoop==true)
	{

		strcpy(szThreadCommand,"Play Music Loop");

	}
	else
	{

		strcpy(szThreadCommand,"Play Music");

	}
	
	//关闭锁定音效资源数组操作
	
	ReleaseMutex(hMusicMutex);

}

void	Music::StopInThread()
{
	
	//锁定音效资源数组操作

	WaitForSingleObject(hMusicMutex,INFINITE);

	//设置不同的命令,以便在线程中去进行相应的操作

	strcpy(szThreadCommand,"Stop Music");

	//关闭锁定音效资源数组操作
	
	ReleaseMutex(hMusicMutex);

}

这里我们使用PlayInThread和StopInThread方法去代替以前的Play和Stop方法,PlayInThread和StopInThread方法的实质就是只对szThreadCommand进行操作指令的赋值,不进行音效的实质性播放和停止操作;待线程中判断szThreadCommand当前的指令,在根据指令的不同进行Play和Stop方法的操作。这样音效的加载、播放、停止操作就同时放在了线程中,同时主程序中也可以方便的操作线程音效的播放,更重要的是,线程中音效的播放不会产生卡顿影响。

主线程操作如下:

		//旧的播放方式会卡,将被线程播放函数取代

		//mWelcome.Play();
		
		//在线程播放声音文件

		mWelcome.PlayInThread();

我们这里有以下命令需要处理,声音的播放,声音的循环播放,声音暂停,在我这个小游戏里,已经基本上够用了。如果后期还需要更复杂的操作,比如控制循环播放的次数,设置音量的大小,包括在3d游戏里面声音随玩家距离的改变而改变音效音量大小等,可以进行更复杂的操作,后期我还会在我的专题OpenGL实现3D游戏编程中详细描述,有兴趣可以了解一下。

6、线程中音效的播放、停止操作

接下来,我们就要在线程中处理音效的播放、停止等操作了。我们通过前期对szThreadCommand赋予不同字符串"Play Music",“Play Music Loop”,"Stop Music"来区分不同操作指令,当线程中读取到以上指令后,立即重置指令为空,并调用以前的Play和Stop方法在线程中播放音效。


//在线程中处理音效的加载、播放、设置、删除

void	GameMusicThread()
{

	//进入处理音效的循环

	while(true)
	{

		//锁定音效资源数组操作

		WaitForSingleObject(hMusicMutex,INFINITE);

		//在线程中自动处理音效的各种操作

		if(tagAlreadyLoadMusicResource==false)
		{

			//标记已经加载音效资源状态

			tagAlreadyLoadMusicResource=true;
			
			//在线程中加载音效资源
			
			for(int i=0;i<MUSICSAVERMAXNUM;i++)
			{
				
				if(ptMusicSaver[i]!=NULL)
				{

					if(strlen(ptMusicSaver[i]->szFilename)!=0)
					{
					
						ptMusicSaver[i]->LoadFromFile();

					}
						
				}
				
			}

		}
		else
		{

			//在线程中加载音效资源
			
			for(int i=0;i<MUSICSAVERMAXNUM;i++)
			{
				
				if(ptMusicSaver[i]!=NULL)
				{

					if(strlen(ptMusicSaver[i]->szFilename)!=0)
					{

						//不同的操作:音乐播放一次

						if(strcmp(ptMusicSaver[i]->szThreadCommand,"Play Music")==0)
						{

							strcpy(ptMusicSaver[i]->szThreadCommand,"");

							ptMusicSaver[i]->Play();

						}

						//不同的操作:音乐的循环播放

						if(strcmp(ptMusicSaver[i]->szThreadCommand,"Play Music Loop")==0)
						{

							strcpy(ptMusicSaver[i]->szThreadCommand,"");

							ptMusicSaver[i]->Play(true);

						}

						//不同的操作:音乐的停止

						if(strcmp(ptMusicSaver[i]->szThreadCommand,"Stop Music")==0)
						{

							strcpy(ptMusicSaver[i]->szThreadCommand,"");

							ptMusicSaver[i]->Stop();

						}

					}

				}

			}

		}

		//关闭锁定音效资源数组操作

		ReleaseMutex(hMusicMutex);

		//防止系统过载运行

		Sleep(1);

	}

}

设计了以上的操作模式,声效的实际操作就变得简单了,你也可以直接解决掉音效卡顿的问题,经过实际测试游戏已经变得非常丝滑,再也没有卡顿的问题。

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

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

相关文章

宠物空气净化器哪款好?希喂、有哈宠物空气净化器测评

回想起几年前那个午后&#xff0c;我意外的在路边捡到了两只小猫咪&#xff0c;心中莫名有一份责任感出现&#xff0c;所以没有丝毫犹豫我就决定将它们带回家。捡回家以后&#xff0c;家里确实多了几分温馨&#xff0c;逐渐成为我的精神支柱。小猫的到来&#xff0c;让家的每一…

Mybatis工具类的封装

为什么要进行Mybatis工具类的封装&#xff1f; 每次我们执行数据库操作都需要做以下操作&#xff1a; //读取配置文件InputStream inputStream Resources.getResourceAsStream("mybatis-config.xml");//通过配置文件创建SqlSessionFactorySqlSessionFactory sqlSess…

模板[C++]

目录 1.&#x1f680;泛型编程&#x1f680; 2.&#x1f680;函数模板&#x1f680; 2.1 ✈️函数模板概念✈️ 2.2 ✈️函数模板格式✈️ 2.3✈️函数模板的原理✈️ 2.4 ✈️函数模板的实例化✈️ 2.5 ✈️模板参数的匹配原则✈️ 3.&#x1f680;类模板&#x1f680…

Ubuntu20.4 系统安装后无wifi图标

0. 问题排查 1.检查 BIOS 设置: 有时候&#xff0c;无线网卡可能在 BIOS 中被禁用。重启电脑&#xff0c;进入 BIOS 设置&#xff0c;确保无线网卡选项是启用的。 2.检查硬件开关: 检查您的笔记本电脑是否有物理开关或键盘快捷键来启用或禁用无线网卡。 3.在软件更新中切换…

Codeforces Round 495 (Div. 2) F. Sonya and Bitwise OR(线段树)

原题链接&#xff1a;F. Sonya and Bitwise OR 题目大意&#xff1a; 给出一个长度为 n n n 的数组 a a a&#xff0c;并给出 m m m 次询问以及一个数字 x x x。 每个询问形式如下给出&#xff1a; 1 1 1 i i i y y y &#xff1a;将 a i a_{i} ai​ 位置的值更改为 y…

将2,3,4,5,6,8分别填入算式“口口口X口口口“的“囗“中,怎么填使得算式结果最大。

一、解题思路 将数组元素进行全排列&#xff1a;对整个数组进行全排列&#xff0c;这样我们可以避免手动选择组合、排列剩余元素等步骤。 直接分割排列后的数组&#xff1a;在每一个全排列中&#xff0c;前3个元素和后3个元素自然形成了一个组合和一个剩余元素组合。 计算并…

Linux安装redis和使用redisDesktop连接

目录 Linux安装redis及启动 第一步&#xff1a;下载redis压缩包 第二步&#xff1a;下载gcc-c 第三步&#xff1a;解压redis文件 第四步&#xff1a;进入redis-4.0.0.0目录执行make命令 第五步&#xff1a;安装redis到redis目录 第五步&#xff1a;复制redis.conf配置文件…

电脑开机LOGO修改教程_BIOS启动图片替换方法

准备工具&#xff1a;刷BIOS神器和change logo&#xff0c;打包下载地址&#xff1a;https://download.csdn.net/download/baiseled/89374686 一.打开刷BIOS神器&#xff0c;点击备份BIOS&#xff0c;保存到桌面 二.打开change logo&#xff0c;1.点击load image&#xff0c;选…

11-sentinel利用nacos作持久化

本文介绍sentinel配置数据的持久化方法。由于sentinel官方并没有提供持久化功能&#xff0c;大家在测试过程中也能发现sentinel服务重启后&#xff0c;原来配置的数据就丢了&#xff0c;本文就是来处理这一问题的。 做好心理准备&#xff0c;我们要修改sentinel的源代码&#…

Python 批量修改 Word 文档中图片的大小并居中对齐

Python 批量修改 Word 文档中图片的大小并居中对齐 错过&#xff0c;再遇见可能就难了&#xff01;此时&#xff0c;你是你&#xff0c;我是我&#xff0c;再遇见&#xff0c;可就真的你是你&#xff0c;我是我&#xff0c;没有一丝的牵连纠缠—— !!! 对于已经编辑好的文档一定…

图表:调用FluentUI中的折线图散点图和饼状图

文章目录 0.文章介绍1.源码位置2.效果图3.代码3.1 代码结构3.2 main.qml3.3 MyLineChart.qml 0.文章介绍 调用项目FluentUI中的散点图、折线图和饼状图组件&#xff0c;做定制化改进。 项目FluentUI源码位置&#xff1a;https://github.com/zhuzichu520/FluentUI 项目FluentUI…

物联网产业链图谱_产业链全景图_物联网行业市场分析

物联网(IoT, Internet of Things)是通信网和互联网的拓展应用与网络延伸&#xff0c;它利用感知技术与智能装置对物理世界进行感知识别&#xff0c;通过网络传输互联&#xff0c;进行计算、处理和知识挖掘&#xff0c;实现人与物、物与物信息交互和无缝链接&#xff0c;达到对物…

springboot生成、响应图片验证码

我们平时经常会碰见图片验证码&#xff0c;那么在springboot中我们该怎么实现呢 我们可以使用一款开源的验证码生成工具EasyCaptcha&#xff0c;其支持多种类型的验证码&#xff0c;例如gif、中文、算术等&#xff0c;并且简单易用&#xff0c;具体内容可参考其官方文档。 效果…

网站SSL证书该如何更新?

网站SSL证书的更新是一个确保网站安全性的重要步骤。以下是一个详细的更新流程&#xff1a; 一、检查证书有效期 首先&#xff0c;需要定期检查SSL证书的有效期。通常情况下&#xff0c;SSL证书的有效期为一年&#xff0c;到期前需要进行更新。可以通过以下方式检查证书有效期…

证书|“机器学习工程师”来了,由工业和信息化部教育与考试中心颁发,含金量高

“机器学习工程师”职业技术考试是由工业和信息化部教育与考试中心推出人才考核标准&#xff0c;在互联网、零售、金融、电信、医学、旅游等行业均有涉及&#xff0c;是专门从事数据采集、数据分析、机器学习、人工智能并能制作业务报告、提供决策的新型数据分析人才所需要的技…

小程序滑动单元格

项目场景&#xff1a;小程序用户管理列表&#xff0c;通过单元格滑动实现“密码重置”、“删除”功能。 技术框架&#xff1a;uniapp、uview3、ts 效果如下&#xff1a; 前端页面&#xff1a; <template><view class"fui-wrap"><view class"f…

Ubuntu连接GitHub

报错&#xff1a;Please make sure you have the correct access rights and the repository exists.原因&#xff1a;本地没有SSH Key存在解决&#xff1a; 首先为系统设置github的用户名和自己的邮箱 git config --global user.name "****" git config --global us…

2024新型数字政府综合解决方案(一)

新型数字政府综合解决方案通过整合先进的数字技术和智能化系统&#xff0c;构建了一个高效、透明且响应迅速的政府服务平台&#xff0c;能够实现跨部门数据共享和实时信息更新。该解决方案包括智能数据分析、大数据平台和云计算服务&#xff0c;旨在提升政府决策的科学性和行政…

Transformer问题总结及实现

目录 前提&#xff1a; 注意&#xff1a;以下对于优化的问题&#xff0c;要回答这个问题&#xff1a;前一种方法的局限性在哪里&#xff0c;优化的方法是怎么进行优化的&#xff1f;&#xff08;未完全解决&#xff09; Step1:关于Transformer的疑问 Step2&#xff1a;关于…

初步认识Linux系统

前言 Linux系统具有许多优点&#xff0c;不仅系统性能稳定&#xff0c;而且是开源软件。其核心防火墙组件性能高效、配置简单&#xff0c;保证了系统的安全。在很多企业网络中&#xff0c;为了追求速度和安全&#xff0c;Linux不仅仅是被网络运维人员当作服务器使用&#xff0c…