using System;
using UnityEngine;
using UnityEngine.UI;
using Framework.Support;
namespace Framework.Dialogue {
/// Variant of the dialogue UI widget manager with sliding animation
///
///
/// This implementation assumes that there's a text area with choice buttons
/// below it. The choice buttons can are animated to slide in from different
/// sides and will smoothly converge on the choice clicked by the user while
/// fading out again.
///
///
/// +---------------------------+ +---------------------------+
/// | Text area | | Text area |
/// | | | |
/// +---------------------------+ +---------------------------+
/// +---------------------------+ +------------+ +------------+
/// | Choice 1 | | Choice 1 | | Choice 2 |
/// +---------------------------+ +------------+ +------------+
/// +---------------------------+ +------------+ +------------+
/// | Choice 2 | | Choice 3 | | Choice 4 |
/// +---------------------------+ +------------+ +------------+
///
///
/// This style is typical of classical adventures and game shows.
/// It is possible to arrange the choice buttons in a 4x4 grid, too,
/// as the slide-in animation uses alternating sides.
///
///
public class SlidingDialogueCanvas : AnimatedDialogueCanvas {
/// 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);
// Obtain the button fade distance (how far the buttons will fly away for
// the slide animations) by checking the size of the canvas
RectTransform canvasTransform = canvas.GetComponent();
this.buttonSlideDistance = canvasTransform.rect.width;
// Pick up the widgets and colors for the choice buttons
int choiceButtonCount = choiceButtons.Length;
this.choiceButtonRoots = choiceButtons;
this.targetChoiceButtonPositions = new Vector3[choiceButtonCount];
for(int index = 0; index < choiceButtonCount; ++index) {
RectTransform rectTransform = choiceButtons[index].GetComponent();
this.targetChoiceButtonPositions[index] = rectTransform.localPosition;
}
}
/// Called once before a new set of choices is displayed
/// Choices that will be displayed
protected override void SetupNewChoices(string[] choices) {
base.SetupNewChoices(choices);
this.slidingChoiceCount = ArrayHelper.CountNullableArray(choices);
}
/// Updates the animation state for showing new dialogue
/// Progress of the animation
protected override void PlayShowChoicesAnimation(float progress) {
base.PlayShowChoicesAnimation(progress);
// Show animation always begins at 0.0 opacity, so we can safely start
// the slide-in without taking the previous state into account.
for(int index = 0; index < this.slidingChoiceCount; ++index) {
Vector3 choiceButtonPosition = this.targetChoiceButtonPositions[index];
choiceButtonPosition.x += getSlideOffset(index, progress);
Transform choiceButtonTransform = this.choiceButtonRoots[index].transform;
choiceButtonTransform.localPosition = choiceButtonPosition;
}
this.slideInAnimationProgress = 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 override void PlayHideChoicesAnimation(float progress, int pickedChoiceIndex) {
if(pickedChoiceIndex == -1) {
// Default transparency animation is good enough for this.
base.PlayHideChoicesAnimation(progress, pickedChoiceIndex);
// Continue at the position the slide-in animation left off to give
// the impression of the choice buttons just continuing their flight
float slideOutAnimationProgress = this.slideInAnimationProgress;
slideOutAnimationProgress += (this.slideInAnimationProgress - progress);
for(int index = 0; index < this.slidingChoiceCount; ++index) {
Vector3 choiceButtonPosition = this.targetChoiceButtonPositions[index];
choiceButtonPosition.x += getSlideOffset(index, slideOutAnimationProgress);
Transform choiceButtonTransform = this.choiceButtonRoots[index].transform;
choiceButtonTransform.localPosition = choiceButtonPosition;
}
} else {
// Fade the choice buttons out at different speeds so the selected
// choice will stay on screen for a little bit longer
float earlyProgress = Math.Max(0.0f, progress * 2.0f - 1.0f);
for(int index = 0; index < this.slidingChoiceCount; ++index) {
if(index == pickedChoiceIndex) {
SetChoiceAlpha(index, progress);
} else {
SetChoiceAlpha(index, earlyProgress);
}
}
// Slide the buttons towards the position of the selected button to
// provide additional feedback to the user on the selected choice.
float slideOutAnimationProgress = Math.Max(
(progress * 2.0f / this.slideInAnimationProgress) - 1.0f, 0.0f
);
for(int index = 0; index < this.slidingChoiceCount; ++index) {
Vector3 choiceButtonPosition = this.targetChoiceButtonPositions[index];
choiceButtonPosition.x += getSlideOffset(index, this.slideInAnimationProgress);
this.choiceButtonRoots[index].transform.localPosition = Vector3.LerpUnclamped(
this.targetChoiceButtonPositions[pickedChoiceIndex],
choiceButtonPosition,
slideOutAnimationProgress
);
}
}
}
/// Calculates the X offset from which a button slides in
/// The X offset from which the button should slide in
///
/// Index of the button whose slide in position will be calculated
///
/// Normalized progress of the sliding animation
private float getSlideOffset(int buttonIndex, float progress) {
if((buttonIndex & 1) == 1) {
return Mathf.LerpUnclamped(+this.buttonSlideDistance, 0.0f, progress);
} else {
return Mathf.LerpUnclamped(-this.buttonSlideDistance, 0.0f, progress);
}
}
/// Button controls the player can click to make choices
private Button[] choiceButtonRoots;
/// Original positions of the choice buttons
private Vector3[] targetChoiceButtonPositions;
/// Distance the buttons will move to the side during fade animations
private float buttonSlideDistance;
/// Number of choice buttons that take part in the sliding animation
private int slidingChoiceCount;
/// How far the slide in animation has progressed
/// >
/// This is used when the user clicks on a button before the slide in animation
/// has finished playing. Having the buttons jump to the center would suck,
/// so we finish up the slide-in animation while already fading out the buttons.
///
private float slideInAnimationProgress;
}
} // namespace Framework.Dialogue