using System; using System.Collections.Generic; #if TEXTMESHPRO_INSTALLED using TMPro; #endif using UnityEngine; using UnityEngine.UI; namespace EasyTalk.Display { /// /// An implementation of a dialogue option display which allows each option to be attributes to a specific Vector2 direction. /// public class DirectionalOptionDisplay : OptionDisplay { /// /// A List of the directional option elements used by the option display. Each element contains a button and a linked image which is manipulated whenever /// an interaction with a option button occurs. /// [Tooltip("The Directional Option Elements used by the Option Display. Each option element ties an Option Button to an image which will" + "be highlighted as their associated buttons are highlighted, etc..")] [SerializeField] private List optionElements; /// /// The central location of the option display. This location is used to calculate the relative direction to each option unless an option's direction has been overriden. /// [Tooltip("The location of the center of the display. It is from this point that the direction to each Directional Option Element is determined." + "When input comes from the player in a certain direction, that direction is compared to the direction of each Option Element to determine if that" + "is the option which should be selected.")] [SerializeField] private Transform centerTransform; /// /// The main/central image used in the directional display. /// [Tooltip("The main image to use for the directional display.")] [SerializeField] private Image mainImage; /// /// If true, the option display's linked images will use the same colors as their corresponding buttons when in normal, pressed, highlighted, and disabled modes. /// [Tooltip("If set to true, the images linked to the Option Buttons (via Directional Option Elements) will use the same colors as the Option Buttons when" + "options are hovered over, left, pressed, etc..")] [SerializeField] private bool useOptionButtonColors = true; /// /// The color to use on a linked image when in normal mode (only used if useOptionButtonColors is set to false). /// [Tooltip("The color to use for a an image linked to an Option Button when the option isn't selected, disabled, or pressed.")] [SerializeField] [ColorUsage(true, true)] private Color linkNormalColor = new Color(0.8f, 0.8f, 0.8f); /// /// The color to use on a linked image when in highlighted mode (only used if useOptionButtonColors is set to false). /// [Tooltip("The color to use for a an image linked to an Option Button when the option is selected or hovered.")] [SerializeField] [ColorUsage(true, true)] private Color linkHighlightColor = new Color(1.0f, 1.0f, 1.0f); /// /// The color to use on a linked image when in pressed mode (only used if useOptionButtonColors is set to false). /// [Tooltip("The color to use for a an image linked to an Option Button when the option is pressed.")] [SerializeField] [ColorUsage(true, true)] private Color linkPressedColor = new Color(0.6f, 0.6f, 0.6f); /// /// The color to use on a linked image when in disabled mode (only used if useOptionButtonColors is set to false). /// [Tooltip("The color to use for a an image linked to an Option Button when the option is disabled")] [SerializeField] [ColorUsage(true, true)] private Color linkDisabledColor = new Color(0.35f, 0.35f, 0.35f); /// /// The currently selected option element. /// private DirectionalOptionElement currentlySelectedOptionElement; /// /// Initializes the option display. This method sets up the option element links to update when the mouse interacts with their corresponding buttons and sets up the /// actions of the buttons when they are clicked. /// public override void Init() { base.Init(); foreach (DirectionalOptionElement optionElement in optionElements) { if (optionElement.button != null) { SetUpButtonAction(optionElement); if (optionElement.linkedImage != null) { SetUpLinkHighlight(optionElement); SetUpLinkExit(optionElement); SetUpLinkPress(optionElement); } } } } /// /// Sets up the dialogue option button associated with the provided DirectionalOptionElement to choose the currently selected option whenever it is clicked. /// /// The directional option element to set up. private void SetUpButtonAction(DirectionalOptionElement optionElement) { DialogueButton optionButton = optionElement.button; optionButton.onClick.AddListener( delegate { currentlySelectedOptionElement = optionElement; OptionChosen(); } ); } /// /// Sets up the link image for the provided DirectionalOptionElement to update its color when the associated option button is hovered over by the mouse. /// /// The directional option element to set up. private void SetUpLinkHighlight(DirectionalOptionElement optionElement) { optionElement.button.onEnter.AddListener(delegate { if (optionElement.button.IsClickable) { if (currentlySelectedOptionElement != null && currentlySelectedOptionElement.button != null) { currentlySelectedOptionElement.button.DisplayNormal(); if (useOptionButtonColors) { currentlySelectedOptionElement.linkedImage.color = optionElement.button.normalButtonColor; } else { currentlySelectedOptionElement.linkedImage.color = LinkNormalColor; } } if (useOptionButtonColors) { optionElement.linkedImage.color = optionElement.button.highlightedButtonColor; } else { optionElement.linkedImage.color = LinkHighlightColor; } optionElement.button.DisplayHighlighted(); DialogueOption oldOption = (DialogueOption)currentlySelectedOptionElement.button.Value; DialogueOption newOption = (DialogueOption)optionElement.button.Value; currentlySelectedOptionElement = optionElement; if(oldOption != newOption) { OptionChanged(oldOption, newOption); } OptionSelected(newOption); } }); } /// /// Sets up the link image for the provided DirectionalOptionElement to update its color when the associated option button is left by the mouse. /// /// The directional option element to set up. private void SetUpLinkExit(DirectionalOptionElement optionElement) { optionElement.button.onLeave.AddListener(delegate { if (optionElement.button.IsClickable) { if (useOptionButtonColors) { optionElement.linkedImage.color = optionElement.button.normalButtonColor; } else { optionElement.linkedImage.color = linkNormalColor; } } }); } /// /// Sets up the link image for the provided DirectionalOptionElement to update its color when the associated option button is pressed. /// /// The directional option element to set up. private void SetUpLinkPress(DirectionalOptionElement optionElement) { optionElement.button.onPress.AddListener(delegate { if (optionElement.button.IsClickable) { if (useOptionButtonColors) { optionElement.linkedImage.color = optionElement.button.pressedButtonColor; } else { optionElement.linkedImage.color = linkPressedColor; } } }); } /// /// Selects the dialogue option with the specified index from the displayed List of options. /// /// The index of the option to select. /// Whether the button's hover sound should be played. public void SelectOption(int index, bool playSound = true) { DialogueOption oldOption = null; if (currentlySelectedOptionElement != null) { oldOption = (DialogueOption)currentlySelectedOptionElement.button.Value; } currentlySelectedOptionElement = optionElements[index]; HighlightSelectedOption(playSound); DialogueOption newOption = (DialogueOption)currentlySelectedOptionElement.button.Value; if (oldOption != newOption) { OptionChanged(oldOption, newOption); } OptionSelected(newOption); } /// /// Highlight the currently selected option. /// /// Whether the selection sound for the option should be played (default true). protected void HighlightSelectedOption(bool playSound = true) { DialogueButton button = currentlySelectedOptionElement.button; if (playSound && button.isActiveAndEnabled) { button.PlayHoverSound(); } button.DisplayHighlighted(); if (useOptionButtonColors) { currentlySelectedOptionElement.linkedImage.color = button.highlightedButtonColor; } else { currentlySelectedOptionElement.linkedImage.color = linkHighlightColor; } } /// /// Deselects the option at the specified index (from the List of directional option elements). /// /// The index of the option to deselect. public void DeselectOption(int index) { DirectionalOptionElement element = optionElements[index]; DialogueButton button = optionElements[index].button; if (button.IsClickable) { button.DisplayNormal(); } else { button.DisplayDisabled(); } if (useOptionButtonColors) { if (button.IsClickable) { element.linkedImage.color = button.normalButtonColor; } else { element.linkedImage.color = button.disabledButtonColor; } } else { if (button.IsClickable) { element.linkedImage.color = linkNormalColor; } else { element.linkedImage.color = linkDisabledColor; } } } /// /// Returns the directional option element which is currently selected. /// /// The currently selected directional option element. public DirectionalOptionElement GetSelectedOptionElement() { return currentlySelectedOptionElement; } /// /// Returns the link image at the specified index. /// /// The index of the link image to retrieve. /// The link image configured for the directional option element at the specified index. public Image GetLinkImage(int index) { return optionElements[index].linkedImage; } /// /// Returns the dialogue button at the specified index. /// /// The index of the button to retrieve. /// The dialogue button configured for the directional option element at the specified index. public DialogueButton GetButton(int index) { return optionElements[index].button; } /// /// Gets the List of DirectionalOptionElements containing the dialogue option buttons and linked images. /// public List OptionElements { get { return optionElements; } } /// public override bool SelectOptionInDirection(Vector2 direction) { float currentAngle = float.MaxValue; DirectionalOptionElement chosenButtonLinkElement = null; //Find the angle between the center image and each option, choose whichever one has the smallest angle. for (int i = 0; i < OptionElements.Count; i++) { DirectionalOptionElement element = OptionElements[i]; DialogueButton button = element.button; if (button.isActiveAndEnabled && button.IsClickable) { Vector2 buttonDirection = Vector2.zero; if (element.useCustomDirectionVector) { buttonDirection = element.directionVector; } else { buttonDirection = button.transform.position - centerTransform.transform.position; } float buttonAngle = Vector2.Angle(direction, buttonDirection); if (buttonAngle < currentAngle) { currentAngle = buttonAngle; chosenButtonLinkElement = element; } } } if (chosenButtonLinkElement != currentlySelectedOptionElement) { if (currentlySelectedOptionElement != null) { int idx = OptionElements.IndexOf(currentlySelectedOptionElement); DeselectOption(idx); } if (chosenButtonLinkElement != null) { int idx = OptionElements.IndexOf(chosenButtonLinkElement); SelectOption(idx); } } if (currentlySelectedOptionElement != null) { return true; } return false; } /// public override void SetOptions(List options) { OnOptionsSet(options); int buttonIdx = 0; bool isOptionSelected = false; for (int i = 0; i < OptionElements.Count; i++) { DirectionalOptionElement currentElement = OptionElements[i]; bool isButtonActive = false; if(currentElement.activationMask.Count >= options.Count) { isButtonActive = currentElement.activationMask[options.Count - 1]; } DialogueButton optionButton = currentElement.button; optionButton.gameObject.SetActive(isButtonActive); Image linkedImage = currentElement.linkedImage; linkedImage.gameObject.SetActive(isButtonActive); if (isButtonActive) { DialogueOption currentOption = options[buttonIdx]; optionButton.Value = currentOption; optionButton.Interactable = false; optionButton.IsClickable = currentOption.IsSelectable; optionButton.SetText(currentOption.OptionText); buttonIdx++; if (!isOptionSelected && currentOption.IsSelectable) { isOptionSelected = true; currentlySelectedOptionElement = optionElements[i]; } } } } /// protected override void ReadyOptions() { for (int i = 0; i < optionElements.Count; i++) { if (optionElements[i].button.gameObject.activeSelf) { optionElements[i].button.Interactable = true; optionElements[i].button.DisplayNormal(); } } if (currentlySelectedOptionElement != null) { OptionSelected(currentlySelectedOptionElement.button.Value as DialogueOption); } HighlightSelectedOption(true); } /// public override int GetSelectedOption() { return ((DialogueOption)currentlySelectedOptionElement.button.Value).OptionIndex; } /// public override List GetOptionButtons() { List buttons = new List(); foreach (DirectionalOptionElement element in OptionElements) { buttons.Add(element.button); } return buttons; } /// public override void ActivateButtons() { foreach (DirectionalOptionElement element in optionElements) { element.button.Interactable = true; } } /// public override void DeactivateButtons() { foreach (DirectionalOptionElement element in optionElements) { element.button.Interactable = false; } } /// /// Gets or sets the center transform used by the directional option display. /// public Transform CenterTransform { get { return this.centerTransform; } set { this.centerTransform = value; } } /// /// Gets or sets the link images used for each option element. /// public List LinkedImages { get { List images = new List(); foreach(DirectionalOptionElement element in optionElements) { images.Add(element.linkedImage); } return images; } set { for(int i = 0; i < value.Count; i++) { Image currentImage = value[i]; if(optionElements.Count > i) { optionElements[i].linkedImage = currentImage; } } } } /// /// Gets or sets whether the directional option display's option element link images should inherit their colors from the buttons or not. /// public bool UseOptionButtonColors { get { return this.useOptionButtonColors; } set { this.useOptionButtonColors = value; } } /// /// Gets or sets the color to use for option element link images when their corresponding button is in 'normal' mode (only applicable when useOptionButtonColors is false). /// public Color LinkNormalColor { get { return this.linkNormalColor; } set { this.linkNormalColor = value; } } /// /// Gets or sets the color to use for option element link images when their corresponding button is in 'highlighted' mode (only applicable when useOptionButtonColors is false). /// public Color LinkHighlightColor { get { return this.linkHighlightColor; } set { this.linkHighlightColor = value; } } /// /// Gets or sets the color to use for option element link images when their corresponding button is in 'disabled' mode (only applicable when useOptionButtonColors is false). /// public Color LinkDisabledColor { get { return this.linkDisabledColor; } set { this.linkDisabledColor = value; } } /// /// Gets or sets the color to use for option element link images when their corresponding button is in 'pressed' mode (only applicable when useOptionButtonColors is false). /// public Color LinkPressedColor { get { return this.linkPressedColor; } set { this.linkPressedColor = value; } } /// /// Gets or sets the main/central image used by the directional option display. /// public Image MainImage { get { return this.mainImage; } set { this.mainImage = value; } } } /// /// A Directional Option Element establishes a link between a button and another image. When the button is highlighted, the corresponding image will be highlighted, etc.. In /// addition, the element allows for a custom direction to be defined for the element to be selected. /// [Serializable] public class DirectionalOptionElement { /// /// The button to use. /// [Tooltip("The Option Button used for this element.")] [SerializeField] public DialogueButton button; /// /// The linked image. /// [Tooltip("The image linked to the Option Button. It will be highlighted when the Option Button is hovered over, etc..")] [SerializeField] public Image linkedImage; /// /// Whether the option should be attributed to a custom direction vector rather than being based on the relative location of the option. /// [Tooltip("If true, allows a custom direction vector to be used for activating/choosing the option associated with this element.")] [SerializeField] public bool useCustomDirectionVector = false; /// /// The custom direction vector to use when useCustomDirectionVector is set to true. /// [Tooltip("The direction vector to compare to player input to see if this is the chosen option element.")] [SerializeField] public Vector3 directionVector = Vector3.zero; /// /// The mask which determines when the option element is shown/used based on the number of options being presented. /// [Tooltip("This mask determines when the button/link is activated based on the number of options being presented. For example, if 2 options are presented and the second" + " toggle is checked, this element will be activated whenever 2 options are presented, but if the second toggle isn't checked, this option element will not be used.")] [SerializeField] public List activationMask = new List(); } }