声音的要素

1: 音频文件AudioClip
2: 音源AudioSource;
3: 耳朵AudioListener;//全局只能有一个
4: 2D/3D音频;//2D只是简单地播放声音,3D可以根据距离衰减音量

怎样听到声音:

创建一个节点,挂载AudioSource组件,AudioSource组件关联AudioClip属性,设置声音是否一开始就加载播放play on awake,是否循环播放,2D还是3D

场景中有一个节点有AudioListener组件,挂载AudioListener组件的节点在挂载AudioSource组件节点的声音范围内。运行,可以听到声音。

音乐与音效管理

1: 游戏里面一般分为音乐与音效;
2: 音乐指的是游戏的背景音乐;
3: 音效是游戏中的较短的音乐,来配合游戏的动作等;
4: Unity游戏没有音乐和音效之分,都是AudioSource + AudioClip;
5: 2D音效没有衰减,所以与位置没有关系;
6: 3D音效需要有声音所在节点的位置;
7: 游戏需求需要音乐和音效分开设置,开启和关闭;

sound_manager脚本

首先,声音包括音乐和音效,我们分两大块来管理,音乐为一块,音效为一块,这个块实际上Unity内部是不分的,但是我们来分,

用字典数据类型保存URL和音源AudioSource之间的对应关系,两个表,一个是音乐的,一个是音效的。

有了这两个表,这样如果我们要禁止背景音乐就去遍历音乐表,如果我们要禁止音效就去遍历音效表。

播放一个音效,需要new一个物体,在这个物体上加音源AudioSource组件,关联好音频文件AudioClip,这样就会播放出一个音效,

播放出来的音效会有分2D和3D,2D随便放在哪里,3D需要管理,有一个位置,所以加一个播放3D音效的接口。

1: 全局唯一的sound_manager;
2: 在场景里面创建一个物体(做为声音的根节点),而且设置这个物体场景切换也不会删除;
3: 编写接口播放背景音乐play_music;
4: 编写接口播放背景音效play_effect; // 2D声音
5: 音乐和音效内部实现都是一样的, 只不过要把url分组管理, 音效为一组,音乐为一组;
6: 提供开关音效接口set_mute(),并将值写入本地,下一次打开游戏的时候会读取这个值,维持上一次的设置。
7: 提供开关背景音乐接口switch_music(),并将值写入本地,下一次打开游戏的时候会读取这个值,维持上一次的设置。
8: 添加一个play_effect接口在指定的坐标出访一个声音, 可以用于3D音效;
9: 添加一个接口停止掉背景音乐stop_music(url);
10: 添加一个接口删除掉播放的背景音乐clear_music(url), clear_effect(url);
11: 声音文件来自Resouce还是assetBundle,可以通过资源管理来封装,目前从Resource里面加载;
12: 加一个脚本,每隔0.5秒扫描一次已经播放完的声音组件,将它disable;

声音管理实例

1.创建Unity项目工程和文件目录,保存场景

2.在Resources文件夹下创建一个sounds文件夹,把背景音乐Login.mp3和音效Close.mp3(第70)放进去

3.为了统一管理游戏中的声音,写一个脚本sound_manager来管理

 打开sound_manager.cs,初始化sound_manager

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class sound_manager {
// (1) 声音根节点的物体;
// (2) 保证这个节点在场景切换的时候不会删除,这样就不用再初始化一次;
// (3) 所有播放声音的生源节点,都是在这个节点下
static GameObject sound_play_object;//这个就是根节点 // url --> AudioSource 映射, 区分音乐,音效
static Dictionary<string, AudioSource> musics=null;//音乐表
static Dictionary<string, AudioSource> effects = null;//音效表 public static void init()
{
sound_play_object = new GameObject("sound_play_object");//初始化根节点
GameObject.DontDestroyOnLoad(sound_play_object);//场景切换的时候不会删除根节点 //初始化音乐表和音效表
musics = new Dictionary<string, AudioSource>();
effects = new Dictionary<string, AudioSource>();
}
}

4.创建一个空节点,下面挂载一个脚本game_scene来测试音乐和音效

 打开game_scene.cs,写一个语句测试初始化

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class game_scene : MonoBehaviour { // Use this for initialization
void Start () {
sound_manager.init();//初始化音乐音效管理
} // Update is called once per frame
void Update () { }
}

5.运行,初始化,节点如下

6.接下来开始写背景音乐和音效的接口

 打开sound_manager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class sound_manager {
// (1) 声音根节点的物体;
// (2) 保证这个节点在场景切换的时候不会删除,这样就不用再初始化一次;
// (3) 所有播放声音的生源节点,都是在这个节点下
static GameObject sound_play_object;//这个就是根节点
static bool is_music_mute = false;//存放当前全局背景音乐是否静音的变量
static bool is_effect_mute = false;//存放当前音效是否静音的变量 // url --> AudioSource 映射, 区分音乐,音效
static Dictionary<string, AudioSource> musics=null;//音乐表
static Dictionary<string, AudioSource> effects = null;//音效表 //初始化
public static void init()
{
sound_play_object = new GameObject("sound_play_object");//初始化根节点
sound_play_object.AddComponent<sound_scan>();//把声音检测组件挂载到根节点下
GameObject.DontDestroyOnLoad(sound_play_object);//场景切换的时候不会删除根节点 //初始化音乐表和音效表
musics = new Dictionary<string, AudioSource>();
effects = new Dictionary<string, AudioSource>(); // 从本地来加载这个开关
if (PlayerPrefs.HasKey("music_mute"))//判断is_music_mute有没有保存在本地
{
int value = PlayerPrefs.GetInt("music_mute");
is_music_mute = (value == );//int转换bool,如果value==1,返回true,否则就是false
} // 从本地来加载这个开关
if (PlayerPrefs.HasKey("effect_mute"))//判断is_effect_mute有没有保存在本地
{
int value = PlayerPrefs.GetInt("effect_mute");
is_effect_mute = (value == );//int转换bool,如果value==1,返回true,否则就是false
}
} //播放指定背景音乐的接口
public static void play_music(string url, bool is_loop = true)
{
AudioSource audio_source = null;
if (musics.ContainsKey(url))//判断是否已经在背景音乐表里面了
{
audio_source = musics[url];//是就直接赋值过去
}
else//不是就新建一个空节点,节点下再新建一个AudioSource组件
{
GameObject s = new GameObject(url);//创建一个空节点
s.transform.parent = sound_play_object.transform;//加入节点到场景中 audio_source = s.AddComponent<AudioSource>();//空节点添加组件AudioSource
AudioClip clip = Resources.Load<AudioClip>(url);//代码加载一个AudioClip资源文件
audio_source.clip = clip;//设置组件的clip属性为clip
audio_source.loop = is_loop;//设置组件循环播放
audio_source.playOnAwake = true;//再次唤醒时播放声音
audio_source.spatialBlend = 0.0f;//设置为2D声音 musics.Add(url, audio_source);//加入到背景音乐字典中,下次就可以直接赋值了
}
audio_source.mute = is_music_mute;
audio_source.enabled = true;
audio_source.Play();//开始播放
} //停止播放指定背景音乐的接口
public static void stop_music(string url)
{
AudioSource audio_source = null;
if (!musics.ContainsKey(url))//判断是否已经在背景音乐表里面了
{
return;//没有这个背景音乐就直接返回
}
audio_source = musics[url];//有就把audio_source直接赋值过去
audio_source.Stop();//停止播放
} //停止播放所有背景音乐的接口
public static void stop_all_music()
{
foreach (AudioSource s in musics.Values)
{
s.Stop();
}
} //删除指定背景音乐和它的节点
public static void clear_music(string url)
{
AudioSource audio_source = null;
if (!musics.ContainsKey(url))//判断是否已经在背景音乐表里面了
{
return;//没有这个背景音乐就直接返回
}
audio_source = musics[url];//有就把audio_source直接赋值过去
musics[url] = null;//指定audio_source组件清空
GameObject.Destroy(audio_source.gameObject);//删除掉挂载指定audio_source组件的节点
} //切换背景音乐静音开关
public static void switch_music()
{
// 切换静音和有声音的状态
is_music_mute = !is_music_mute; //把当前是否静音写入本地
int value = (is_music_mute) ? : ;//bool转换int
PlayerPrefs.SetInt("music_mute", value); // 遍历所有背景音乐的AudioSource元素
foreach (AudioSource s in musics.Values)
{
s.mute = is_music_mute;//设置为当前的状态
}
} //当我的界面的静音按钮要显示的时候,到底是显示关闭,还是开始状态;
public static bool music_is_off()
{
return is_music_mute;
} //接下来开始是音效的接口
//播放指定音效的接口
public static void play_effect(string url, bool is_loop = false)
{
AudioSource audio_source = null;
if (effects.ContainsKey(url))//判断是否已经在音效表里面了
{
audio_source = effects[url];//是就直接赋值过去
}
else//不是就新建一个空节点,节点下再新建一个AudioSource组件
{
GameObject s = new GameObject(url);//创建一个空节点
s.transform.parent = sound_play_object.transform;//加入节点到场景中 audio_source = s.AddComponent<AudioSource>();//空节点添加组件AudioSource
AudioClip clip = Resources.Load<AudioClip>(url);//代码加载一个AudioClip资源文件
audio_source.clip = clip;//设置组件的clip属性为clip
audio_source.loop = is_loop;//设置组件循环播放
audio_source.playOnAwake = true;//再次唤醒时播放声音
audio_source.spatialBlend = 0.0f;//设置为2D声音 effects.Add(url, audio_source);//加入到音效字典中,下次就可以直接赋值了
}
audio_source.mute = is_effect_mute;
audio_source.enabled = true;
audio_source.Play();//开始播放
} //停止播放指定音效的接口
public static void stop_effect(string url)
{
AudioSource audio_source = null;
if (!effects.ContainsKey(url))//判断是否已经在音效表里面了
{
return;//没有这个背景音乐就直接返回
}
audio_source = effects[url];//有就把audio_source直接赋值过去
audio_source.Stop();//停止播放
} //停止播放所有音效的接口
public static void stop_all_effect()
{
foreach (AudioSource s in effects.Values)
{
s.Stop();
}
} //删除指定音效和它的节点
public static void clear_effect(string url)
{
AudioSource audio_source = null;
if (!effects.ContainsKey(url))//判断是否已经在音效表里面了
{
return;//没有这个音效就直接返回
}
audio_source = effects[url];//有就把audio_source直接赋值过去
effects[url] = null;//指定audio_source组件清空
GameObject.Destroy(audio_source.gameObject);//删除掉挂载指定audio_source组件的节点
} //切换音效静音开关
public static void switch_effect()
{
// 切换静音和有声音的状态
is_effect_mute = !is_effect_mute; //把当前是否静音写入本地
int value = (is_effect_mute) ? : ;//bool转换int
PlayerPrefs.SetInt("effect_mute", value); // 遍历所有音效的AudioSource元素
foreach (AudioSource s in effects.Values)
{
s.mute = is_effect_mute;//设置为当前的状态
}
} //当我的界面的静音按钮要显示的时候,到底是显示关闭,还是开始状态;
public static bool effect_is_off()
{
return is_effect_mute;
} //播放3D的音效
public static void play_effect3D(string url, Vector3 pos, bool is_loop = false)
{
AudioSource audio_source = null;
if (effects.ContainsKey(url))
{
audio_source = effects[url];
}
else
{
GameObject s = new GameObject(url);
s.transform.parent = sound_play_object.transform;
s.transform.position = pos;//3D音效的位置 audio_source = s.AddComponent<AudioSource>();
AudioClip clip = Resources.Load<AudioClip>(url);
audio_source.clip = clip;
audio_source.loop = is_loop;
audio_source.playOnAwake = true;
audio_source.spatialBlend = 1.0f; // 3D音效 effects.Add(url, audio_source);
}
audio_source.mute = is_effect_mute;
audio_source.enabled = true;
audio_source.Play();
} //优化策略接口
public static void disable_over_audio()
{
//遍历背景音乐表
foreach(AudioSource s in musics.Values){
if (!s.isPlaying)//判断是否在播放
{
s.enabled = false;//不在播放就直接隐藏
}
} //遍历音效表
foreach (AudioSource s in effects.Values)
{
if (!s.isPlaying)//判断是否在播放
{
s.enabled = false;//不在播放就直接隐藏
}
}
} }

7.在game_scene脚本里面写测试语句,测试接口是否可以使用

 打开game_scene.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class game_scene : MonoBehaviour { // Use this for initialization
void Start () {
sound_manager.init();//初始化音乐音效管理 //sound_manager.play_music("sounds/Login");//播放背景音乐 //this.InvokeRepeating("test_music_mute", 1, 3); sound_manager.play_effect("sounds/Close");//播放音效
if (sound_manager.effect_is_off())//如果当前是静音,就切换成有声音的状态
{
sound_manager.switch_effect();
}
this.InvokeRepeating("again", , );//每隔3秒调用一次 //this.InvokeRepeating("test_effect_mute", 1, 3);
} //背景音乐静音切换测试函数
void test_music_mute()
{
Debug.Log("test_music_mute");
sound_manager.switch_music();
} //音效静音切换测试函数
void test_effect_mute()
{
Debug.Log("test_effect_mute");
sound_manager.switch_effect();
} //隐藏AudioSource组件优化测试函数
void again()
{
sound_manager.play_effect("sounds/Close");
} // Update is called once per frame
void Update () { }
}

8.还有一个问题就是,在某个声音播放完成的时候,可以隐藏AudioSource组件,这样就不会去调用组件的相关接口,游戏性能就能有所提升,这个接口在第6步已经写好了,测试语句也在第7步显现出来了,现在就写一个扫描的脚本

 可以创建一个脚本sound_scan来检测哪些声音已经播放完成,播完的节点就把它的AudioSource组件隐藏,这个脚本0.5s启动一次去检测声音节点。  

 打开sound_scan.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class sound_scan : MonoBehaviour { // Use this for initialization
void Start () {
//固定一个节奏去扫描,每隔0.5s扫描一次
this.InvokeRepeating("scan",, 0.5f);
} // Update is called once per frame
void Update () { } //定时器函数
void scan()
{
sound_manager.disable_over_audio();//调用隐藏AudioSource组件接口
}
}

9.运行时的节点

播放背景音乐时

播放音效时

注意:

Unity代码加载资源文件的时候是没有文件后缀名的,看到什么名字就是什么名字

关于Unity中的声音管理模块(专题七)的更多相关文章

  1. Unity中实现全局管理类的几种方式

    (搬运自我在SegmentFault的博客) 如何在Unity中实现全局管理类?由于Unity脚本的运行机制和面向组件编程(COP)的思想,实现起来和普通的方式略有差别. 第一种方式是使用静态类.适合 ...

  2. 解读Unity中的CG编写Shader系列七(不透明度与混合)

    转自http://www.itnose.net/detail/6098539.html 1.不透明度 当我们要将两个半透的纹理贴图到一个材质球上的时候就遇到混合的问题,由于前面的知识我们已经知道了片段 ...

  3. 关于Unity中3D声音的使用

    3D声音 3D立体声和2D声不同的地方是它是会随着距离衰减的,距离越近声音越大,距离越远声音越小. 1: 声音: 背景音乐和音效; 2: 声音文件支持的格式: ogg, mp3, wave, AIFF ...

  4. 项目日志的管理和应用 log4js-Node.js中的日志管理模块使用与封装

    开发过程中,日志记录是必不可少的事情,尤其是生产系统中经常无法调试,因此日志就成了重要的调试信息来源. Node.js,已经有现成的开源日志模块,就是log4js,源码地址:点击打开链接 项目引用方法 ...

  5. log4js-Node.js中的日志管理模块使用与封装

    开发过程中,日志记录是不可缺少的事情.尤其是生产系统中常常无法调试,因此日志就成了重要的调试信息来源. Node.js,已经有现成的开源日志模块,就是log4js,源代码地址:点击打开链接 项目引用方 ...

  6. 关于Unity中DOTween插件的使用(专题一)

    DOTween flash里面的一个概念叫补间动画,DOTween就是干这个事情的. 补间动画:在1秒钟之内从A点移动到B点,在这个之间会把动画补间补好. 当我们安装好DOTween后,它就会提供很多 ...

  7. log4js_Node.js中的日志管理模块使用

    { "appenders": [ // 下面一行应该是用于跟express配合输出web请求url日志的 {"type": "console" ...

  8. Unity游戏开发中的内存管理_资料

    内存是手游的硬伤——Unity游戏Mono内存管理及泄漏http://wetest.qq.com/lab/view/135.html 深入浅出再谈Unity内存泄漏http://wetest.qq.c ...

  9. 【转】【Unity】实现全局管理类的几种方式

    本文原作者未知,转载自:http://blog.csdn.net/ycl295644/article/details/42458477 如何在Unity中实现全局管理类?由于Unity脚本的运行机制和 ...

随机推荐

  1. ASP.NET MVC中切换模板页(不同目录的cshtml文件)

    看来以后建立一个父类控制器还是有必要的... using System;using System.Collections.Generic;using System.Linq;using System. ...

  2. struts2(三) 输入校验和拦截器

    前面知道了struts2的架构图和struts2的自动封装表单参数和数据类型自动转换,今天来学struts2的第三第四个东西,输入校验和拦截器, --WH 一.输入校验 在以前我们写一个登录页面时,并 ...

  3. Word实用教程——五分钟教你如何在任意页开始添加页码

    最近在写一篇论文,但是在排版上遇到一点小问题,就是要加入页码,而且页码是从目录的下一页开始计数,于是我就在网上找如何在任意页添加页码.后来辗转终于搞定,真心觉得这一个小功能让微软做的如此的麻烦,真是活 ...

  4. 每日英语:How Often Do Gamblers Really Win?

    The casino billboards lining America's roadways tantalize with the lure of riches. 'Easy Street. It' ...

  5. Java Hex 16进制的 byte String 转换类

    package mobi.dzs.android.util; import java.util.Locale; /** * 16进制值与String/Byte之间的转换 * @author Jerry ...

  6. java版本DbhelperMysql

    package com.hebut.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.P ...

  7. java 多线程 19: ReentrantLock 与 Condition

    ReentrantLock ReentrantLock,一个可重入的互斥锁,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. Reentran ...

  8. 通过python-libvirt管理KVM虚拟机 源码

    版本:0.9.13 libvirt库可真是大,先看看该版本里面都有哪些类和方法,验证过的方法我会用O开头,|开头的标示还没亲自验证过. <span style="font-size:1 ...

  9. Android padding 和margin

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout ...

  10. 04-老马jQuery教程-DOM节点操作及位置和大小

    1. jQuery创建DOM标签 1.1 DOM动态创建标签的方法 DOM时代我们通过document的createElement方法动态创建标签.创建标签后,动态的给他添加属性.例如代码: // 动 ...