AnimatorPlayWait() not waiting for state to end

Announcements, support questions, and discussion for the Dialogue System.
User avatar
Tony Li
Posts: 22051
Joined: Thu Jul 18, 2013 1:27 pm

Re: AnimatorPlayWait() not waiting for state to end

Post by Tony Li »

FlaconiaUnited wrote: Wed Oct 07, 2020 6:46 pm...The dialogue manager just skips immediately to the next line ignoring everything.
You may need to set the PC node's Sequence to make it do something. If it's blank, it may skip immediately to the next node.
FlaconiaUnited wrote: Wed Oct 07, 2020 6:46 pmAbout scripting the subclass, could you give me more details?
Should I duplicate the SetContent subclass, rename it and trigger that new one instead of the original SetContent?
Create a script file -- for example, named MySubtitlePanel.cs:

Code: Select all

using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System;
using PixelCrushers.DialogueSystem

public class MySubtitlePanel : StandardUISubtitlePanel
{
    public override void SetContent(Subtitle subtitle)
    {
        // Make sure we run the StartTypingWhenFocused coroutine:
        delayTypewriterUntilOpen = true;
        hasFocus = false;
        // Call the base method. With the values above set, we know it will call StartTypingWhenFocused.
        base.SetContent(subtitle);
    }
    
    // Override this method to wait until the animator is at the end of the Show() state.
    public override IEnumerator StartTypingWhenFocused(UITextField subtitleText, string text, int fromIndex)
    {
        subtitleText.text = string.Empty;
        float timeout = Time.realtimeSinceStartup + 5f; // This is just a safety valve to prevent infinite loops.
        while (!IsAtEndOfShowState() && Time.realtimeSinceStartup < timeout)
        {
            yield return null;
        }
        subtitleText.text = text;
        TypewriterUtility.StartTyping(subtitleText, text, fromIndex);
    }
    
    // This function returns true if we're at the end of the Show() state.
    protected bool IsAtEndOfShowState()
    {
        var currentAnimatorState = animator.GetCurrentAnimatorStateInfo(0);
        return currentAnimatorState.IsName("Show") && 
            currentAnimatorState.normalizedTime >= 0.99f; // Give floating point error margin.
    }
}
(I just typed that in; it might have typos.)

To replace the subtitle panel script in place and retain the UI element assignments, change the Inspector view to debug mode by selecting the menu gizmo in the upper right of the Inspector. Select Debug. Then find the StandardUISubtitlePanel component. Drag your script into the Script field.
FlaconiaUnited wrote: Wed Oct 07, 2020 6:46 pmshould I just change the conditions nedeed to enter the IF/ELSE to that specific state of the animator?
I'm sorry; I don't understand that. But the subclass may do the trick for you.
User avatar
FlaconiaUnited
Posts: 14
Joined: Fri Oct 02, 2020 2:07 pm

Re: AnimatorPlayWait() not waiting for state to end

Post by FlaconiaUnited »

Tony Li wrote: Wed Oct 07, 2020 8:42 pm You may need to set the PC node's Sequence to make it do something. If it's blank, it may skip immediately to the next node.
Not sure if I understand this, I'm passing through that PROGRESS CHECK state in the conversation, where I have 2 sequence commands like I showed you before:

Code: Select all

AnimatorPlayWait(HideLeft, Dialogue Panel);
Continue();
But on the other hand, the big conversation that contains all the others had no Actor or Conversant assigned, so I tried with both assigning the Player and the NPC (we only have these 2), no luck.
So I suppose you're talking about having some sequencer command, and I do have 2.

-

About the script:

I did what you told me, the coee you passed me (thanks!) had 2 problems:
  • It didn't find the UITextField method, so I had to add PixelCrushers. before it.
  • there was no animator declared, so it didn't know which animator to check the state of, in the last method, so I added it at the beginning
So here's the new code I tried with (the ciao, ciao2 and ciao3 Debug.Logs are for making sure it enters the methods, which it does, but still no difference):

Code: Select all

using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System;
using PixelCrushers.DialogueSystem;

public class MountainUISubtitlePanel : StandardUISubtitlePanel
{
    public Animator animator;
    
    public override void SetContent(Subtitle subtitle)
    {
        Debug.Log("ciao");
        // Make sure we run the StartTypingWhenFocused coroutine:
        delayTypewriterUntilOpen = true;
        hasFocus = false;
        // Call the base method. With the values above set, we know it will call StartTypingWhenFocused.
        base.SetContent(subtitle);
    }

    // Override this method to wait until the animator is at the end of the Show() state.
    public override IEnumerator StartTypingWhenFocused(PixelCrushers.UITextField subtitleText, string text, int fromIndex)
    {
        Debug.Log("ciao2");
        subtitleText.text = string.Empty;
        float timeout = Time.realtimeSinceStartup + 5f; // This is just a safety valve to prevent infinite loops.
        while (!IsAtEndOfShowState() && Time.realtimeSinceStartup < timeout)
        {
            yield return null;
        }
        subtitleText.text = text;
        TypewriterUtility.StartTyping(subtitleText, text, fromIndex);
    }

    // This function returns true if we're at the end of the Show() state.
    protected bool IsAtEndOfShowState()
    {
        Debug.Log("ciao3");
        var currentAnimatorState = animator.GetCurrentAnimatorStateInfo(0);
        return currentAnimatorState.IsName("Show") &&
            currentAnimatorState.normalizedTime >= 0.99f; // Give floating point error margin.
    }
}
The original StandardUISubtitlePanel had a method which was protected, so I couldn't override it (don't remember if it was SetContent() itself or StartTypingWhenFocused() ) anyway, I changed it from protected to public, that's the only edit I did to the original files.


Then, being my new one a subclass deriving from the StandardUISubtitlePanel, it used the same Inspector GUI, which was encapsulated in another script called StandardUISubtitlePanelEditor. I didn't want to make modifications on the original stuff, so I created an Editor script that applies a new GUI only to my script, which is this one (the only difference is I tried to add an animator field, to give it the Dialogue Panel one):

Code: Select all

// Copyright (c) Pixel Crushers. All rights reserved.

using UnityEngine;
using UnityEditor;

namespace PixelCrushers.DialogueSystem
{

    [CustomEditor(typeof(MountainUISubtitlePanel), true)]
    public class MountainUISubtitlePanelEditor : StandardUISubtitlePanelEditor
    {

        public new void OnInspectorGUI()
        {
            serializedObject.Update();
            EditorGUILayout.LabelField("Dialogue Panel reference", EditorStyles.boldLabel);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("animator"), true);
            
            EditorGUILayout.LabelField("UI Elements", EditorStyles.boldLabel);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("panel"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("portraitImage"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("portraitName"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("subtitleText"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("continueButton"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("onlyShowNPCPortraits"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("useAnimatedPortraits"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("accumulateText"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("addSpeakerName"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("addSpeakerNameFormat"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("delayTypewriterUntilOpen"), true);


            

            EditorGUILayout.LabelField(new GUIContent("Navigation", "Joystick/keyboard navigation settings."), EditorStyles.boldLabel);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("firstSelected"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("focusCheckFrequency"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("refreshSelectablesFrequency"), true);
            var selectPreviousOnDisableProperty = serializedObject.FindProperty("selectPreviousOnDisable");
            if (selectPreviousOnDisableProperty != null) EditorGUILayout.PropertyField(selectPreviousOnDisableProperty); // Not present in older versions of UIPanel.

            EditorGUILayout.LabelField("Visibility", EditorStyles.boldLabel);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("visibility"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("startState"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("showAnimationTrigger"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("hideAnimationTrigger"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("focusAnimationTrigger"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("unfocusAnimationTrigger"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("m_hasFocus"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("onOpen"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("onClose"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("onFocus"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("onUnfocus"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("onBackButtonDown"), true);

            serializedObject.ApplyModifiedProperties();
            
        }

    }

}
But I cannot seem to be able to make the new field appear.

Your inspector debug mode was really helpful because in that way I can tell my subclass which animator it needs to check anyway, even without the field serialized.

-

Anyway, despite now the subclass is integrated, put into the Subtitle Panel and no errors in console... still no change, still the typewriter effect starts no matter what's happening to the Dialogue Panel. :(
Tony Li wrote: Wed Oct 07, 2020 8:42 pm FlaconiaUnited wrote: ↑Thu Oct 08, 2020 12:46 am
should I just change the conditions nedeed to enter the IF/ELSE to that specific state of the animator?
I'm sorry; I don't understand that. But the subclass may do the trick for you.
Sorry, nothing important here, I just wasn't understand how to structure all this but then you've been clear, never mind.


So, any other ideas on how to achieve this?
I'm starting to think that if animating the UI this way is something so hard to achieve maybe it doesn't make much sense, but I feel a little bit limited to not being able to animate the UI the way I want...
User avatar
Tony Li
Posts: 22051
Joined: Thu Jul 18, 2013 1:27 pm

Re: AnimatorPlayWait() not waiting for state to end

Post by Tony Li »

Hi,

It's entirely possible. I suspect there's just one or two things that we're not getting across to each other.

---
As a side note: You can completely bypass the StandardDialogueUI system if you want. Maybe it has too many options that are getting in the way of what you want to do. If you prefer, you can write your own dialogue UI script that implements the IDialogueUI interface. This interface has just a few methods to implement such as ShowSubtitle() and ShowResponses(). The Dialogue System's Templates/Scripts folder contains a commented starter script. Then assign the GameObject that has your dialogue UI script to the Dialogue Manager's Dialogue UI field.
---

---
Other side note: The 'protected' keyword means the method (SetContent in this case) is accessible to subclasses. So you should have been able to define:

Code: Select all

protected override void SetContent(Subtitle subtitle)
Also, there's no need to update the editor script. You can grab the Animator in an Awake method:

Code: Select all

protected virtual void Awake()
{
    animator = GetComponentInChildren<Animator>();
}
---

My understanding is that you want to do the following:

1. Continuously stay in conversation mode.
2. Subtitles require a continue button click to progress.
3. The last node of the current conversation may link to the first node of the next conversation.
4. As you cross into the next conversation:
  • Slide the dialogue UI offscreen to the left using an animation.
  • Slide the dialogue UI back onscreen using an animation.
  • Once the dialogue UI is fully back onscreen, start typing the subtitle text of the next conversation.
I'll assume that's correct for now and put together a small example scene that demonstrates this.
User avatar
Tony Li
Posts: 22051
Joined: Thu Jul 18, 2013 1:27 pm

Re: AnimatorPlayWait() not waiting for state to end

Post by Tony Li »

Here's an example scene:

DS_FlaconiaExample_2020-10-10.unitypackage

It didn't require any custom scripting.

It looks like this:



The node that does the animation has this Sequence:

Code: Select all

AnimatorPlayWait(Hide,Dialogue Panel)->Message(Hidden);
ClearSubtitleText()@Message(Hidden);
AnimatorPlayWait(Show,Dialogue Panel)@Message(Hidden)->Message(Ready);
Continue()@Message(Ready)
User avatar
FlaconiaUnited
Posts: 14
Joined: Fri Oct 02, 2020 2:07 pm

Re: AnimatorPlayWait() not waiting for state to end

Post by FlaconiaUnited »

Hello Toni!!
Tony Li wrote: Sat Oct 10, 2020 3:35 pm As a side note: You can completely bypass the StandardDialogueUI system if you want. Maybe it has too many options that are getting in the way of what you want to do. If you prefer, you can write your own dialogue UI script that implements the IDialogueUI interface. This interface has just a few methods to implement such as ShowSubtitle() and ShowResponses(). The Dialogue System's Templates/Scripts folder contains a commented starter script. Then assign the GameObject that has your dialogue UI script to the Dialogue Manager's Dialogue UI field.
That's what I was thinking about, but my scripting skills are really bad for now, I'm still learning the basics so I'm not sure it would be a good idea, but thanks anyway for these info, might come handy in the future;
Thanks also for the explanation about protected and the Awake method!
Tony Li wrote: Sat Oct 10, 2020 3:35 pm My understanding is that you want to do the following:

1. Continuously stay in conversation mode.
2. Subtitles require a continue button click to progress.
3. The last node of the current conversation may link to the first node of the next conversation.
4. As you cross into the next conversation:
Slide the dialogue UI offscreen to the left using an animation.
Slide the dialogue UI back onscreen using an animation.
Once the dialogue UI is fully back onscreen, start typing the subtitle text of the next conversation.
That's precisely it, in fact the example you sent me was perfect, it's so cool to see that it could be done easily just with Sequencer commands!!
I used your example as reference and discovered that I had a few flaws in my dialogue database.

For example, all the nodes where I just do hidden stuff (modifying variables, checking conditions etc.) had their name in the Dialogue Text field, which apparently, when running the inbetween scenarios transitions, gets in the way, showing unwanted texts like "RETURN" or stuff like that during the transition. I solved by emptying all those Dialogue text fields and putting the name of the state in the Title instead:
from dialogue text to title.png
from dialogue text to title.png (60.11 KiB) Viewed 307 times
_
So apparently the Dialogue Text fieldshould be used exclusively when you have an actual dialogue that you want to show. It was obvious :lol:

--

So, going back to the seqencer commands we're now using to achieve that result:

Code: Select all

AnimatorPlayWait(Hide,Dialogue Panel)->Message(Hidden);
ClearSubtitleText()@Message(Hidden);
AnimatorPlayWait(Show,Dialogue Panel)@Message(Hidden)->Message(Ready);
Continue()@Message(Ready)
I works just fine when I'm between scenarios, but what if I wanted to have the same thing at the beginning of the game?

I have the same problem, I cannot manage to let the typewriter effect start after the first animation.
I tried with this sequence ina state before the first line fo dialogue:

Code: Select all

AnimatorPlayWait(Show,Dialogue Panel)->Message(Ready);
Continue()@Message(Ready)
since I'm starting from a deactivated Dialogue Panel, I don't need to hide it and clear the Subtitle panel first, so I removed the first message part.
It doesn't work like this, I'm getting the typewriter effect during the animation, while it's still working between scenarios.

I even tried to put the same exact sequencer command list that you passed me before, no result. :cry:
Am I missing anything that is needed specifically for the very first time we open the conversation?
User avatar
Tony Li
Posts: 22051
Joined: Thu Jul 18, 2013 1:27 pm

Re: AnimatorPlayWait() not waiting for state to end

Post by Tony Li »

Hi,

When you start the conversation, does it play your "show" animation using the Show Animation Trigger? If so, then if you tick 'Delay Typewriter Until Focus' it should wait until the show animation is done.
User avatar
FlaconiaUnited
Posts: 14
Joined: Fri Oct 02, 2020 2:07 pm

Re: AnimatorPlayWait() not waiting for state to end

Post by FlaconiaUnited »

I'm sorry, I just realized that the previous conversation where I create the character stats, was pointing to a precise state of the introduction conversation, skipping the first state and therefore not executing its command! That's because I created the first state now on the previously existing conversation, but forgot to re link where the character creator was pointing to :roll:

My bad, now everything works as expected.
Thanks so much for your great support!

Now I think I got it perfectly.
User avatar
Tony Li
Posts: 22051
Joined: Thu Jul 18, 2013 1:27 pm

Re: AnimatorPlayWait() not waiting for state to end

Post by Tony Li »

Glad to hear everything's working now. Thanks for your patience as we got it sorted out.
Post Reply