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