Unity 简易音乐播放系统

news2024/12/22 20:38:31

前言

众所周知, Unity自带音效播放没有回调,不能自动播放clip列表; 所以简单实现一个带自动播放功能的接口,用以实现音乐列表的逐个播放.

一. 功能分析

  1. 首先要求切换场景时音乐不停,只在需要时播放
  2. 其次即传入音乐名和播放次数,即可将该音乐循环播放指定次数
  3. 可以直接传入一个包含音乐名和播放次数的列表, 将所有列表中的音乐全部播放指定次数
  • 第一点很好解决,只要将挂载AudioListenerAudioSource的节点设置为切换场景不销毁即可
  • 第二个功能则需要通过开启协程,通过协程来判断音乐的播放状态, 再传入回调函数, 递归调用播放音乐函数
  • 第三个功能在第二个功能的基础上实现,通过传入回调函数并递归实现

二.实现

  1. 初始化
    这里的选择是创建一个AudioSystem节点挂在Listener,下面挂着两个节点分别挂载音乐节点和音效节点.
    在这里插入图片描述
    将父节点设置为不因场景跳转销毁的模式.
    注意在这里我将Loop属性关闭, 因为我是通过音乐是否在播放来实现协程检测的.
public class AudioSystem : MonoBehaviour
{
	// 单例
    static AudioSystem audioSys;
	// 音乐播放
    static AudioSource musicSource;
	// 音效播放
    static AudioSource soundSource;

	void Awake()
	{
		DontDestroyOnLoad(gameObject);
	}
    public static AudioSystem Instance
    {
        get
        {
            if(audioSys == null)
            {
                audioSys = new GameObject("AudioSystem").AddComponent<AudioSystem>();
                audioSys.gameObject.AddComponent<AudioListener>();
                audioSys.Init();
            }
            return audioSys;
        }
    }

    void Init()
    {
        musicSource = new GameObject("musicSource").AddComponent<AudioSource>();
        musicSource.transform.SetParent(audioSys.transform);
        musicSource.playOnAwake = false;
        musicSource.loop = false;
        soundSource = new GameObject("soundSource").AddComponent<AudioSource>();
        soundSource.transform.SetParent(audioSys.transform);
        soundSource.playOnAwake = false;
        soundSource.loop = false;
    }
}
  1. 音乐播放
    函数传入name音乐名, loopTime循环次数, action回调函数
public void PlayMusic(string name, int loopTime, Action action = null)

通过协程实际上很好实现,AudioSource中有个属性为IsPlaying,通过这个属性我们可以随时知道当前是否有音频在播放; 所以直接开一个协程等待即可.

  • 主要分为几步:
  • 判断是否正在播放该音频,如果是,那么重启一个协程即可,
  • 如果不是当前正在播放的音频,则加载需要播放的资源并播放.
  • 开启协程时,将循环次数减少,代表还剩多少遍播放.

协程中:

  • 先等待音乐播放完
  • 播完后判断是否需要循环
  • 循环的话就调用PlayMusic
  • 不循环则执行回调
	static string nowName = null;
	static Coroutine coroutine;
    // 播放完成执行acion
    public void PlayMusic(string name, int loopTime, Action action = null)
    {
        if (coroutine != null)
            StopCoroutine(coroutine);
        
        if(nowName == name)
        {
        	//正在播放当前资源
            if(!musicSource.isPlaying)
                musicSource.Play();
        }
        else
        {
        	//加载资源
        	AudioClip clip = Resources.Load<AudioClip>(PATH + fname + ".wav");
            if(clip == null)
                return;
            if (musicSource.isPlaying)
                musicSource.Stop();
            musicSource.clip = clip;
            nowName = name;
            musicSource.Play();
        }
        // 开启协程,并将循环次数 - 1
        coroutine = StartCoroutine(MusicLoop(name, loopTime - 1, action));
    }

    IEnumerator MusicLoop(string path, int loopTime, Action action = null)
    {
    	//等待播放完毕
        while (musicSource.isPlaying) yield return null;
		//播放次数不为0就继续循环
        if (loopTime !=  0)
            PlayMusic(path, loopTime, action);
        //播放完毕且有回调则执行
        else if(action != null)
            action();
    }
  1. 播放列表
    这里只要利用回调函数来移动下标就可以了
    //播放列表音乐
    public void PlayMusicList(List<KeyValuePair<string, int>> list, int index = 0)
    {
        if (list == null || index >= list.Count) return;
        PlayMusic(list[index].Key, list[index].Value, () =>
        {
            PlayMusicList(list, index + 1);
        });
    }

三. 完整代码

  • 注意销毁协程

public class AudioSystem : MonoBehaviour
{
	// 单例
    static AudioSystem audioSys;
	// 音乐播放
    static AudioSource musicSource;
	// 音效播放
    static AudioSource soundSource;
	// 当前音乐名
    static string nowName = null;

	void Awake()
	{
		DontDestroyOnLoad(gameObject);
	}
	// 单例
    public static AudioSystem Instance
    {
        get
        {
            if(audioSys == null)
            {
                audioSys = new GameObject("AudioSystem").AddComponent<AudioSystem>();
                audioSys.gameObject.AddComponent<AudioListener>();
                audioSys.Init();
            }
            return audioSys;
        }
    }

	// 创建两个子节点
    void Init()
    {
        musicSource = new GameObject("musicSource").AddComponent<AudioSource>();
        musicSource.transform.SetParent(audioSys.transform);
        musicSource.playOnAwake = false;
        musicSource.loop = false;
        soundSource = new GameObject("soundSource").AddComponent<AudioSource>();
        soundSource.transform.SetParent(audioSys.transform);
        soundSource.playOnAwake = false;
        soundSource.loop = false;
    }

    //播放列表音乐
    public void PlayMusicList(List<KeyValuePair<string, int>> list, int index = 0)
    {
        if (list == null || index >= list.Count) return;
        PlayMusic(list[index].Key, list[index].Value, () =>
        {
            PlayMusicList(list, index + 1);
        });
    }

    static Coroutine coroutine;
    // 播放完成执行acion
    public void PlayMusic(string name, int loopTime, Action action = null)
    {
        if (coroutine != null)
            StopCoroutine(coroutine);
        
        if(nowName == name)
        {
        	//正在播放当前资源
            if(!musicSource.isPlaying)
                musicSource.Play();
        }
        else
        {
        	//加载资源
        	AudioClip clip = Resources.Load<AudioClip>(PATH + fname + ".wav");
            if(clip == null)
                return;
            if (musicSource.isPlaying)
                musicSource.Stop();
            musicSource.clip = clip;
            nowName = name;
            musicSource.Play();
        }
        // 开启协程,并将循环次数 - 1
        coroutine = StartCoroutine(MusicLoop(name, loopTime - 1, action));
    }

    IEnumerator MusicLoop(string path, int loopTime, Action action = null)
    {
    	//等待播放完毕
        while (musicSource.isPlaying) yield return null;
		//播放次数不为0就继续循环
        if (loopTime !=  0)
            PlayMusic(path, loopTime, action);
        //播放完毕且有回调则执行
        else if(action != null)
            action();
    }

    public void SetMusicVolume(float volume)
    {
        musicSource.volume = Mathf.Clamp(volume, 0, 1);
    }

    public void PlayOneShot(string name)
    {

    }

    private void OnDestroy()
    {
        if (coroutine != null)
            StopCoroutine(coroutine);
    }
}

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

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

相关文章

【OpenCV 例程 300篇】256. 特征检测之 CenSurE(StarDetector)算法

『youcans 的 OpenCV 例程300篇 - 总目录』 【youcans 的 OpenCV 例程 300篇】256. 特征检测之 CenSurE&#xff08;StarDetector&#xff09;算法 6.9.1 算法简介 中心环绕算法&#xff08;Center Surround Extremas, CenSurE&#xff09;是 Agrawal M 等于 2008年提出的关键…

K8S StatefulSet基本使用

K8S StatefulSet 清空K8S对象 为了避免之前学习的内容造成的影响&#xff0c;先手动把K8S集群中的所有对象清空&#xff0c;使用一个全新的环境来学习StatefulSet的基本使用。 查看对象 查看service对象 kubectl get services查看ReplicaSet对象 kubectl get rs查看Repli…

达梦数据库导入dmp文件

找到达梦数据库安装文件的bin目录按着Shift键&#xff0c;右键输入以下命令&#xff08;注意更改参数&#xff09;.\dimp DGYH(用户名)/DGYH(密码)127.0.0.1 FILEdmp所在文件夹路径\20230103.dmp fullY然后根据提示,写Y 或 N 回车即可注意&#xff1a;若导入成功&#xff0c;但…

Java9的新特性模块化(Module)

一、 模块化是什么&#xff1f; Java 9引入了模块化系统&#xff0c;称为"Java Platform Module System"&#xff08;JPMS&#xff09; 这个系统允许将Java程序分成模块&#xff0c;每个模块都有自己的规范&#xff0c;可以明确地声明它依赖于哪些其他模块&#xff…

小波分析在电力系统暂态信号处理中的应用

前面我们主要讲了小波分析在机械振动信号或者其他时间序列中的应用 基于小波包特征提取和随机森林的CWRU轴承数据集故障识别 - 哥廷根数学学派的文章 - 知乎 https://zhuanlan.zhihu.com/p/556172942 基于小波区间相关&#xff08;Interval-Dependent&#xff09;的信号降噪方…

nacos源码分析-心跳检测(服务端)

前言 前面我们讲了《nacos源码分析-服务注册(客户端)》 和 《nacos源码分析-服务注册(服务端)》&#xff0c;主要是讲的服务注册流程&#xff0c;本章节我们来讲服务心跳检测机制。 心跳续约客户端 其实我们在讲 nacos服务注册客户端的时候顺带就说了心跳&#xff0c;服务注…

iNav飞控AOCODARC-F7MINI固件编译

iNav飞控AOCODARC-F7MINI固件编译1. 编译目标&#xff08;AOCODARC-F7MINI&#xff09;2. 编译步骤Step 1 软件配置环境准备Step 2 获取开源代码Step 3 构建命令介绍Step 4 厂家目标板查询Step 5 目标固件编译Step 6 目标固件清理3. 参考资料iNav是一款非常出色的飞控航模开源软…

怎么恢复360删除的文件?360文件恢复,快速完成

日常生活和工作中&#xff0c;使用电脑总会保存着很多数据。其中有我们很多的文件&#xff0c;如果不小心删除了重要的文件&#xff0c;我们该怎么恢复呢&#xff1f; 很多人都喜欢在电脑上安装3 60安 全卫士&#xff0c;文件被误删&#xff0c;我们可以通过它来恢复数据。文件…

来看看我在CSDN上的好朋友们吧,看看有没有你

首先&#xff0c;感谢支持我的所有人&#xff0c;其次&#xff0c;感谢支持我的所有人&#xff0c;然后感谢支持我的所有人&#xff0c;最后&#xff0c;感谢支持我的所有人&#xff08;我这是废话吗&#xff1f;&#xff1f;&#xff1f;不是吧&#xff09; 今天就来看看我在…

基于transfomer架构的模型[GPT、BERT、VIT、ST、MAE等等]总结

Transformer首先我们来回顾一下Transformer模型架构图对于Transformer从宏观角度可以可以理解为6个Encoder6个Decoder组成各部分介绍输入部分主要就是词嵌入位置编码对于词嵌入比较简单&#xff0c;就是对一个句子里的每个词做一个嵌入操作映射到相应的维度。一般来说就是先把句…

一次非典型的Netty内存泄露案例复盘

背景 作为后端开发相信大家或多或少都接触过Nettty&#xff0c;说起Netty真实又爱又恨&#xff0c;因为基于它可以很简单的开发高性能的Java网络通信服务&#xff0c;但同时要是不小心就会出现各种奇奇怪怪的问题&#xff0c;特别是由于特殊的内存管理机制很容易出现内存泄漏问…

数据大佬的成长经验分享 | ​我的非典型数据分析之路

小飞象交流会哪有什么错过的人&#xff0c;会离开的都是路人。哪有什么命运不公&#xff0c;都是懒惰让你变得无能。内部交流│19期数据大佬的成长经验分享我的非典型数据分析之路data analysis●●●●分享人&#xff1a;夏宇‍在大数据、人工智能热、5G、物联网的时代&#x…

1、Mavan项目管理工具

1.1 什么是 Maven 1.1.1 什么是 Maven Maven 的正确发音是[ˈmevən]&#xff0c;而不是“马瘟”以及其他什么瘟。Maven 在美国是一个口语化的词 语&#xff0c;代表专家、内行的意思。 一个对 Maven 比较正式的定义是这么说的&#xff1a;Maven 是一个项目管理工具&#xff0…

Spring Boot学习篇(十)

Spring Boot学习篇(十) shiro安全框架使用篇(二)——登录实例(密码以密文方式存储,不含记住密码) 1.模拟注册时,生成密文到数据库中 1.1 在zlz包下创建util包,并在下面创建SHAUtil01类(初始里面无方法)和SHAUtil02类,其目录结构如下所示 1.2 两种生成密文的方式 1.2.1 自己…

一篇文章彻底搞懂折半查找法[二分查找法]算法~

算法实现的要求&#xff1a; 折半查找法又称为二分查找法&#xff0c;这种方法对待查找的列表有两个要求&#xff1a; 1&#xff1a;必须采用顺序存储结构 2&#xff1a;必须按关键字大小有序排列算法思想&#xff1a; 将表中间位置记录的关键字与查找关键字进行比较&#x…

性能测试时那些「难以启齿」的问题-CPU相关

NO.1 为什么cpu使用率可以>100%? 小白的我在进行压测的时候&#xff0c;查看服务的cpu总使用率如下&#xff0c;总使用率会超过100%&#xff0c;这个数据是怎么来的呢&#xff0c;为什么会有大于100%的情况呢&#xff1f; 作为小白的我刚开始觉得这个问题应该很基础&#x…

Go语言实现猜数字小游戏

目录 前言 一、设计思路 二、代码编写 2.1 产生随机数 2.2 用户输入数据 2.3 核心代码 三、 全部代码 四、效果图 总结 前言 最近在学习go语言&#xff0c;刚刚学完go语言的基础语法。编写了一个猜数字的小游戏来练习循环、分支语句、变量定义、输入输出等基础的go语…

4、变量与常量

目录 一、标识符和关键字 1.标识符 2.关键字 二、声明变量 三、声明常量 四、变量的有效范围 1. 成员变量 2. 局部变量 一、标识符和关键字 1.标识符 Java语言规定标识符由任意顺序的字母、下画线&#xff08;_&#xff09;、美元符号&#xff08;$&#xff09;和数字…

【数据结构】手撕八大排序算法

作者&#xff1a;一个喜欢猫咪的的程序员 专栏&#xff1a;《数据结构》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 1.排序的概念&#xff1a; 2.八大排序的思路及其细节 2.1直接插入排序 …

适合编程初学者的开源项目:小游戏2048(安卓Compose版)

目标 为编程初学者打造入门学习项目&#xff0c;使用各种主流编程语言来实现。 2048游戏规则 一共16个单元格&#xff0c;初始时由2或者4构成。 1、手指向一个方向滑动&#xff0c;所有格子会向那个方向运动。 2、相同数字的两个格子&#xff0c;相遇时数字会相加。 3、每次…