Page 1 of 1

Showing the responses a little early

Posted: Sat Dec 22, 2018 1:48 pm
by ava
Hi,

I wanted to ask what the best approach is for this: I'd like the responses to show before the end of the npc subtitle, for example 30% before the end.

Is this somehow possible? I realize this requires the system to know how long a subtitle will be visible.

Thanks for any help!
Alex

Re: Showing the responses a little early

Posted: Sat Dec 22, 2018 2:50 pm
by Tony Li
Hi Alex,

Yes, it's possible. By default, the subtitle panel fast-forwards the typewriter effect when it progresses to the response menu. To change this, replace your Standard UI Subtitle Panel component with a script like this:

MyCustomSubtitlePanel.cs

Code: Select all

public class MyCustomSubtitlePanel : PixelCrushers.DialogueSystem.StandardUISubtitlePanel
{
    public override void FinishSubtitle()
    {        
        HideContinueButton(); // Hide continue button but don't fast-forward typewriter.
    }
}
To replace it, inspect the subtitle panel. Use the three-bar menu in the upper right of the Inspector to switch from Normal to Debug mode. Then drag the script (MyCustomSubtitlePanel) into the Standard UI Subtitle Panel's Script field. This will keep your existing UI element assignments intact. If you want to rename MyCustomSubtitlePanel to something more descriptive, remember to rename the file and the class name in the first line of the script.

Then add this script to the Dialogue Manager:

EarlyAdvance.cs

Code: Select all

using UnityEngine;
using PixelCrushers.DialogueSystem;

public class EarlyAdvance : MonoBehaviour
{
    public float charactersPerSecond = 50;
    public float percentToDelay = 70;

    void OnConversationLine(Subtitle subtitle) // Add a Delay() sequencer command.
    {
        float numCharacters = Tools.StripRichTextCodes(subtitle.formattedText.text).Length;
        float duration = (percentToDelay / 100) * (numCharacters / charactersPerSecond);
        if (string.IsNullOrEmpty(subtitle.sequence)) subtitle.sequence = "{{default}}";
        subtitle.sequence = "Delay(" + duration + "); " + subtitle.sequence;
    }
}
Set Characters Per Second to the typewriter speed or however else you want to determine what 100% duration is. Then set Percent To Delay to the percent of that duration you want to wait before progressing to the response menu. This script adds a Delay() sequencer command.

If your Dialogue Manager's Default Sequence field or the dialogue entry node's Sequence field have "Delay({{end}})", remove it.

Re: Showing the responses a little early

Posted: Sun Dec 23, 2018 3:36 am
by ava
Hi,

Thanks for this splendid reply, I love the way you take the time to give such good answers!

I tried your solution, but it didn't work. I figured it's because I use a AudioWait sequence command to play the audio with the subtitle. The Delay command introduced by your solution did not have an effect together with the AudioWait.

But your answer put me on track for a solution: I introduced a new sequencer command AudiWaitScaled, with as first parameter the percentage of time you want to wait before showing the next node:

Code: Select all

public class SequencerCommandAudioWaitScaled : SequencerCommand
{

private float stopTime = 0;
private float scale = 1.0f;
private AudioSource audioSource = null;
private int nextClipIndex = 3;
private AudioClip currentClip = null;
private AudioClip originalClip = null;

public IEnumerator Start()
{
scale = GetParameterAsInt(0) / 100.0f;
string audioClipName = GetParameter(1);
Transform subject = GetSubject(1);
nextClipIndex = 2;
if (DialogueDebug.logInfo) Debug.Log(string.Format("{0}: Sequencer: AudioWaitScaled({1})", new System.Object[] { DialogueDebug.Prefix, GetParameters() }));
audioSource = SequencerTools.GetAudioSource(subject);
if (audioSource == null)
{
if (DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Sequencer: AudioWaitScaled() command: can't find or add AudioSource to {1}.", new System.Object[] { DialogueDebug.Prefix, subject.name }));
Stop();
}
else
{
originalClip = audioSource.clip;
stopTime = DialogueTime.time + 1; // Give time for yield return null.
yield return null;
originalClip = audioSource.clip;
TryAudioClip(audioClipName);
}
}

private void TryAudioClip(string audioClipName)
{
try
{
AudioClip audioClip = (!string.IsNullOrEmpty(audioClipName)) ? (DialogueManager.LoadAsset(audioClipName) as AudioClip) : null;
if (audioClip == null)
{
if (DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Sequencer: AudioWaitScaled() command: Clip '{1}' wasn't found.", new System.Object[] { DialogueDebug.Prefix, audioClipName }));
}
else
{
if (IsAudioMuted())
{
if (DialogueDebug.logInfo) Debug.Log(string.Format("{0}: Sequencer: AudioWaitScaled(): waiting but not playing '{1}'; audio is muted.", new System.Object[] { DialogueDebug.Prefix, audioClipName }));
}
else
{
if (DialogueDebug.logInfo) Debug.Log(string.Format("{0}: Sequencer: AudioWaitScaled(): playing '{1}'.", new System.Object[] { DialogueDebug.Prefix, audioClipName }));
currentClip = audioClip;
audioSource.clip = audioClip;
audioSource.Play();
}
}
stopTime = DialogueTime.time + audioClip.length * scale;
}
catch (System.Exception)
{
stopTime = 0;
}
}

public void Update()
{
if (DialogueTime.time >= stopTime)
{
if (nextClipIndex < parameters.Length)
{
TryAudioClip(GetParameter(nextClipIndex));
nextClipIndex++;
}
else
{
Stop();
}
}
}

public void OnDialogueSystemPause()
{
if (audioSource == null) return;
audioSource.Pause();
}

public void OnDialogueSystemUnpause()
{
if (audioSource == null) return;
audioSource.Play();
}
}
Completely based on your AudioWait command, I introduced the scale parameter + I removed what happened in OnDestroy.

And this does the job for me!

Thanks for your help!

Re: Showing the responses a little early

Posted: Sun Dec 23, 2018 8:33 am
by Tony Li
Hi,

Sorry, in my previous reply I totally forgot to mention that I tested with the Audio() command instead of AudioWait(). The Audio() command kicks off the audio but doesn't wait. Instead, the Delay() command that's added by the script makes the subtitle wait an appropriate duration.

But your custom sequencer command is a great solution, too!