/// <summary> /// A coroutine that fades a <see cref="CanvasGroup"/> object's /// opacity from <paramref name="from"/> to <paramref name="to"/> /// over the course of <see cref="fadeTime"/> seconds, and then /// invokes <paramref name="onComplete"/>. /// </summary> /// <param name="from">The opacity value to start fading from, /// ranging from 0 to 1.</param> /// <param name="to">The opacity value to end fading at, ranging /// from 0 to 1.</param> /// <param name="onComplete">A delegate to invoke after fading is /// complete.</param> public static IEnumerator FadeAlpha(CanvasGroup canvasGroup, float from, float to, float fadeTime, PresentationFlag presented = null, Action onComplete = null) { canvasGroup.alpha = from; var timeElapsed = 0f; while (timeElapsed < fadeTime) { var fraction = timeElapsed / fadeTime; timeElapsed += Time.deltaTime; float a = Mathf.Lerp(from, to, fraction); canvasGroup.alpha = a; yield return(null); } canvasGroup.alpha = to; if (to == 0) { canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } else { canvasGroup.interactable = true; canvasGroup.blocksRaycasts = true; } presented?.Set(); onComplete?.Invoke(); }
public static IEnumerator Typewriter(TextMeshProUGUI text, float lettersPerSecond, PresentationFlag presented, Action onCharacterTyped, Action onComplete) { // Start with everything invisible text.maxVisibleCharacters = 0; // Wait a single frame to let the text component process its // content, otherwise text.textInfo.characterCount won't be // accurate yield return(null); // How many visible characters are present in the text? var characterCount = text.textInfo.characterCount; // Early out if letter speed is zero or text length is zero if (lettersPerSecond <= 0 || characterCount == 0) { // Show everything and invoke the completion handler text.maxVisibleCharacters = characterCount; onComplete?.Invoke(); yield break; } // Convert 'letters per second' into its inverse float secondsPerLetter = 1.0f / lettersPerSecond; // If lettersPerSecond is larger than the average framerate, we // need to show more than one letter per frame, so simply // adding 1 letter every secondsPerLetter won't be good enough // (we'd cap out at 1 letter per frame, which could be slower // than the user requested.) // // Instead, we'll accumulate time every frame, and display as // many letters in that frame as we need to in order to achieve // the requested speed. var accumulator = Time.deltaTime; while (text.maxVisibleCharacters < characterCount) { // We need to show as many letters as we have accumulated // time for. while (accumulator >= secondsPerLetter) { text.maxVisibleCharacters += 1; onCharacterTyped?.Invoke(); accumulator -= secondsPerLetter; } accumulator += Time.deltaTime; yield return(null); } // We either finished displaying everything, or were // interrupted. Either way, display everything now. text.maxVisibleCharacters = characterCount; presented?.Set(); // Wrap up by invoking our completion handler. onComplete?.Invoke(); }
public override void RunLine(LocalizedLine dialogueLine, Action onDialogueLineFinished) { currentLine = dialogueLine; hasPresented.Clear(); // if we have auto advance on we just hand over the completion handler // if we don't have auto advance we send over no completion handler and store the completion handler // this way it can be called later by the user action var completionHandler = autoAdvance ? onDialogueLineFinished : null; dialogueFinishedAction = onDialogueLineFinished; lineText.gameObject.SetActive(true); canvasGroup.gameObject.SetActive(true); if (continueButton != null) { continueButton.SetActive(false); } if (characterNameText == null) { if (showCharacterNameInLineView) { lineText.text = dialogueLine.Text.Text; } else { lineText.text = dialogueLine.TextWithoutCharacterName.Text; } } else { characterNameText.text = dialogueLine.CharacterName; lineText.text = dialogueLine.TextWithoutCharacterName.Text; } bool needsHold = autoAdvance && holdTime > 0; if (useFadeEffect) { if (useTypewriterEffect) { // If we're also using a typewriter effect, ensure that // there are no visible characters so that we don't // fade in on the text fully visible lineText.maxVisibleCharacters = 0; } else { // Ensure that the max visible characters is effectively unlimited. lineText.maxVisibleCharacters = int.MaxValue; } // if we are set to auto advance we want to hold for the // amount of time set in holdTime before calling the // completion handler to continue the dialogue if (needsHold) { StartCoroutine(Effects.FadeAlpha(canvasGroup, 0, 1, fadeInTime, useTypewriterEffect ? null: hasPresented, () => FadeComplete(() => HoldAndContinue(completionHandler)))); } else { // Fade up and then call FadeComplete when done StartCoroutine(Effects.FadeAlpha(canvasGroup, 0, 1, fadeInTime, useTypewriterEffect ? null: hasPresented, () => FadeComplete(completionHandler))); } } else { // Immediately appear canvasGroup.interactable = true; canvasGroup.alpha = 1; canvasGroup.blocksRaycasts = true; if (useTypewriterEffect) { if (needsHold) { StartCoroutine(Effects.Typewriter(lineText, typewriterEffectSpeed, hasPresented, OnCharacterTyped, () => HoldAndContinue(completionHandler))); } else { StartCoroutine(Effects.Typewriter(lineText, typewriterEffectSpeed, hasPresented, OnCharacterTyped, completionHandler)); } } else { if (needsHold) { Action hold = () => { hasPresented.Set(); completionHandler(); }; HoldAndContinue(hold); } else { hasPresented.Set(); completionHandler(); } } } void FadeComplete(Action onFinished) { if (useTypewriterEffect) { StartCoroutine(Effects.Typewriter(lineText, typewriterEffectSpeed, hasPresented, OnCharacterTyped, onFinished)); } else { onFinished(); } } void HoldAndContinue(Action onFinished) { StartCoroutine(DelayAction(holdTime, onFinished)); } }