Overlapping Dialogue Interactions

Using Unity 2021.3.20f1 and FMOD 2.02.13. I have several mini dialogue events that correspond with player interactions in my game (eg, player picks up a key and says, “Found it!”) The problem I’m running into is that if multiple interactions happen within a short period of each other, there is a bit of overlap in the dialogue, which obviously sounds unnatural. I’ve already added a 1-2 second delay to most of the events anyway, since there are UI sound effects involved with most of the interactions that I didn’t want the dialogue clashing with, and that has minimized this problem a tad, but not completely. Is there a way to tell FMOD via script to check if a line of dialogue is currently playing and not to start the next line until the current one has stopped? (PS - I’m very new to scripting and game sound design in general, so the more specific details the better, lol).

Hi,
I had the same issue and build a component to handle this. What it does is that it keeps track of the last event it plays and it gives you the means to define what should happen when you start another one while it is still playing. I call that the concurrency strategy.
It can abort the current one, ignore the new one, wait until the current one is finished, or just play anyway.

In order to use this, you would have to have one object with this script attached and other scripts could call the Play method and pass an EventReference as well as the concurrency strategy.

Feel free to use and modify this in any way you like.

using System.Collections;
using FMOD.Studio;
using FMODUnity;
using UnityEngine;

public enum SFXConcurrencyStrategy
{
    Cancel,
    Wait,
    AbortCurrent,
    PlayAnyway
}

public class NarratorController : MonoBehaviour
{
    private EventInstance _instance;

    public bool IsPlaying
    {
        get
        {
            if (!_instance.isValid()) return false;
            _instance.getPlaybackState(out PLAYBACK_STATE state);
            return state == PLAYBACK_STATE.PLAYING || state == PLAYBACK_STATE.STARTING;
        }
    }

    public void Play(EventReference eventReference,
        SFXConcurrencyStrategy sfxConcurrencyStrategy = SFXConcurrencyStrategy.Cancel)
    {
        if (eventReference.IsNull)
        {
            return;
        }

        if (!IsPlaying)
        {
            PlayNow(eventReference);
        }
        else
        {
            switch (sfxConcurrencyStrategy)
            {
                case SFXConcurrencyStrategy.Cancel:
                    _instance.getDescription(out EventDescription eventDescription);
                    eventDescription.getPath(out string currentEventPath);
                    Debug.LogWarning(
                        $"Will not play narrator comment {eventReference}, because {currentEventPath} is already playing");
                    break;
                case SFXConcurrencyStrategy.Wait:
                    StartCoroutine(PlayAfterCurrent(eventReference));
                    break;
                case SFXConcurrencyStrategy.AbortCurrent:
                    _instance.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT);
                    PlayNow(eventReference);
                    break;
                case SFXConcurrencyStrategy.PlayAnyway:
                    PlayNow(eventReference);
                    break;
            }
        }
    }

    private void PlayNow(EventReference eventReference)
    {
        _instance = RuntimeManager.CreateInstance(eventReference);
        _instance.start();
        _instance.release();
    }

    private IEnumerator PlayAfterCurrent(EventReference eventReference)
    {
        while (enabled)
        {
            _instance.getPlaybackState(out PLAYBACK_STATE state);
            if (state == PLAYBACK_STATE.PLAYING || state == PLAYBACK_STATE.STARTING) yield return null;
            else break;
        }

        PlayNow(eventReference);
    }
}
1 Like

Awesome, thanks!!!

Mmkay, so I have the basic idea down, but the implementation I’m a bit hazy on. As suggested, I have an empty game object (NarratorController) which I’ve attached the script to. Then in my CollectObjects script I call on the IsPlaying bool with:
NarratorController narratorController = NarratorControllerOB.GetComponent();
IsPlaying = narratorController.IsPlaying;

After that, is where I’m currently unsure of what to do next.

Do I need to add the:
public enum SFXConcurrencyStrategy
{
Cancel,
Wait,
AbortCurrent,
PlayAnyway
}
to the start of my CollectObjects script, or is that unnecessary?

And not sure how to proceed with assigning whether to Cancel, Wait, AbortCurrent, or PlayAnyway within the CollectObjects script.

For example, here are two coroutines that I currently have set up to play two interaction dialogues (one for when he picks up a toolbelt, and another for picking up a tape player).

IEnumerator ToolBeltDialogue()
{
yield return new WaitForSeconds(1);
FMODUnity.RuntimeManager.PlayOneShot(“event:/Dialogue/Chapter1/Collections/CollectedToolBelt”);
}

    IEnumerator TapePlayerDialogue()
    {
        yield return new WaitForSeconds(1);
        FMODUnity.RuntimeManager.PlayOneShot("event:/Dialogue/Chapter1/Collections/CollectedTapePlayer");
    }

If I understand correctly, I would want to add an if(IsPlaying) and an if(!IsPlaying) to each coroutine, and within the if(IsPlaying) I would call on the Wait function of the NarratorController script, and in the if(!IsPlaying) I would just play the event (and I presume I would need to toggle the !IsPlaying bool to true - and setup another coroutine to toggle it back to false after the clip is done). Just not sure how to call upon the Wait function from within the CollectObjects script.

You are trying to implement all the things that my script is supposed to do for you :slight_smile:

Your script needs two public fields. One that references the NarratorController and one of type EventReference.

Then instead of doing RuntimeManager.PlayOneShot you call NarratorController.Play()
As the first argument you pass your event reference and as the second one you can pass the concurrency strategy.

Hope that’ll help :slight_smile:

1 Like

Sorry - like I said, total newb, lol! (I started teaching myself all of this in February, so they don’t come much greener than me, lol!)

So at the start of the class, I would put something along the lines of:

public GameObject ConcurrencyManagerOB;
public FMOD.Studio.EventInstance dialogueCues;

Then in the Coroutine, using the example I included above, I’d put:

yield return new WaitForSeconds(1);
NarratorController.Play();
            {
                FMODUnity.RuntimeManager.PlayOneShot("event:/Dialogue/Chapter1/Collections/CollectedToolBelt");
                Wait;
            }

I’m getting errors on the last part, so I gather there’s something I’m missing, lol.

Not quite. You don’t call PlayOneShot yourself. That happens in the narrator controller.
Your class would look something like this

// assign these fields in the inspector
public EventReference dialogueCue;
public NarratorController narratorController;

private IEnumerator PlaySoundDelayedCoroutine()
{
  // wait for one second
  yield return new WaitForSeconds(1);

  // tell the narrator controller to play the event as soon as whatever it is currently playing has finished
  narratorController.Play(dialogueCue, SFXConcurrencyStrategy.Wait);
}

It’ll take time to get used to coding stuff like this. Keep up your efforts! :slight_smile:

1 Like

Got it! Cheers, that’s a really useful tool to have on hand!

Follow up question: if I need to pause one of these event references (e.g., if the player activates the pause menu in the middle of the audio clip), how would you go about that? For most other events using EventInstance, I use the code:
“if (pauseMenuOn) narrationInstance.setPaused(true);”

but that doesn’t seem to work on EventReference. Is there a similar code to use for EventInstance? If not, I guess it’s not that critical if these short dialogue cues bleed over into the pause menu for a few seconds, so I can let my OCD live with it, lol, but if there is an easy fix, I’d like to employ it.

EventInstance does have a setPaused() function.
General (somewhat patronizing) advice: you will need a code editor that offers you code completion so you can quickly find what methods an object offers. Also you will have to get used to reading documentation. You won’t be able to get answers through forums for these issues.

But also, pausing short dialogue cues might not be something you actually want to do. Might be better to just let them finish.