642 lines
24 KiB
C#
642 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
#if TEXTMESHPRO_INSTALLED
|
|
using TMPro;
|
|
#endif
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace EasyTalk.Display
|
|
{
|
|
/// <summary>
|
|
/// An implementation of a dialogue option display which allows each option to be attributes to a specific Vector2 direction.
|
|
/// </summary>
|
|
public class DirectionalOptionDisplay : OptionDisplay
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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<DirectionalOptionElement> optionElements;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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;
|
|
|
|
/// <summary>
|
|
/// The main/central image used in the directional display.
|
|
/// </summary>
|
|
[Tooltip("The main image to use for the directional display.")]
|
|
[SerializeField]
|
|
private Image mainImage;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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;
|
|
|
|
/// <summary>
|
|
/// The color to use on a linked image when in normal mode (only used if useOptionButtonColors is set to false).
|
|
/// </summary>
|
|
[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);
|
|
|
|
/// <summary>
|
|
/// The color to use on a linked image when in highlighted mode (only used if useOptionButtonColors is set to false).
|
|
/// </summary>
|
|
[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);
|
|
|
|
/// <summary>
|
|
/// The color to use on a linked image when in pressed mode (only used if useOptionButtonColors is set to false).
|
|
/// </summary>
|
|
[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);
|
|
|
|
/// <summary>
|
|
/// The color to use on a linked image when in disabled mode (only used if useOptionButtonColors is set to false).
|
|
/// </summary>
|
|
[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);
|
|
|
|
/// <summary>
|
|
/// The currently selected option element.
|
|
/// </summary>
|
|
private DirectionalOptionElement currentlySelectedOptionElement;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets up the dialogue option button associated with the provided DirectionalOptionElement to choose the currently selected option whenever it is clicked.
|
|
/// </summary>
|
|
/// <param name="optionElement">The directional option element to set up.</param>
|
|
private void SetUpButtonAction(DirectionalOptionElement optionElement)
|
|
{
|
|
DialogueButton optionButton = optionElement.button;
|
|
optionButton.onClick.AddListener(
|
|
delegate
|
|
{
|
|
currentlySelectedOptionElement = optionElement;
|
|
OptionChosen();
|
|
}
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets up the link image for the provided DirectionalOptionElement to update its color when the associated option button is hovered over by the mouse.
|
|
/// </summary>
|
|
/// <param name="optionElement">The directional option element to set up.</param>
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets up the link image for the provided DirectionalOptionElement to update its color when the associated option button is left by the mouse.
|
|
/// </summary>
|
|
/// <param name="optionElement">The directional option element to set up.</param>
|
|
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;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets up the link image for the provided DirectionalOptionElement to update its color when the associated option button is pressed.
|
|
/// </summary>
|
|
/// <param name="optionElement">The directional option element to set up.</param>
|
|
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;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects the dialogue option with the specified index from the displayed List of options.
|
|
/// </summary>
|
|
/// <param name="index">The index of the option to select.</param>
|
|
/// <param name="playSound">Whether the button's hover sound should be played.</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Highlight the currently selected option.
|
|
/// </summary>
|
|
/// <param name="playSound">Whether the selection sound for the option should be played (default true).</param>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deselects the option at the specified index (from the List of directional option elements).
|
|
/// </summary>
|
|
/// <param name="index">The index of the option to deselect.</param>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the directional option element which is currently selected.
|
|
/// </summary>
|
|
/// <returns>The currently selected directional option element.</returns>
|
|
public DirectionalOptionElement GetSelectedOptionElement()
|
|
{
|
|
return currentlySelectedOptionElement;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the link image at the specified index.
|
|
/// </summary>
|
|
/// <param name="index">The index of the link image to retrieve.</param>
|
|
/// <returns>The link image configured for the directional option element at the specified index.</returns>
|
|
public Image GetLinkImage(int index)
|
|
{
|
|
return optionElements[index].linkedImage;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the dialogue button at the specified index.
|
|
/// </summary>
|
|
/// <param name="index">The index of the button to retrieve.</param>
|
|
/// <returns>The dialogue button configured for the directional option element at the specified index.</returns>
|
|
public DialogueButton GetButton(int index)
|
|
{
|
|
return optionElements[index].button;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the List of DirectionalOptionElements containing the dialogue option buttons and linked images.
|
|
/// </summary>
|
|
public List<DirectionalOptionElement> OptionElements { get { return optionElements; } }
|
|
|
|
/// <inheritdoc/>
|
|
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;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void SetOptions(List<DialogueOption> 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];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
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);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override int GetSelectedOption()
|
|
{
|
|
return ((DialogueOption)currentlySelectedOptionElement.button.Value).OptionIndex;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override List<DialogueButton> GetOptionButtons()
|
|
{
|
|
List<DialogueButton> buttons = new List<DialogueButton>();
|
|
foreach (DirectionalOptionElement element in OptionElements)
|
|
{
|
|
buttons.Add(element.button);
|
|
}
|
|
|
|
return buttons;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void ActivateButtons()
|
|
{
|
|
foreach (DirectionalOptionElement element in optionElements)
|
|
{
|
|
element.button.Interactable = true;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void DeactivateButtons()
|
|
{
|
|
foreach (DirectionalOptionElement element in optionElements)
|
|
{
|
|
element.button.Interactable = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the center transform used by the directional option display.
|
|
/// </summary>
|
|
public Transform CenterTransform
|
|
{
|
|
get { return this.centerTransform; }
|
|
set { this.centerTransform = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the link images used for each option element.
|
|
/// </summary>
|
|
public List<Image> LinkedImages
|
|
{
|
|
get
|
|
{
|
|
List<Image> images = new List<Image>();
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the directional option display's option element link images should inherit their colors from the buttons or not.
|
|
/// </summary>
|
|
public bool UseOptionButtonColors
|
|
{
|
|
get { return this.useOptionButtonColors; }
|
|
set { this.useOptionButtonColors = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
public Color LinkNormalColor
|
|
{
|
|
get { return this.linkNormalColor; }
|
|
set { this.linkNormalColor = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
public Color LinkHighlightColor
|
|
{
|
|
get { return this.linkHighlightColor; }
|
|
set { this.linkHighlightColor = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
public Color LinkDisabledColor
|
|
{
|
|
get { return this.linkDisabledColor; }
|
|
set { this.linkDisabledColor = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
public Color LinkPressedColor
|
|
{
|
|
get { return this.linkPressedColor; }
|
|
set { this.linkPressedColor = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the main/central image used by the directional option display.
|
|
/// </summary>
|
|
public Image MainImage
|
|
{
|
|
get { return this.mainImage; }
|
|
set { this.mainImage = value; }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class DirectionalOptionElement
|
|
{
|
|
/// <summary>
|
|
/// The button to use.
|
|
/// </summary>
|
|
[Tooltip("The Option Button used for this element.")]
|
|
[SerializeField]
|
|
public DialogueButton button;
|
|
|
|
/// <summary>
|
|
/// The linked image.
|
|
/// </summary>
|
|
[Tooltip("The image linked to the Option Button. It will be highlighted when the Option Button is hovered over, etc..")]
|
|
[SerializeField]
|
|
public Image linkedImage;
|
|
|
|
/// <summary>
|
|
/// Whether the option should be attributed to a custom direction vector rather than being based on the relative location of the option.
|
|
/// </summary>
|
|
[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;
|
|
|
|
/// <summary>
|
|
/// The custom direction vector to use when useCustomDirectionVector is set to true.
|
|
/// </summary>
|
|
[Tooltip("The direction vector to compare to player input to see if this is the chosen option element.")]
|
|
[SerializeField]
|
|
public Vector3 directionVector = Vector3.zero;
|
|
|
|
/// <summary>
|
|
/// The mask which determines when the option element is shown/used based on the number of options being presented.
|
|
/// </summary>
|
|
[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<bool> activationMask = new List<bool>();
|
|
}
|
|
} |