Search Unity

Playing a Coroutine in Timeline

Discussion in 'Timeline' started by akyu01, Oct 4, 2020.

  1. akyu01

    akyu01

    Joined:
    Jan 22, 2020
    Posts:
    1
    Hello, I am trying to implement a typewriting effect of a speech in a timeline for a cutscene.
    So far I created a track with a binding type of textmesh pro, and a clip and a behavior that has the logic of that as follows, But I am not sure how to let WaitForSeconds works, It shows only the first letter.

    Code (CSharp):
    1. public class FirstEventBehaviour : PlayableBehaviour
    2. {
    3.      public string SpeechText;
    4.      private PlayableDirector director;
    5.      public float typingSpeed;
    6.      public override void OnPlayableCreate(Playable playable)
    7.      {
    8.          director = (playable.GetGraph().GetResolver() as PlayableDirector);
    9.      }
    10.      public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    11.      {
    12.          TextMeshProUGUI textDisplay = playerData as TextMeshProUGUI;
    13.          textDisplay.text = "";
    14.          textDisplay.color = new Color(0, 0, 0, info.weight);
    15.          for (int letter = 0; letter <SpeechText.Length; letter++)
    16.          {
    17.              textDisplay.text += SpeechText[letter];
    18.              StartCoroutine(WaitingTime);
    19.          }
    20.      }
    21.      IEnumerator WaitingTime()
    22.      {
    23.          yield return new WaitForSeconds(typingSpeed);
    24.      }
    25. }
     
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    I would recommend against trying to use a Coroutine inside a PlayableBehaviour. A PlayableBehaviour method like ProcessFrame is called every frame that the clip is active.

    Coroutines start a process that last for a certain amount of time. It appears that you need a method that given a certain time, set the text appropriately.

    Here's a simple example how to do the text effect completely in timeline.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3. using UnityEngine.Timeline;
    4. using UnityEngine.UI;
    5.  
    6. class TypewriterEffectBehaviour : PlayableBehaviour
    7. {
    8.     public string DefaultText;
    9.  
    10.     // called every frame the clip is active
    11.     public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    12.     {
    13.         var textObject = playerData as Text;
    14.         if (textObject == null)
    15.             return;
    16.  
    17.         // given the current time, determine how much of the string will be displayed
    18.         var progress = (float) (playable.GetTime() / playable.GetDuration());
    19.         var subStringLength = Mathf.RoundToInt(Mathf.Clamp01(progress) * DefaultText.Length);
    20.         textObject.text = DefaultText.Substring(0, subStringLength);
    21.     }
    22. }
    23.  
    24. public class TypewriterEffectPlayableAsset : PlayableAsset, IPropertyPreview
    25. {
    26.     public string Text = "This is the default text to display";
    27.     public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
    28.     {
    29.         var playable = ScriptPlayable<TypewriterEffectBehaviour>.Create(graph);
    30.         playable.GetBehaviour().DefaultText = Text;
    31.         return playable;
    32.     }
    33.  
    34.     // this will put the text field into preview mode when editing, avoids constant dirtying the scene
    35.     public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
    36.     {
    37.         driver.AddFromName<Text>("m_Text");
    38.     }
    39. }
    40.  
    And the associated track

    Code (CSharp):
    1. [TrackClipType(typeof(TypewriterEffectPlayableAsset))]
    2. [TrackBindingType(typeof(Text))]
    3. public class TypewriterEffectTrack : TrackAsset
    4. {
    5. }
    This will use the clip duration and text length to determine the speed of the effect.

    Alternately you can use a control track, and on your Monobehaviour use the ITimeControl interface to have Timeline pass the current clip time. In that case you should not use a Coroutine either. This might be better if you only need the time, and the duration or speed is determined by your monobehaviour. It very much depends on how you like it work.

    Code (CSharp):
    1. public class TypewriterEffect : MonoBehaviour, ITimeControl
    2. {
    3.     ...
    4.     private float m_Time = 0;
    5.    
    6.     static string GetSubStringForTime(string text, float t, float duration)
    7.     {
    8.         int subStringLength = Mathf.RoundToInt(Mathf.Clamp01(t / duration) * text.Length);
    9.         return text.Substring(0, subStringLength);
    10.     }
    11.  
    12.     // called by timeline control track to pass the current clip time
    13.     public void SetTime(double time)
    14.     {
    15.         m_Time = (float) time;
    16.         m_Text = GetSubStringForTime(m_DefaultText, t, m_Duration);
    17.     }
    18.  
    19.     // called by timeline control track when the clip starts
    20.     public void OnControlTimeStart()
    21.     {
    22.     }
    23.  
    24.     // called by timeline control track when the clip stops
    25.     // not necessarily at the end of the clip.
    26.     public void OnControlTimeStop()
    27.     {
    28.     }
    29. }
    30.  
    31.  
    Timeline works well when you can express your algorithm as a function of the time. Coroutines work well when you only need forward progress at runtime and can accumulate time yourself.
     
    CarlFat and popok1 like this.
  3. HunterEkko

    HunterEkko

    Joined:
    Jan 22, 2021
    Posts:
    1
    Code (CSharp):
    1.  public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    2.     {
    3.         var textObject = playerData as Text;
    4.         if (textObject == null)
    5.             return;
    6.         // given the current time, determine how much of the string will be displayed
    7.         var progress = (float) (playable.GetTime() / playable.GetDuration());
    8.         var subStringLength = Mathf.RoundToInt(Mathf.Clamp01(progress) * DefaultText.Length);
    9.         textObject.text = DefaultText.Substring(0, subStringLength);
    10.     }
    If I use this method, how can I apply the rich text effect to "<color=red>Hello</color> World!"?