using System; using UnityEngine; using UnityEngine.UI; using Framework.Support; namespace Framework.Dialogue { /// Extends the dialogue canvas to support animations on its widgets public class AnimatedDialogueCanvas : DialogueCanvas { /// How fast the canvas' animation effects will play public float AnimationDurationSeconds = 1.0f; /// Allows additional preparations to be made for the choice buttons /// Canvas that is hosting the controls /// Speech panel used to show dialogue text /// Choice buttons ordered from top to bottom protected override void InitializeWidgets( Canvas canvas, GameObject speechPanel, Button[] choiceButtons ) { base.InitializeWidgets(canvas, speechPanel, choiceButtons); // Take all the speech panel widgets and remember their colors so we can // alter their transparency to fade the whole speech panel out. if(speechPanel != null) { this.speechPanelWidgets = speechPanel.GetComponentsInChildren(); this.speechPanelColors = getWidgetColors(this.speechPanelWidgets); } // Take all the choice button widgets and remember their colors as well. if(choiceButtons != null) { int choiceButtonCount = choiceButtons.Length; this.choiceButtonWidgets = new Graphic[choiceButtonCount][]; this.choiceButtonColors = new Color[choiceButtonCount][]; for(int index = 0; index < choiceButtonCount; ++index) { Button choiceButton = choiceButtons[index]; this.choiceButtonWidgets[index] = choiceButton.GetComponentsInChildren(); this.choiceButtonColors[index] = getWidgetColors(this.choiceButtonWidgets[index]); } } } /// Called once before new speech is displayed /// Speech that will be displayed protected override void SetupNewSpeech(string speech) { this.showSpeech = !string.IsNullOrEmpty(speech); // Let the speech text be assigned base.SetupNewSpeech(speech); // There may be a frame rendered between setting up and animating. // If the panel started at full visibility, it would flash for // a moment before smoothly fading in. So ensure their opacity is set. PlayShowSpeechAnimation(this.speechAnimationProgress); } /// Called once after the speech panel has faded out completely protected override void TearDownSpeech() { this.showSpeech = false; // Do not call base.TearDownSpeech() as it would insta-hide the speech panel. // We're going to smoothly fade it out, then hide the control! } /// Called once before a new set of choices is displayed /// Choices that will be displayed protected override void SetupNewChoices(string[] choices) { if(this.choicesAnimationProgress == 0.0f) { this.animatedChoiceCount = ArrayHelper.CountNullableArray(choices); this.showChoices = (this.animatedChoiceCount > 0); // Let the button text be assigned base.SetupNewChoices(choices); this.pickedChoiceIndex = -1; // Fade out without highlighting choice // There may be a frame rendered between setting up and animating. // If the buttons started at full visibility, they would flash for // a moment before smoothly fading in. So ensure their opacity is set. PlayShowChoicesAnimation(this.choicesAnimationProgress); } else { this.nextChoices = choices; this.showChoices = false; } } /// Called once after the choices have been faded out completely protected override void TearDownChoices() { this.nextChoices = null; this.showChoices = false; // Do not call base.TearDownSpeech() as it would insta-hide the speech panel. // We're going to smoothly fade it out, then hide the control! } /// Called once per visual frame to update the UI animations protected virtual void Update() { float deltaTime = Time.deltaTime; deltaTime /= this.AnimationDurationSeconds; animateSpeechPanel(deltaTime); animateChoiceButtons(deltaTime); } /// Updates the animation state for showing new dialogue /// Progress of the animation protected virtual void PlayShowSpeechAnimation(float progress) { SetSpeechAlpha(progress); } /// Updates the animation state for hiding the current dialogue /// Progress of the animation protected virtual void PlayHideSpeechAnimation(float progress) { SetSpeechAlpha(progress); } /// Updates the animation state for showing new dialogue /// Progress of the animation protected virtual void PlayShowChoicesAnimation(float progress) { for(int index = 0; index < this.animatedChoiceCount; ++index) { SetChoiceAlpha(index, progress); } } /// Updates the animation state for hiding the current dialogue /// Progress of the animation /// Index of the choice picked by the user or -1 protected virtual void PlayHideChoicesAnimation(float progress, int pickedChoiceIndex) { for(int index = 0; index < this.animatedChoiceCount; ++index) { SetChoiceAlpha(index, progress); } } /// Fires the Selected event when the user has made a choice /// Index of the choice the user has picked /// /// If the current dialogue was only speech, the choice index is -1. /// protected override void OnSelected(int choiceIndex) { this.pickedChoiceIndex = choiceIndex; base.OnSelected(choiceIndex); } /// Handles the speech panel animations /// Time that has passed since the last update private void animateSpeechPanel(float deltaTime) { // Fade the speech panel in or out depending on the desired visibility if(this.showSpeech) { if(this.speechAnimationProgress < 1.0f) { this.speechAnimationProgress = Math.Min( this.speechAnimationProgress + deltaTime, 1.0f ); PlayShowSpeechAnimation(this.speechAnimationProgress); } } else { if(this.speechAnimationProgress > 0.0f) { this.speechAnimationProgress = Math.Max( this.speechAnimationProgress - deltaTime, 0.0f ); PlayHideSpeechAnimation(this.speechAnimationProgress); if(this.speechAnimationProgress == 0.0f) { base.TearDownSpeech(); // Actually disable the panel now. } } } } /// Handles the choice button animations /// Time that has passed since the last update private void animateChoiceButtons(float deltaTime) { // Fade the choice buttons in or out depending on the desired visibility if(this.showChoices) { if(this.choicesAnimationProgress < 1.0f) { this.choicesAnimationProgress = Math.Min( this.choicesAnimationProgress + deltaTime, 1.0f ); PlayShowChoicesAnimation(this.choicesAnimationProgress); } } else { if(this.choicesAnimationProgress > 0.0f) { this.choicesAnimationProgress = Math.Max( this.choicesAnimationProgress - deltaTime, 0.0f ); PlayHideChoicesAnimation(this.choicesAnimationProgress, this.pickedChoiceIndex); // If the buttons have fully faded out, either turn them off or // prepare to bring up the new set of choices if(this.choicesAnimationProgress == 0.0f) { this.animatedChoiceCount = ArrayHelper.CountNullableArray(this.nextChoices); if(this.animatedChoiceCount > 0) { SetupNewChoices(this.nextChoices); this.nextChoices = null; } else { base.TearDownChoices(); // Actually disable the buttons now. } } } } } /// Changes the opacity value of the speech panel /// Opacity value that will be assigned to the speech panel protected void SetSpeechAlpha(float alpha) { alpha *= alpha; for(int index = 0; index < this.speechPanelWidgets.Length; ++index) { Color color = this.speechPanelColors[index]; color.a *= alpha; this.speechPanelWidgets[index].color = color; } } /// Changes the opacity value of a choice button /// Index of the button whose opacity will be changed /// Opacity value that will be assigned to the button protected void SetChoiceAlpha(int buttonIndex, float alpha) { alpha *= alpha; for(int index = 0; index < this.choiceButtonWidgets[buttonIndex].Length; ++index) { Color color = this.choiceButtonColors[buttonIndex][index]; color.a *= alpha; this.choiceButtonWidgets[buttonIndex][index].color = color; } } /// Determines the colors of all widgets in a list /// Widgets whose colors will be determined /// The colors of all widgets in the specified list private static Color[] getWidgetColors(Graphic[] widgets) { int widgetCount = widgets.Length; Color[] colors = new Color[widgetCount]; for(int index = 0; index < widgetCount; ++index) { colors[index] = widgets[index].color; } return colors; } /// Whether the speech panel should be visible private bool showSpeech; /// Current state of the speech panel (0.0 invisible, 1.0 visible) /// /// This animates towards 1.0 or 0.0 depending on /// the field /// private float speechAnimationProgress; /// Whether the choice buttons should be visible private bool showChoices; /// Current state of the choice buttons (0.0 invisible, 1.0 visible) /// /// This animates towards 1.0 or 0.0 depending on /// the field /// private float choicesAnimationProgress; /// Number of choice buttons currently being animated private int animatedChoiceCount; /// Choice the user has picked /// /// This stores the choice made by the user so the 'show' animation can /// play to completion before the event is sent out. This also prevents /// multiple events from being sent out if the user clicks multiple times /// (unless the implementation disables the buttons are after a click event) /// private int pickedChoiceIndex; /// Choices that will be available on the next fade-in /// /// Unlike speech text, if a new set of choice is displayed while the current /// one is still on-screen, the choice buttons are first completely faded out /// and then faded in again with the new text. /// private string[] nextChoices; /// Widgets that make up the speech panel private Graphic[] speechPanelWidgets; /// Original colors of the speech panel private Color[] speechPanelColors; /// Widgets that make up the choice buttons private Graphic[][] choiceButtonWidgets; /// Original colors of the buttons private Color[][] choiceButtonColors; } } // namespace Framework.Dialogue