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