unity中使用fmod音频插件3

本博关于2D声音和3D声音的播放问题。
1、fmod中区分2D声音和3D声音。在fmod中如果音频有:
在这里插入图片描述

这个音频是3D的,如果没有则是2D声音。具体到unity中,我们可以通过EventBrowser查看声音的属性:
在这里插入图片描述
查看到。

而如果是3D声音,则是:
在这里插入图片描述

代码中判断方法,参考StudioEventEmitter的Play()方法:

eventDescription.is3D(out is3D);

2、2D、3D声音的区别:
在fmod中的,对应2D声音,即使没有加StudioListener组件,也会听到2D的声音。也不要以为是默认摄像机上的AudioListener听到的,因为我们把场景中所有的unity自己的AudioListener去除掉,依然可以听到fmod的2D声音,答案就是fmod为我们提供默认的耳朵以监听2D声音。

在fmod中的,3D声音,需要添加StudioListener组件,而且必须保证耳朵在声音的有效位置处,才能听到3D声音。有效位置是3D声音的最近和最远距离之间。超过这个范围则听不到3D声音。

在有多个StudioListener情况下,同一个3D声音会不会听到多次,答案是不会。fmod只会取离3D声音最近的耳朵监听3D声音。

声音有没有方向,目前不清楚,但是测试下来,没有。

耳朵有没有方向,答案是有的。我们尝试把播放3D声音,同时,移动StudioListener的位置,发现左右耳朵听到的声音会有变化。同时如果我们把物体旋转一个角度,也会发现左右耳朵听到的声音不同,这也符合实际的物理规律。

3、耳朵到底放在哪里,是角色还是摄像机,待思考。

fmod调试unity卡死的问题:http://www.fmod.org/questions/question/visual-studio-freezes-when-debugging-with-fmod-installed/

4、fmod中的master.string读取,检查所有事件的字符串:
在fmod项目导入unity之后,有一个Master Bank.strings,它包含了所有事件的名字。
这个最简单的方式,是查看:GUIDs,因为其是可读的。

5、在升级fmod之后,对原插件代码进行的修改有哪些?
(1)升级到1.10.09之后,会出现vs调试就崩溃的情况,此时参考:
http://www.fmod.org/questions/question/visual-studio-freezes-when-debugging-with-fmod-installed/
具体的修改方法是:
在RuntimeManager中的Initialize()中,将studioInitFlags的赋值修改如下:

 #if UNITY_EDITOR
                        //FIX FOR CRASHES
                        studioInitFlags = FMOD.Studio.INITFLAGS.SYNCHRONOUS_UPDATE;
            #else
                        //ORIGINAL LINE
                        studioInitFlags = FMOD.Studio.INITFLAGS.NORMAL | FMOD.Studio.INITFLAGS.DEFERRED_CALLBACKS;
            #endif

(2)在苹果包上把应用切到后台,然后再切回前台声音消失,修改RuntimeManager中的
OnApplicationFocus方法,在其后加入OnApplicationPause方法。在其OnApplicationPause方法中加入恢复声音的方法:

void OnApplicationPause(bool pauseStatus)
{
    if (studioSystem.isValid())
    {
        // Strings bank is always loaded
        if (loadedBanks.Count > 1)
            PauseAllEvents(pauseStatus);

        if (pauseStatus)
        {
            lowlevelSystem.mixerSuspend();
        }
        else
        {
            lowlevelSystem.mixerResume();
        }
    }
}

(3)测试在Update中测试是否可以屏蔽经常检测拷贝FMODStudioCache的方法。
(4)事件命名的时候,不要有空格,否则会导致事件找不到。
如事件起名字叫:hello world,最好写成hello_world。

6、Refresh Bank按钮执行了什么操作?
反序列化EventCache,这个类是ScriptableObject的子类。

7、项目移动之后,fmod报错如下:
在这里插入图片描述
报错原因:FMODStudioCache.asset缓存文件,记录原先的拷贝路劲。
解决方法:所以可以将此文件删除,然后fmod会自动重新生成一个。

8、升级fmod遇到的问题,同时存在高低两个版本的dll,导致报错
在这里插入图片描述
解决方法:关闭unity,删除低版本的dll,注意只有关闭unity才能将其之前版本的dll删除掉,因为开启unity,dll处于占用状态无法删除。

9、fmod中音频找不到,引起手机包崩溃
原因是在RuntimeManager中的:

public static FMOD.Studio.EventDescription GetEventDescription(string path)
{
    //try
    //{
        return GetEventDescription(PathToGUID(path));
    //}
    //catch (EventNotFoundException)
    //{
    //    throw new EventNotFoundException(path);
    //}
}

public static FMOD.Studio.EventDescription GetEventDescription(Guid guid)
{
    FMOD.Studio.EventDescription eventDesc;
    if (Instance.cachedDescriptions.ContainsKey(guid) && Instance.cachedDescriptions[guid].isValid())
    {
        eventDesc = Instance.cachedDescriptions[guid];
    }
    else
    {
        var result = Instance.studioSystem.getEventByID(guid, out eventDesc);

        if (result != FMOD.RESULT.OK)
        {
            Debug.LogError("GetEventDescription [FMOD] Event not found guid=" + guid + " result=" + result);
            //throw new EventNotFoundException(guid);
        }

        if (eventDesc.isValid())
        {
            Instance.cachedDescriptions[guid] = eventDesc;
        }
        else
        {
            Debug.LogError("eventDesc is not valid guid=" + guid);
        }
    }
    return eventDesc;
}

在找不到音频事件的时候抛出了异常,外面又没有补货异常,所以崩溃了。
解决的方法是改为Debug.LogError,输出一个log即可,达到了提示,音频缺失就缺失,反正要能保证游戏继续运行下去。

10、队友声音播放听不到的问题
原因是在组件中,对fmod没有正确的初始化:

 public void OnEntityLoaded(XGameObject go)
 {
      Transform t = go.Find("");
      if (t != null/* && _emitter != null*/)
      {
          cachedGo = t.gameObject;
      }
  }

这里是判断物体是否加载完毕了,如果加载完毕了,缓存实体,但是不应该判断_emitter为null,才去缓存。
这样导致fmod声音依赖实体没有初始化,所以声音是播放了,但是在要给不确定的位置。
而队友的声音一定是一个3D的声音,对于3D的声音要设置正确位置才能听得到。

11、音频的切换

在遇到boss的时候,要切换背景音乐。如果采用的是两个音频叠加成一个背景音的情况下。
比如一个场景的背景音为A+B,两个音乐组成。
正常情况下播放的是A,而遇到boss的时候切换成B。
那么则需要通过参数设置的形式:

SetBGMParam("Boss", 1);

而如果此时fmod的状态不是有效的,也就是invalid的,那么这个设置是无效的。
所以一定要保证设置参数的时候,fmod是valid的,如果不是,则需要缓存参数,等到fmod为valid的状态,再重新设置参数。
如何判断fmod是否valid,则需要通过如下的代码:

public bool IsValid(AudioChannel channel)
 {
     FMODUnity.StudioEventEmitter e = GetEmitter(channel);
     if (e != null)
     {
         return e.EventInstance.isValid();
     }
     return false;
 }

这样就可获得fmod的状态了。

12、出现[FMOD] OutputWASAPI::mixerThread : Starvation detected in WASAPI output buffer!
报错。
尝试解决方法:
https://gameinstitute.qq.com/community/detail/103142
https://blog.csdn.net/boluns/article/details/52451999

在RuntimeManager中的FMOD.RESULT Initialize()方法中添加:

            result = FMOD.Studio.System.create(out studioSystem);
            CheckInitResult(result, "FMOD.Studio.System.create");

            result = studioSystem.getCoreSystem(out coreSystem);
            CheckInitResult(result, "FMOD.Studio.System.getCoreSystem");

            // 默认是1024的buffer,开大一点,尝试解决因为buffer导致崩溃的问题
#if UNITY_EDITOR || UNITY_STANDALONE
            CoreSystem.setDSPBufferSize(2048, 4);
#endif

其中的CoreSystem.setDSPBufferSize(2048, 4);是将缓冲开到2048。
这个只是尝试解决方法,不知道可行不。

这个问题的参考网址:https://qa.fmod.com/search?q=OutputWASAPI%3A%3AmixerThread%20%3A%20Starvation%20detected%20in%20WASAPI%20output%20buffer!%20category%3A7

13、fmod的设置参考网址
https://fmod.com/resources/documentation-unity?version=2.0&page=settings.html#logging-level

14、今天又遇到一个问题,就是停背景音停不了的问题。
问题描述:
进入场景A,使用频道action,播放背景音bg1
切换到战斗场景B,使用频道action,播放背景音bg2
战斗胜利,需要播放两段音乐musicA,musicB
原先做法是:
使用同一个频道action播放musicA,以打断的方式,也就是此时打断了战斗场景2的背景音bg2,然后播放了musicA,这步是正确的。
然后再以非打断的方式播放musicB,此时你能听到两个声音musicA和musicB,这是正确的。

但是,但是,当我们切换到场景A的时候,应该播放bg1,但是此时我们还听到musicA的声音。这是为啥呢?

原因剖析:
每次load场景之后,都会调用这样的接口:

public void PlayBGM(string bgm, AudioChannel channel = AudioChannel.Action)
{
    if (!hasSound)
        return;
    if (string.IsNullOrEmpty(bgm)) return;

    if (m_bgmFmod == null)
    {
        m_bgmFmod = XAudioMgr.singleton.FModBus.GetRuntimeFMOD() as IXFmod;
    }
    m_bgmFmod.StartEvent("event:/" + bgm, channel, true); //这里是打断的方式
}

所以当播放musicA的时候,action被musicA占用,而播放musicB的时候,以非打断的方式播放,那么action频道此时被musicA和musicB公共用,但是此时的句柄已经指向了musicB时候的句柄了。musicA那个instance则不受控制,直到自己消亡。
所以当你切回到场景A的时候,虽然以打断的方式,使用action频道,但是此时只能停止的(假设musicB还在播放)是musicB。
所以当然你能够听到musicA(因为它没有人能够停止掉)。

解决方法:
使用action频道,以打断的方式播放musicA,但是使用另外一个频道播放musicB。由于musicB声音很短,所以播放完自己停止了。不用关心,而回到场景A的时候,bg1以打断的方式使用action频道,此时能够只听到bg1声音,因为同为action频道的musicA被打断了。而另外一个频道的musicB由于时长太短(路飞的一个胜利语气而已,在结算界面出来之后一会就完了,所以不用关心)。这样就解决了。

那么fmod代码中的关于时刻保持一个最新频道的引用的代码在这里:

if (!instance.isValid()) //假设当前频道的实例不可用,那么则直接回收句柄
{ 
    instance.clearHandle();
}

// Let previous oneshot instances play out
if (instance.isValid()) //假设当前频道的实例可用,那么同样进行释放和回收,只时多了一个release()方法。
{
    instance.release();
    instance.clearHandle();
}

if (!instance.isValid()) //那么此时当前频道的实例绝对是不用了,所以重新创建了一个实例,这也就是证明了,如果以非打断的方式播放同一个频道的话,那么过一会你在想停止前面的声音,则是不可控制的。
{
    eventDescription.createInstance(out instance);

同一个频道使用的例子:
播放A——》非打断播放B
此时你想停止A,那么是不可能的了,最多也就停止了B,因为A的句柄已经回收了。你拿不到,此时只能任由其死亡。
而这种方式,最好的应用场景是,UI的点击,都是非打断的方式播放。
其他的都应该以打断的方式播放。

15、人物跑步声音的添加
总的思路是:制作短暂的跑步的音频,以定时器的方式,每隔一段时间播放一次。

private XTimerMgr.ElapsedEventHandler m_playMoveSoundHandler = new XTimerMgr.ElapsedEventHandler(PlayMoveSound);

private void PlayMoveSound(object obj)
{
    //XAudioMgr.singleton.PlaySound(XEntityMgr.singleton.CombatPlayer, AudioChannel.Motion, "Tone/Role/Luffy/Select"); //->测试
    XAudioMgr.singleton.PlaySound(XEntityMgr.singleton.CombatPlayer, AudioChannel.Motion, XAudioStateDefine.XState_Audio_Move);
    float interval = GetStepInvalTime(XEntityMgr.singleton.CombatPlayer);
    m_moveTrack1 = XTimerMgr.singleton.SetTimer(interval, m_playMoveSoundHandler, null);
    XDebug.singleton.AddGreenLog("play move sound interval=" + interval);
}

循环播放,那么什么时候停止播放呢?

public void OnStateChange(ulong id,int state)
{
    XStateType eStage = (XStateType)(state);
    switch (eStage)
    {
        case XStateType.Move: 
            if (XEntityMgr.singleton.CombatPlayer.ID == id)
            {
                float inval = GetStepInvalTime(XEntityMgr.singleton.CombatPlayer);
                if (m_moveTrack1 == 0) m_moveTrack1 = XTimerMgr.singleton.SetTimer(inval * 0.5f, m_playMoveSoundHandler, null);
            }
            break;
        default:
            if (XEntityMgr.singleton.CombatPlayer.ID == id)
            {
                if (m_moveTrack1 != 0) XTimerMgr.singleton.KillTimer(m_moveTrack1);
                m_moveTrack1 = 0;
                XDebug.singleton.AddGreenLog("stop move sound");
            }
            break;
    }
}

可以看到再切换到idle的时候,就kill掉定时器,然后就不会再循环播放跑步声音了。

16、今天遇到一个bug,是停不了声音的问题。

public override void OnGraphStart (Playable playable)
{
	if(_runtimeFmod == null) //如果不判断空,那么此时会重新申请一个fmod,那么前面的fmod会丢失,导致不能停止声音。
    {
    	_runtimeFmod = XRuntimeFmod.GetFMOD();
    }
 }

17、遇到timeline中,重复播放声音的问题。
当函数OnBehaviourPlay反复进入的时候时候,会导致声音播放。这里加了个保护,就是讲播放的音频,如果
已经播放了 ,则不再播放, fpa.isExecuted = true; 设置为已经播放。

public override void OnBehaviourPlay(Playable playable, FrameData info)
{
    
        var fpa = curveAsset;
        if (_runtimeFmod != null && fpa != null && !fpa.isExecuted)
        {
            fpa.isExecuted = true;
            _runtimeFmod.StartEvent (fpa.clip, AudioChannel.Action, true);
            RTimeline.singleton.RegistFmod(fpa.clip, _runtimeFmod);
        }
    }
}

18、今天遇到一个问题,就是在timeline里面,有一个qte的按钮,如果点击了qte按钮,则瞬间切换到第二段音乐。
但是此时和最后的动作声音不匹配了。
最终的原因是,跳转的时候,跳转到了到switch=2的下一个时间点了,导致动作被截断了一部分,所以导致,动作播放完毕,声音还在的情况。
解决的方法是,切换声音的时候,跳转的时间点要一致才行。

在这里插入图片描述
这里从第一个signal,直接跳转到了后面的那个紫色的箭头了。此时音乐是切换了,但是动作则是被阶段了。所以要第二个signal和紫色箭头的跳转时间,重叠即可。

19、速度和音量大小的控制
控制速度的接口:
m_emitter.EventInstance.setPitch(0.1f);
控制音量的接口:
FMOD.RESULT x = m_emitter.EventInstance.setVolume(0.2f);

20、控制event不要刷新:
const int FilePollTimeSeconds = 5; //fmod默认是每隔5秒就刷新,所以会导致git不能拉取远程的故,可以设置一个变量,在编辑器下控制这个变量。让其不要自动刷新。

在这里插入图片描述

21、不运行unity,在编辑器下,预览fmod的声音
这个使用StudioEventEmitter组件,直接在获取使其播放。
如下:

[ExecuteInEditMode]
public class NewBehaviourScript : MonoBehaviour
{
    public StudioEventEmitter emitter;

    private void OnEnable()
    {
        emitter.Play();
    }

    private void OnDisable()
    {
        emitter.Stop();
    }
}

这个这个在不运行unity的时候,隐藏和显示物体,会触发播放和停止。
但是不会正确播放声音,还需要一个update的方法。
在这里插入图片描述
这个脚本挂在一个物体上,使其一直执行Update方法,让其调用:FMODUnity.RuntimeManager.StudioSystem.update()方法,这样才能在非运行的状态下,播放和停止声音。
可以参考:fmod提供的那个timeline的track方法:
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值