Wait for a playing animation to end (or skipped) before showing subtitle

Announcements, support questions, and discussion for the Dialogue System.
Post Reply
LostTrainDude
Posts: 61
Joined: Wed Mar 21, 2018 2:14 pm

Wait for a playing animation to end (or skipped) before showing subtitle

Post by LostTrainDude »

[Unity 2021.3.25f1 - DS v.2.2.37]

Hi Tony!

The game I'm working on features animated character closeups: characters can perform animations either in-between lines or randomly while waiting for you to select an option.

To "direct" them I use Articy's "Stage Directions" in DialogueFragments, like the following:
Image

The shortcuts roughly translate to:

Code: Select all

DoTheAnimation()->Message(next); MyCustomAudioWait(entrytag)@Message(next)
(the asterisks append the ->Message() and @Message() respectively)

But what I see is that while the audio (and related lipsync) do wait for the actual animation to finish, the Subtitle still appears before the animation even starts.

Is there a way to have the subtitle appear along with the audio, without having to create an additional empty line\DialogueFragment? Avoiding it would help a lot when it comes to exporting the voice over spreadsheets and avoid endless "Director_xy_z" empty nodes, I think.

Anyway, I also guess that having it all in a single DialogueFragment would eventually get in the way of me being able to skip the animation only, without skipping the whole dialogue line?

Thanks in advance!
User avatar
Tony Li
Posts: 21055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Wait for a playing animation to end (or skipped) before showing subtitle

Post by Tony Li »

Hi,

You could write subclasses of two scripts: StandardUISubtitlePanel and StandardUIContinueButtonFastForward.

In StandardUISubtitlePanel, override the SetContent() method. Check if the sequence contains "->Message(next)". If so, don't start the typewriter until the sequencer receives the message "next".

In StandardUIContinueButtonFastForward, override OnFastForward(). If the sequence contains "->Message(next)" and the sequencer message hasn't been sent yet, call Sequencer.Message("next") instead of base.OnFastForward().

I can provide help with these later today if you have questions about them. I just need to run to a meeting right now.
LostTrainDude
Posts: 61
Joined: Wed Mar 21, 2018 2:14 pm

Re: Wait for a playing animation to end (or skipped) before showing subtitle

Post by LostTrainDude »

Thanks for the answer!
Yes, please. I'd love some further guidance on this - but, please take your time.

I already have a custom StandardUISubtitlePanel, so I tried to copy the content of the base SetContent() method in my override but introducing a check as you suggested.

Code: Select all

bool isWaitingOnAnimationToFinish;
public override void SetContent(Subtitle subtitle)
{
	if (subtitle == null) return;
	
	currentSubtitle = subtitle;
	lastActorID = subtitle.speakerInfo.id;
	
	if (subtitle.sequence.Contains("->Message(next)"))
		isWaitingOnAnimationToFinish = true;
	else
	{
		CheckSubtitleAnimator(subtitle);

		if (!onlyShowNPCPortraits || subtitle.speakerInfo.isNPC)
		{
			if (portraitImage != null)
			{
				var sprite = subtitle.GetSpeakerPortrait();
				SetPortraitImage(sprite);
			}

			portraitActorName = subtitle.speakerInfo.nameInDatabase;
		
			if (portraitName.text != subtitle.speakerInfo.Name)
			{
				portraitName.text = subtitle.speakerInfo.Name;
				UITools.SendTextChangeMessage(portraitName);
			}
		}

		if (waitForOpen && panelState != PanelState.Open)
			DialogueManager.instance.StartCoroutine(SetSubtitleTextContentAfterOpen(subtitle));
		else
			SetSubtitleTextContent(subtitle);
		
		frameLastSetContent = Time.frameCount;
	}
}
Then, using my own DialogueSystemEventBroadcaster to send\receive DS messages, I subscribed this Subtitle Panel to the OnSequencerMessage event and trigger basically the same content you see above (minus the check) if the isWaitingOnAnimationToFinish is set to true.

Code: Select all

public void OnSequencerMessage(string message)
{
	if (!isWaitingOnAnimationToFinish)
		return;

	if (message.Equals("next"))
	{
		isWaitingOnAnimationToFinish = false;
		CheckSubtitleAnimator(currentSubtitle);

		if (!onlyShowNPCPortraits || currentSubtitle.speakerInfo.isNPC)
		{
			if (portraitImage != null)
			{
				var sprite = currentSubtitle.GetSpeakerPortrait();
				SetPortraitImage(sprite);
			}

			portraitActorName = currentSubtitle.speakerInfo.nameInDatabase;

			if (portraitName.text != currentSubtitle.speakerInfo.Name)
			{
				portraitName.text = currentSubtitle.speakerInfo.Name;
				UITools.SendTextChangeMessage(portraitName);
			}
		}

		if (waitForOpen && panelState != PanelState.Open)
			DialogueManager.instance.StartCoroutine(SetSubtitleTextContentAfterOpen(currentSubtitle));
		else
			SetSubtitleTextContent(currentSubtitle);

		frameLastSetContent = Time.frameCount;
	}
}
The code seems to be working, meaning that everything seems to be properly executed in the due order and time, but the subtitle panel still shows from the get go.

My SubtitlePanel settings, I tried ticking Wait For Open, but nothing changed:

Image

My custom Dialogue UI settings:

Image
User avatar
Tony Li
Posts: 21055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Wait for a playing animation to end (or skipped) before showing subtitle

Post by Tony Li »

Hi,

Sorry, I misunderstood you and thought you only wanted to delay the subtitle text until after the animation. To delay the appearance of the entire subtitle, move that check into the Show() method instead.
LostTrainDude
Posts: 61
Joined: Wed Mar 21, 2018 2:14 pm

Re: Wait for a playing animation to end (or skipped) before showing subtitle

Post by LostTrainDude »

Hi!

Did you mean the ShowSubtitle() method?

I tried moving the check in the ShowSubtitle() method, but now the sequencer message is never sent.

Code: Select all

public override void ShowSubtitle(Subtitle subtitle)
{
	if (subtitle.sequence.Contains("->Message(next)"))
		isWaitingOnAnimationToFinish = true;
	else
		base.ShowSubtitle(subtitle);
}

public void OnSequencerMessage(string message)
{
	if (!isWaitingOnAnimationToFinish)
		return;

	if (message.Equals("next"))
	{
		isWaitingOnAnimationToFinish = false;

		Debug.Log("SubtitlePanel: this should be executed after animation has finished");
		base.ShowSubtitle(currentSubtitle);
	}
}
Trying debugging the execution, I also tried moving the check to the ShowSubtitleNow() method, instead, to no avail.

Eventually I also tried moving the check before the Open() and Focus() methods in the ShowSubtitleNow() like so:

Code: Select all

protected override void ShowSubtitleNow(Subtitle subtitle)
{
	SetUIElementsActive(true);
	if (!isOpen)
	{
		hasFocus = false;
		isFocusing = false;
	}

	if (subtitle.sequence.Contains("->Message(next)"))
		isWaitingOnAnimationToFinish = true;
	else
	{
		Open();
		Focus();
	}
	SetContent(subtitle);
	actorOverridingPanel = null;
}

public void OnSequencerMessage(string message)
{
	if (!isWaitingOnAnimationToFinish)
		return;

	if (message.Equals("next"))
	{
		isWaitingOnAnimationToFinish = false;

		Debug.Log("SubtitlePanel: this should be executed after animation has finished");
		Open();
		Focus();
	}
}
I assumed the SetContent() was necessary for the Sequencer message to be sent, but delving into the code more I am now not sure I actually understand where does it happen.
User avatar
Tony Li
Posts: 21055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Wait for a playing animation to end (or skipped) before showing subtitle

Post by Tony Li »

Yes, sorry, I meant the StandardDialogueUI's ShowSubtitle() method. This takes a little more setup, but in the end it's not much code. Here's an example scene:

DS_TestDelayShowSubtitle_2023-05-21.unitypackage

The conversation plays two NPC dialogue entries. Their Sequences are set to:

Code: Select all

Delay(2)->Message(next); 
Delay({{end}})@Message(next)
The Dialogue System only sends the OnSequencerMessage() message to the Dialogue Manager GameObject, so the example uses this script on the Dialogue Manager GameObject to allow scripts on other GameObjects to register for the event:

HandleOnSequenceMessage.cs

Code: Select all

using UnityEngine;
public class HandleOnSequenceMessage : MonoBehaviour
{
    public System.Action<string> receivedSequencerMessage = null;

    void OnSequencerMessage(string message)
    {
        receivedSequencerMessage?.Invoke(message);
    }
}
Then it replaces the dialogue UI's StandardDialogueUI component with this subclass:

DialogueUIDelayUntilNext.cs

Code: Select all

using UnityEngine;
using PixelCrushers.DialogueSystem;
public class DialogueUIDelayUntilNext : StandardDialogueUI
{
    private Subtitle subtitleToShow;

    public override void Start()
    {
        base.Start();
        DialogueManager.instance.GetComponent<HandleOnSequenceMessage>().receivedSequencerMessage += OnReceivedSequencerMessage;
    }

    public override void ShowSubtitle(Subtitle subtitle)
    {
        if (subtitle.sequence.Contains("->Message(next)"))
        {
            subtitleToShow = subtitle;
        }
        else
        {
            base.ShowSubtitle(subtitle);
        }
    }

    void OnReceivedSequencerMessage(string message)
    {
        if (message == "next")
        {
            base.ShowSubtitle(subtitleToShow);
        }
    }
}
In the scene, I configured the subtitle panel with these settings:

standardUISubtitlePanel.png
standardUISubtitlePanel.png (56.83 KiB) Viewed 427 times

I don't know that all of those settings are necessary, but they work fine in this example.
LostTrainDude
Posts: 61
Joined: Wed Mar 21, 2018 2:14 pm

Re: Wait for a playing animation to end (or skipped) before showing subtitle

Post by LostTrainDude »

Hi!

I had written a "success" post, then deleted it because I realized I wasn't really 100% successful. I thought I could bring it back now that I have fixed my issues, because I didn't tick the "Delete permanently" box, but alas I can't. In other words: apologies if I'm repeating myself!

Thanks for your example! I actually didn't need to import your Unity package because the code you posted made me realize I was making a silly mistake. My SubtitlePanels were unsubscribing upon OnDisable: of course the Sequencer message would not be received!

So this is my fully working code, for this specific problem:

Code: Select all

protected override void Awake()
{
	base.Awake();
	DialogueSystemEventBroadcaster.onSequencerMessage += OnSequencerMessage;
}

bool isWaitingOnAnimationToFinish;

public override void ShowSubtitle(Subtitle subtitle)
{
	if (subtitle.sequence.Contains("->Message(next)"))
	{
		currentSubtitle = subtitle;
		isWaitingOnAnimationToFinish = true;
	}
	else
		base.ShowSubtitle(subtitle);
}

public void OnSequencerMessage(string message)
{
	if (!isWaitingOnAnimationToFinish)
		return;

	if (message.Equals("next"))
	{
		isWaitingOnAnimationToFinish = false;
		base.ShowSubtitle(currentSubtitle);
	}
}
What made me realize it was that you suggested doing something I already did: to have a "broadcaster" component attached to the Dialogue Manager (my DialogueSystemEventBroadcaster object I refer to in the Awake method), so I eventually put two and two together.

Thank you! ...And apologies for the mistake!

Now on to figure out the fast forward bit 😬
User avatar
Tony Li
Posts: 21055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Wait for a playing animation to end (or skipped) before showing subtitle

Post by Tony Li »

Hi,

I'm glad you got that working the way you want. If you run into any questions with fast-forwarding, let me know.
Post Reply