public override void RunLine(LocalizedLine dialogueLine, Action onDialogueLineFinished) { var characterName = dialogueLine.CharacterName; Color colorToUse = defaultColor; if (string.IsNullOrEmpty(characterName) == false) { foreach (var color in colorData) { if (color.characterName.Equals(characterName, StringComparison.InvariantCultureIgnoreCase)) { colorToUse = color.displayColor; break; } } } foreach (var text in lineTexts) { text.color = colorToUse; } onDialogueLineFinished(); }
/// <summary> /// Start playback of the associated voice over <see /// cref="AudioClip"/> of the given <see cref="LocalizedLine"/>. /// </summary> /// <param name="dialogueLine"></param> /// <returns></returns> public override void RunLine(LocalizedLine dialogueLine, Action onDialogueLineFinished) { finishCurrentLine = false; if (!(dialogueLine is AudioLocalizedLine audioLine)) { Debug.LogError($"Playing voice over failed because {nameof(RunLine)} expected to receive an {nameof(AudioLocalizedLine)}, but instead received a {dialogueLine.GetType()}. Is your {nameof(DialogueRunner)} set up to use a {nameof(AudioLineProvider)}?", gameObject); onDialogueLineFinished(); return; } // Get the localized voice over audio clip var voiceOverClip = audioLine.AudioClip; if (!voiceOverClip) { Debug.Log("Playing voice over failed since the AudioClip of the voice over audio language or the base language was null.", gameObject); onDialogueLineFinished(); return; } if (audioSource.isPlaying) { // Usually, this shouldn't happen because the // DialogueRunner finishes and ends a line first audioSource.Stop(); } audioSource.PlayOneShot(voiceOverClip); StartCoroutine(DoPlayback(onDialogueLineFinished)); }
public override void OnLineStatusChanged(LocalizedLine dialogueLine) { switch (dialogueLine.Status) { case LineStatus.Presenting: // Nothing to do here - continue running. break; case LineStatus.Interrupted: // The user wants us to wrap up the audio quickly. The // DoPlayback coroutine will apply the fade out defined // by fadeOutTimeOnLineFinish. interrupted = true; break; case LineStatus.FinishedPresenting: // The line has finished delivery on all views. Nothing // left to do for us, since the audio will have already // finished playing out. break; case LineStatus.Dismissed: // The line is being dismissed; ensure that we // interrupt the FMOD instance. interrupted = true; break; } }
public override void OnLineStatusChanged(LocalizedLine dialogueLine) { switch (dialogueLine.Status) { case LineStatus.Running: // No-op; this line is running break; case LineStatus.Interrupted: // The line is now interrupted, and we need to hurry up // in our delivery finishCurrentLine = true; break; case LineStatus.Delivered: // The line has now finished its delivery across all // views, so we can signal call our UnityEvent for it onLineFinishDisplaying?.Invoke(); break; case LineStatus.Ended: // The line has now Ended. DismissLine will be called // shortly. onLineEnd?.Invoke(); break; } }
public override void OnLineStatusChanged(LocalizedLine dialogueLine) { switch (dialogueLine.Status) { case LineStatus.Running: // Nothing to do here - continue running. break; case LineStatus.Interrupted: // The user wants us to wrap up the audio quickly. The // DoPlayback coroutine will apply the fade out defined // by fadeOutTimeOnLineFinish. finishCurrentLine = true; break; case LineStatus.Delivered: // The line has finished delivery on all views. Nothing // left to do for us, since the audio will have already // finished playing out. break; case LineStatus.Ended: // The line is being dismissed; we should ensure that // audio playback has ended. audioSource.Stop(); break; } }
public override void RunLine(LocalizedLine dialogueLine, Action onDialogueLineFinished) { // Don't do anything with this line except note it and // immediately indicate that we're finished with it. RunOptions // will use it to display the text of the previous line. lastSeenLine = dialogueLine; onDialogueLineFinished(); }
public override void RunLine(LocalizedLine dialogueLine, System.Action onDialogueDeliveryComplete) { StartCoroutine(DoRunLine(dialogueLine, onDialogueDeliveryComplete)); IEnumerator DoRunLine(LocalizedLine dialogueLine, System.Action onDialogueDeliveryComplete) { interrupted = false; // Check if this instance is currently playing back another // voice over in which case we stop it if (lastVoiceOverEvent.isValid()) { lastVoiceOverEvent.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT); } // Create playback event FMOD.Studio.EventInstance dialogueInstance; try { dialogueInstance = FMODUnity.RuntimeManager.CreateInstance(fmodEvent); } catch (Exception) { UnityEngine.Debug.LogWarning("FMOD: Voice over playback failed.", gameObject); throw; } lastVoiceOverEvent = dialogueInstance; // Pin the key string in memory and pass a pointer through the // user data GCHandle stringHandle = GCHandle.Alloc(dialogueLine.TextID.Remove(0, 5), GCHandleType.Pinned); dialogueInstance.setUserData(GCHandle.ToIntPtr(stringHandle)); dialogueInstance.setCallback(dialogueCallback, FMOD.Studio.EVENT_CALLBACK_TYPE.ALL); dialogueInstance.start(); dialogueInstance.release(); while (!interrupted && dialogueInstance.isValid()) { yield return(null); } if (dialogueInstance.isValid()) { dialogueInstance.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT); } } }
public override void DismissLine(Action onDismissalComplete) { currentLine = null; hasPresented.Clear(); if (useFadeEffect) { StartCoroutine(Effects.FadeAlpha(canvasGroup, 1, 0, fadeOutTime, hasPresented, onDismissalComplete)); } else { canvasGroup.interactable = false; canvasGroup.alpha = 0; canvasGroup.blocksRaycasts = false; onDismissalComplete(); } }
public override void InterruptLine(LocalizedLine dialogueLine, Action onInterruptLineFinished) { currentLine = dialogueLine; StopAllCoroutines(); hasPresented.Clear(); // for now we are going to just immediately show everything // later we will make it fade in lineText.gameObject.SetActive(true); canvasGroup.gameObject.SetActive(true); int length; if (continueButton != null) { continueButton.SetActive(false); } if (characterNameText == null) { if (showCharacterNameInLineView) { lineText.text = dialogueLine.Text.Text; length = dialogueLine.Text.Text.Length; } else { lineText.text = dialogueLine.TextWithoutCharacterName.Text; length = dialogueLine.TextWithoutCharacterName.Text.Length; } } else { characterNameText.text = dialogueLine.CharacterName; lineText.text = dialogueLine.TextWithoutCharacterName.Text; length = dialogueLine.TextWithoutCharacterName.Text.Length; } lineText.maxVisibleCharacters = length; canvasGroup.interactable = true; canvasGroup.alpha = 1; canvasGroup.blocksRaycasts = true; onInterruptLineFinished(); }
public override void RunLine(LocalizedLine dialogueLine, Action onDialogueLineFinished) { // Try and get the character name from the line var hasCharacterName = dialogueLine.Text.TryGetAttributeWithName("character", out var characterAttribute); // Did we find one? if (hasCharacterName) { // Then notify the rest of the scene about it. This // generally involves updating a text view and making it // visible. onNameUpdate?.Invoke(characterAttribute.Properties["name"].StringValue); } else { // Otherwise, notify the scene about not finding it. This // generally involves making the name text view not // visible. onNameNotPresent?.Invoke(); } // Immediately mark this view as having finished its work onDialogueLineFinished(); }
public override void RunLine(LocalizedLine dialogueLine, Action onDialogueLineFinished) { // Try and get the character name from the line string characterName = dialogueLine.CharacterName; // Did we find one? if (!string.IsNullOrEmpty(characterName)) { // Then notify the rest of the scene about it. This // generally involves updating a text view and making it // visible. onNameUpdate?.Invoke(characterName); } else { // Otherwise, notify the scene about not finding it. This // generally involves making the name text view not // visible. onNameNotPresent?.Invoke(); } // Immediately mark this view as having finished its work onDialogueLineFinished(); }
public override void InterruptLine(LocalizedLine dialogueLine, Action onDialogueLineFinished) { interrupted = true; onDialogueLineFinished(); }
/// <summary> /// Start playback of the associated voice over <see /// cref="AudioClip"/> of the given <see cref="LocalizedLine"/>. /// </summary> /// <param name="dialogueLine"></param> /// <returns></returns> public override void RunLine(LocalizedLine dialogueLine, Action onDialogueLineFinished) { interrupted = false; if (!(dialogueLine is AudioLocalizedLine audioLine)) { Debug.LogError($"Playing voice over failed because {nameof(RunLine)} expected to receive an {nameof(AudioLocalizedLine)}, but instead received a {dialogueLine?.GetType().ToString() ?? "null"}. Is your {nameof(DialogueRunner)} set up to use a {nameof(AudioLineProvider)}?", gameObject); onDialogueLineFinished(); return; } // Get the localized voice over audio clip var voiceOverClip = audioLine.AudioClip; if (!voiceOverClip) { Debug.Log("Playing voice over failed since the AudioClip of the voice over audio language or the base language was null.", gameObject); onDialogueLineFinished(); return; } if (audioSource.isPlaying) { // Usually, this shouldn't happen because the // DialogueRunner finishes and ends a line first audioSource.Stop(); } StartCoroutine(DoPlayback(voiceOverClip, onDialogueLineFinished)); IEnumerator DoPlayback(AudioClip clip, Action onFinished) { // If we need to wait before starting playback, do this now if (waitTimeBeforeLineStart > 0) { yield return(new WaitForSeconds(waitTimeBeforeLineStart)); } // Start playing the audio. audioSource.PlayOneShot(clip); // Wait until either the audio source finishes playing, or the // interruption flag is set. while (audioSource.isPlaying && !interrupted) { yield return(null); } // If the line was interrupted, we need to wrap up the playback // as quickly as we can. We do this here with a fade-out to // zero over fadeOutTimeOnLineFinish seconds. if (audioSource.isPlaying && interrupted) { // Fade out voice over clip float lerpPosition = 0f; float volumeFadeStart = audioSource.volume; while (audioSource.volume != 0) { lerpPosition += Time.unscaledDeltaTime / fadeOutTimeOnLineFinish; audioSource.volume = Mathf.Lerp(volumeFadeStart, 0, lerpPosition); yield return(null); } audioSource.Stop(); audioSource.volume = volumeFadeStart; } else { audioSource.Stop(); } // We've finished our playback at this point, either by waiting // normally or by interrupting it with a fadeout. If we weren't // interrupted, and we have additional time to wait after the // audio finishes, wait now. (If we were interrupted, we skip // this wait, because the user has already indicated that // they're fine with things moving faster than sounds normal.) if (interrupted == false && waitTimeAfterLineComplete > 0) { yield return(new WaitForSeconds(waitTimeAfterLineComplete)); } // We can now signal that the line delivery has finished. onFinished(); } }
/// <summary> /// Shows a line of dialogue, gradually. /// </summary> /// <param name="dialogueLine">The line to deliver.</param> /// <param name="onDialogueLineFinished">A callback to invoke when /// the text has finished appearing.</param> /// <returns></returns> protected IEnumerator DoRunLine(LocalizedLine dialogueLine, System.Action onDialogueLineFinished) { onLineStart?.Invoke(); finishCurrentLine = false; // The final text we'll be showing for this line. string text; // Are we hiding the character name? if (showCharacterName == false) { // First, check to see if we have it var hasCharacterAttribute = dialogueLine.Text.TryGetAttributeWithName("character", out var characterAttribute); // If we do, remove it from the markup, and use the // resulting text if (hasCharacterAttribute) { text = dialogueLine.Text.DeleteRange(characterAttribute).Text; } else { // This line doesn't have a [character] attribute, so // there's nothing to remove. We'll use the entire // text. text = dialogueLine.Text.Text; } } else { text = dialogueLine.Text.Text; } if (textSpeed > 0.0f) { // Display the line one character at a time var stringBuilder = new StringBuilder(); foreach (char c in text) { stringBuilder.Append(c); onLineUpdate?.Invoke(stringBuilder.ToString()); if (finishCurrentLine) { // We've requested a skip of the entire line. // Display all of the text immediately. onLineUpdate?.Invoke(text); break; } yield return(new WaitForSeconds(textSpeed)); } } else { // Display the entire line immediately if textSpeed <= 0 onLineUpdate?.Invoke(text); } // Indicate to the rest of the game that the text has finished // being delivered onTextFinishDisplaying?.Invoke(); // Indicate to the dialogue runner that we're done delivering // the line here onDialogueLineFinished(); }
public override void OnLineStatusChanged(LocalizedLine dialogueLine) { // We don't need to do anything when the line status changes // for this view }
/// <inheritdoc/> public override void RunLine(LocalizedLine dialogueLine, System.Action onDialogueLineFinished) { StartCoroutine(DoRunLine(dialogueLine, onDialogueLineFinished)); }
public override void RunLine(LocalizedLine dialogueLine, System.Action onDialogueDeliveryComplete) { StartCoroutine(DoRunLine(dialogueLine, onDialogueDeliveryComplete)); }
/// <summary> /// Called by the <see cref="DialogueRunner"/> to signal that a line has /// been interrupted, and that the Dialogue View should finish /// presenting its line as quickly as possible. /// </summary> /// <remarks> /// <para> /// This method is called when Dialogue Runner wants to interrupt the /// presentation of the current line, in order to proceed to the next /// piece of content. /// </para> /// <para> /// When this method is called, the Dialogue View must finish presenting /// their line as quickly as it can. Depending on how this Dialogue View /// presents lines, this can mean different things: for example, a view /// that plays voice-over audio might stop playback immediately, or fade /// out playback over a short period of time; a view that displays text /// a letter at a time might display all of the text at once. /// </para> /// <para> /// The process of finishing the presentation can take time to complete, /// but should happen as quickly as possible, because this method is /// generally called when the user wants to skip the current line. /// </para> /// <para> /// When the line has finished presenting, the <paramref /// name="onDialogueLineFinished"/> method must be called, which /// indicates to the Dialogue Runner that this line is ready to be /// dismissed. /// </para> /// <para style="danger"> /// When <see cref="InterruptLine"/> is called, you must not call the /// completion handler that <see cref="RunLine"/> has previously /// received - this completion handler is no longer valid. Call this method's <paramref name="onDialogueLineFinished"/> instead. /// </para> /// <para style="note"> /// The default implementation of this method immediately calls the /// <paramref name="onDialogueLineFinished"/> method (that is, it /// reports that it has finished presenting the line the moment that it /// receives it), and otherwise does nothing. /// </para> /// </remarks> /// <param name="dialogueLine">The current line that is being /// presented.</param> /// <param name="onDialogueLineFinished">The method that should be /// called after the line has finished being presented.</param> /// <seealso cref="RunLine(LocalizedLine, Action)"/> /// <seealso cref="DismissLine(Action)"/> public virtual void InterruptLine(LocalizedLine dialogueLine, Action onDialogueLineFinished) { // the default implementation does nothing onDialogueLineFinished?.Invoke(); }
/// <summary> /// Called by the DialogueRunner to indicate that the line that /// this view is delivering has changed state. /// </summary> /// <remarks> /// Subclasses of <see cref="DialogueViewBase"/> should override /// this method to be notified when a line has become interrupted, /// and when the line has finished being delivered by all views. /// /// The default implementation does nothing. /// </remarks> /// <param name="dialogueLine">The <see cref="LocalizedLine"/> that /// has changed state.</param> /// <seealso cref="LineStatus"/> public abstract void OnLineStatusChanged(LocalizedLine dialogueLine);
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)); } }
/// <summary> /// Called by the <see cref="DialogueRunner"/> to signal that a line /// should be displayed to the user. /// </summary> /// <remarks> /// <para> /// When this method is called, the Dialogue View should present the /// line to the user. The content to present is contained within the /// <paramref name="dialogueLine"/> parameter, which contains /// information about the line in the user's current locale. /// </para> /// <para style="info">The value of the <paramref name="dialogueLine"/> /// parameter is produced by the Dialogue Runner's <see /// cref="LineProviderBehaviour"/>. /// </para> /// <para> /// It's up to the Dialogue View to decide what "presenting" the line /// may mean; for example, showing on-screen text, or playing voice-over /// audio. /// </para> /// <para>When the line has finished being presented, this method calls /// the <paramref name="onDialogueLineFinished"/> method, which signals /// to the Dialogue Runner that this Dialogue View has finished /// presenting the line. When all Dialogue Views have finished /// presenting the line, the Dialogue Runner calls <see /// cref="DismissLine(Action)"/> to signal that the views should get rid /// of the line.</para> /// <para> /// If you want to create a Dialogue View that waits for user input /// before continuing, either wait for that input before calling /// <paramref name="onDialogueLineFinished"/>, or don't call it at all /// and instead call <see cref="requestInterrupt"/> to tell the Dialogue /// Runner to interrupt the line. /// </para> /// <para style="danger"> /// The <paramref name="onDialogueLineFinished"/> method should only be /// called when <see cref="RunLine"/> finishes its presentation /// normally. If <see cref="InterruptLine"/> has been called, you must /// call the completion handler that it receives, and not the completion /// handler that <see cref="RunLine"/> has received. /// </para> /// <para style="note"> /// The default implementation of this method immediately calls the /// <paramref name="onDialogueLineFinished"/> method (that is, it /// reports that it has finished presenting the line the moment that it /// receives it), and otherwise does nothing. /// </para> /// </remarks> /// <param name="dialogueLine">The content of the line that should be /// presented to the user.</param> /// <param name="onDialogueLineFinished">The method that should be /// called after the line has finished being presented.</param> /// <seealso cref="InterruptLine(LocalizedLine, Action)"/> /// <seealso cref="DismissLine(Action)"/> /// <seealso cref="RunOptions(DialogueOption[], Action{int})"/> public virtual void RunLine(LocalizedLine dialogueLine, Action onDialogueLineFinished) { // The default implementation does nothing, and immediately calls // onDialogueLineFinished. onDialogueLineFinished?.Invoke(); }
/// <summary> /// Called by the <see cref="DialogueRunner"/> to signal that a /// line should be displayed to the user. /// </summary> /// <remarks> /// If this method returns <see /// cref="Dialogue.HandlerExecutionType.ContinueExecution"/>, it /// should not call the <paramref name="onDialogueLineFinished"/> /// method. /// </remarks> /// <param name="dialogueLine">The content of the line that should /// be presented to the user.</param> /// <param name="onDialogueLineFinished">The method that should be /// called after the line has been finished.</param> /// FIXME: If this method is expected to be called only from the /// DialogueRunner then this should be converted into a coroutine /// and merged with RunLineWithCallback(); public abstract void RunLine(LocalizedLine dialogueLine, Action onDialogueLineFinished);