Page 1 of 1

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

Posted: Sat May 20, 2023 6:40 am
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!

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

Posted: Sat May 20, 2023 8:55 am
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.

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

Posted: Sat May 20, 2023 11:33 am
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

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

Posted: Sat May 20, 2023 11:23 pm
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.

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

Posted: Sun May 21, 2023 5:22 am
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.

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

Posted: Sun May 21, 2023 10:20 am
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 431 times

I don't know that all of those settings are necessary, but they work fine in this example.

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

Posted: Sun May 21, 2023 11:37 am
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 😬

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

Posted: Sun May 21, 2023 3:22 pm
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.