as a default sequence, and the entrytag does not exist, what exactly happens? and is this an ideal thing to do if I have some nodes that are there for non-dialogue actions? (see the attached img)
화면 캡처 2022-02-15 173913.png (70.28 KiB) Viewed 1601 times
If you're using FMOD, you can make an audio event for each entrytag value. Then you can use a custom FMOD sequencer command such as the one in this forum post.
In the custom sequencer command, you may want to check if the event exists. Then only call FMOD's RuntimeManager.PlayOneShot() if the event exists.
You can still use "entrytag". Entrytags are not specific to AudioWait(). They just represent a unique string for each dialogue entry. You can use them with any sequencer command.
Here's a rough sketch of an FMODWait() sequencer command. I'm just typing it in here; there may be typos. I'm also omitting using statements for brevity.
That looks fine to me. You remembered to include an OnDestroy() method, which is important in case the player skips ahead early before the VO has finished. You can remove the empty Awake() and Update() methods.
I think I will be using "Table" in FMOD which fits better to my production.
It is for quite large list of VO lines (which in my case it will exceed around 10500 lines).
public class SequencerCommandFMODWait : SequencerCommand
{
EventInstance eventInstance;
PLAYBACK_STATE state;
EventDescription eventCheck;
FMOD.Studio.EVENT_CALLBACK dialogueCallback;
public IEnumerator Start()
{
dialogueCallback = new FMOD.Studio.EVENT_CALLBACK(DialogueEventCallback);
PlayDialogue(key); // key will be the entryTag e.g. [CharacterName]_[SceneID]_[EntryID] e.g "Tifa_6_22" or do "entrytag". -> PlayDialogue(GetParamgeter(0)); and in the sequence, you would put just --> FMODWait(entrytag);
if (state != PLAYBACK_STATE.STOPPED)
{
while (IsPlaying())
{
yield return null;
}
}
Stop();
}
bool IsPlaying()
{
if (state == PLAYBACK_STATE.STOPPED) return false;
eventInstance.getPlaybackState(out state);
return state != PLAYBACK_STATE.STOPPED;
}
public void OnDestroy()
{
if (state != PLAYBACK_STATE.STOPPED)
{
if (IsPlaying()) eventInstance.stop(STOP_MODE.IMMEDIATE);
eventInstance.release();
}
}
void PlayDialogue(string key)
{
// TODO: check localization and set the bank to EN JP or KR
eventInstance = FMODUnity.RuntimeManager.CreateInstance("event:/Dialogue/Dialogue_EN"); // this event will be the part where I will be swapping only for localization. So, "event:/Dialogue/Dialogue_EN" contains, say 4000 english lines, "event:/Dialogue/Dialogue_JA" will contain 4000 japanese lines.
// Pin the key string in memory and pass a pointer through the user data
GCHandle stringHandle = GCHandle.Alloc(key);
eventInstance.setUserData(GCHandle.ToIntPtr(stringHandle));
eventInstance.setCallback(dialogueCallback);
eventInstance.start();
}
[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
static FMOD.RESULT DialogueEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
{
FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);
// Retrieve the user data
IntPtr stringPtr;
instance.getUserData(out stringPtr);
// Get the string object
GCHandle stringHandle = GCHandle.FromIntPtr(stringPtr);
String key = stringHandle.Target as String;
switch (type)
{
case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
{
FMOD.MODE soundMode = FMOD.MODE.LOOP_NORMAL | FMOD.MODE.CREATECOMPRESSEDSAMPLE | FMOD.MODE.NONBLOCKING;
var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
if (key.Contains("."))
{
FMOD.Sound dialogueSound;
var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(Application.streamingAssetsPath + "/" + key, soundMode, out dialogueSound);
if (soundResult == FMOD.RESULT.OK)
{
parameter.sound = dialogueSound.handle;
parameter.subsoundIndex = -1;
Marshal.StructureToPtr(parameter, parameterPtr, false);
}
}
else
{
FMOD.Studio.SOUND_INFO dialogueSoundInfo;
var keyResult = FMODUnity.RuntimeManager.StudioSystem.getSoundInfo(key, out dialogueSoundInfo);
if (keyResult != FMOD.RESULT.OK)
{
break;
}
FMOD.Sound dialogueSound;
var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(dialogueSoundInfo.name_or_data, soundMode | dialogueSoundInfo.mode, ref dialogueSoundInfo.exinfo, out dialogueSound);
if (soundResult == FMOD.RESULT.OK)
{
parameter.sound = dialogueSound.handle;
parameter.subsoundIndex = dialogueSoundInfo.subsoundindex;
Marshal.StructureToPtr(parameter, parameterPtr, false);
}
}
break;
}
case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
{
var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
var sound = new FMOD.Sound(parameter.sound);
sound.release();
break;
}
case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROYED:
{
// Now the event has been destroyed, unpin the string memory so it can be garbage collected
stringHandle.Free();
break;
}
}
return FMOD.RESULT.OK;
}
}
Let me know if this looks weird in terms of Dialogue System wise. (or even FMOD wise - I am new to this)
At least it seems to work as intended so far.
Above bring up 1 more question- is it possible to know when the voice sound ended (if player didnt make any interation yet)?
So I can sync mouth animation keep rolling while there is sound and stop when there isn't.