using System.Collections; using System.Collections.Generic; #if TEXTMESHPRO_INSTALLED using TMPro; #endif using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; using UnityEngine.UI; namespace EasyTalk.Display { /// /// This is an implementation of a button component used by the dialogue display system. /// public class DialogueButton : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler { #if TEXTMESHPRO_INSTALLED /// /// The text element of the button. /// [Tooltip("The text element used to display option text on the button.")] [SerializeField] private TMP_Text textMeshProText; #endif /// /// The text element of the button. /// [Tooltip("The text element used to display option text on the button.")] [SerializeField] private Text text; /// /// The color of the text when the button is in 'normal' mode. /// [Tooltip("The color to use for the button's text when the button isn't selected/highlighted, disabled, or pressed.")] [SerializeField] [ColorUsage(true)] public Color normalTextColor = new Color(0.85f, 0.85f, 0.85f); /// /// The color of the text when the button is in 'disabled' mode. /// [Tooltip("The color to use for the button's text when the button is disabled.")] [SerializeField] [ColorUsage(true)] public Color disabledTextColor = new Color(0.4f, 0.4f, 0.4f); /// /// The color of the text when the button is in 'highlighted' mode. /// [Tooltip("The color to use for the button's text when the button is selected/highlighted.")] [SerializeField] [ColorUsage(true)] public Color highlightedTextColor = new Color(1.0f, 1.0f, 1.0f); /// /// The color of the text when the button is in 'pressed' mode. /// [Tooltip("The color to use for the button's text when the button is pressed.")] [SerializeField] [ColorUsage(true)] public Color pressedTextColor = new Color(0.7f, 0.7f, 0.7f); /// /// The image used for the button. /// [Tooltip("The background image of button.")] [SerializeField] public Image backgroundImage; /// /// The color of the image when the button is in 'normal' mode. /// [Tooltip("The color to use for the button's image when the button isn't selected/highlighted, disabled, or pressed.")] [SerializeField] [ColorUsage(true)] public Color normalButtonColor = new Color(0.3f, 0.3f, 0.3f); /// /// The color of the image when the button is in 'disabled' mode. /// [Tooltip("The color to use for the button's image when the button is disabled.")] [SerializeField] [ColorUsage(true)] public Color disabledButtonColor = new Color(0.15f, 0.15f, 0.15f); /// /// The color of the image when the button is in 'highlighted' mode. /// [Tooltip("The color to use for the button's image when the button is selected/highlighted.")] [SerializeField] [ColorUsage(true)] public Color highlightedButtonColor = new Color(0.5f, 0.5f, 0.5f); /// /// The color of the image when the button is in 'pressed' mode. /// [Tooltip("The color to use for the button's image when the button is pressed.")] [SerializeField] [ColorUsage(true)] public Color pressedButtonColor = new Color(0.25f, 0.25f, 0.25f); /// /// The sound to play when the player hovers over the button. /// [Tooltip("The sound to play whenever the player hovers over the button.")] [SerializeField] public AudioClip hoverSound; /// /// The audio source used by the button. /// [Tooltip("The Audio Source used by the button. If unset, the button will attempt to find an Audio Source on its parent.")] [SerializeField] private AudioSource audioSource; /// /// A Unity Event that gets triggered when the button is clicked. /// [Tooltip("Callback triggered whenever the button is clicked.")] [SerializeField] public UnityEvent onClick = new UnityEvent(); /// /// A Unity Event that gets triggered when the button is entered by the mouse. /// [Tooltip("Callback triggered whenever the button is entered by the mouse.")] [SerializeField] public UnityEvent onEnter = new UnityEvent(); /// /// A Unity Event that gets triggered when the button is left by the mouse. /// [Tooltip("Callback triggered whenever the button is left by the mouse.")] [SerializeField] public UnityEvent onLeave = new UnityEvent(); /// /// A Unity Event that gets triggered when the button is pressed down. /// [Tooltip("Callback triggered whenever the button is pressed.")] [SerializeField] public UnityEvent onPress = new UnityEvent(); /// /// A Unity Event that gets triggered when the button goes into 'highlighted' mode. /// [Tooltip("Callback triggered whenever the button goes into 'highlighted' mode.")] [SerializeField] public UnityEvent onHighlighted = new UnityEvent(); /// /// A Unity Event that gets triggered when the button goes into 'normal' mode. /// [Tooltip("Callback triggered whenever the button goes back into 'normal' mode.")] [SerializeField] public UnityEvent onNormal = new UnityEvent(); /// /// Whether the button is clickable. /// private bool isClickable = true; /// /// A flag indicating whether the button is ready for a click to be completed (set to true when the button is pressed and the mouse is in the button's area). /// private bool isClickReady = false; /// /// A value attributed to the button. /// private object value; /// /// A collection of temporary audio sources used to queue up sounds to prevent hard cutoffs. /// private List onDemandAudioSources = new List(); /// /// The maximum number of audio sources the button is allowed to create at once. /// private int maxAudioSources = 4; /// /// The index of the next available audio source. /// private int currentSourceIdx = 0; /// /// A flag which controls whether the button is interactable. /// private bool isInteractable = true; /// /// A flag used to prevent butons from being re-highlighted after a button press when the mouse is still on the button. /// private bool allowHighlight = true; /// /// Sets up various callbacks for mouse interactions. /// private void Awake() { onEnter.AddListener(DisplayHighlighted); onEnter.AddListener(PlayHoverSound); onLeave.AddListener(DisplayNormal); onPress.AddListener(DisplayPressed); onClick.AddListener(DisplayHighlighted); } /// /// A flag which controls whether the button is interactable. /// public bool Interactable { get { return this.isInteractable; } set { this.isInteractable = value; if(StandardText != null) { StandardText.raycastTarget = value; } #if TEXTMESHPRO_INSTALLED if (TMPText != null) { TMPText.raycastTarget = value; } #endif if (backgroundImage != null) { backgroundImage.raycastTarget = value; } } } /// /// Called when the mouse pointer is released over the button. /// /// public void OnPointerUp(PointerEventData eventData) { if (!isClickReady || !isInteractable) { return; } if (isClickable && onClick != null) { onClick.Invoke(); } isClickReady = false; } /// /// Called when the mouse is pressed over the button. /// /// public void OnPointerDown(PointerEventData eventData) { if(!isInteractable) { return; } isClickReady = true; allowHighlight = false; if (isClickable && onPress != null) { onPress.Invoke(); } } /// /// Called when the mouse moves over the button. /// /// public void OnPointerEnter(PointerEventData eventData) { if(!isInteractable) { return; } allowHighlight = true; if (isClickable && onEnter != null) { onEnter.Invoke(); } } /// /// Called when the mouse leaves the button. /// /// public void OnPointerExit(PointerEventData eventData) { if(!isInteractable) { return; } isClickReady = false; allowHighlight = true; if (isClickable && onLeave != null) { onLeave.Invoke(); } } /// /// Plays the hover sound set on the button. /// public void PlayHoverSound() { if (hoverSound != null && isClickable) { if (audioSource == null) { audioSource = this.gameObject.AddComponent(); } if(audioSource == null) { return; } if (audioSource.isPlaying) { AudioSource tempAudioSource; if (onDemandAudioSources.Count < maxAudioSources) { tempAudioSource = this.gameObject.AddComponent(); tempAudioSource.volume = audioSource.volume; tempAudioSource.pitch = audioSource.pitch; tempAudioSource.panStereo = audioSource.panStereo; tempAudioSource.mute = audioSource.mute; tempAudioSource.velocityUpdateMode = audioSource.velocityUpdateMode; tempAudioSource.dopplerLevel = audioSource.dopplerLevel; tempAudioSource.loop = audioSource.loop; onDemandAudioSources.Add(tempAudioSource); } else { tempAudioSource = onDemandAudioSources[currentSourceIdx]; currentSourceIdx++; if (currentSourceIdx >= maxAudioSources) { currentSourceIdx = 0; } } StartCoroutine(PlaySoundAsync(tempAudioSource, hoverSound)); } else { audioSource.clip = hoverSound; audioSource.Play(); } } } /// /// Plays the provided sound asynchronously and removes the audio source from the on demand queue once the sound is finished playing. /// /// The audio source to use. /// The audio clip to play. /// private IEnumerator PlaySoundAsync(AudioSource source, AudioClip clip) { source.clip = hoverSound; source.Play(); yield return new WaitForSeconds(clip.length); onDemandAudioSources.Remove(source); Destroy(source); } /// /// Sets the button to display itself in highlighted mode. /// public void DisplayHighlighted() { if(!allowHighlight) { return; } EventSystem eventSystem = null; #if UNITY_2022_3_OR_NEWER eventSystem = GameObject.FindFirstObjectByType(); #else eventSystem = GameObject.FindObjectOfType(); #endif if(eventSystem != null) { eventSystem.SetSelectedGameObject(this.gameObject); } if (StandardText != null) { StandardText.color = new Color(highlightedTextColor.r, highlightedTextColor.g, highlightedTextColor.b, highlightedTextColor.a); } #if TEXTMESHPRO_INSTALLED if (TMPText != null) { TMPText.color = new Color(highlightedTextColor.r, highlightedTextColor.g, highlightedTextColor.b, highlightedTextColor.a); } #endif if (backgroundImage != null) { backgroundImage.color = new Color(highlightedButtonColor.r, highlightedButtonColor.g, highlightedButtonColor.b, highlightedButtonColor.a); } if(onHighlighted != null) { onHighlighted.Invoke(); } } /// /// Sets the button to display itself in normal mode. /// public void DisplayNormal() { if (!isClickable) { DisplayDisabled(); return; } if (StandardText != null) { StandardText.color = new Color(normalTextColor.r, normalTextColor.g, normalTextColor.b, normalTextColor.a); } #if TEXTMESHPRO_INSTALLED if (TMPText != null) { TMPText.color = new Color(normalTextColor.r, normalTextColor.g, normalTextColor.b, normalTextColor.a); } #endif if (backgroundImage != null) { backgroundImage.color = new Color(normalButtonColor.r, normalButtonColor.g, normalButtonColor.b, normalButtonColor.a); } if(onNormal != null) { onNormal.Invoke(); } } /// /// Sets the button to display itself in pressed mode. /// public void DisplayPressed() { if (StandardText != null) { StandardText.color = new Color(pressedTextColor.r, pressedTextColor.g, pressedTextColor.b, pressedTextColor.a); } #if TEXTMESHPRO_INSTALLED if (TMPText != null) { TMPText.color = new Color(pressedTextColor.r, pressedTextColor.g, pressedTextColor.b, pressedTextColor.a); } #endif if (backgroundImage != null) { backgroundImage.color = new Color(pressedButtonColor.r, pressedButtonColor.g, pressedButtonColor.b, pressedButtonColor.a); } } /// /// Sets the button to display itself in disabled mode. /// public void DisplayDisabled() { if (StandardText != null) { StandardText.color = new Color(disabledTextColor.r, disabledTextColor.g, disabledTextColor.b, disabledTextColor.a); } #if TEXTMESHPRO_INSTALLED if (TMPText != null) { TMPText.color = new Color(disabledTextColor.r, disabledTextColor.g, disabledTextColor.b, disabledTextColor.a); } #endif if (backgroundImage != null) { backgroundImage.color = new Color(disabledButtonColor.r, disabledButtonColor.g, disabledButtonColor.b, disabledButtonColor.a); } } /// /// Whether the button is clickable. Returns false if the button is disabled. Setting this value to true will change the button to display in normal mode, and setting it to /// false will set it to display in disabled mode. /// public bool IsClickable { get { return this.isClickable; } set { this.isClickable = value; if (this.isClickable) { DisplayNormal(); } else { DisplayDisabled(); } } } /// /// Gets or sets the value attributed to the button. /// public object Value { get { return this.value; } set { this.value = value; } } /// /// Removes all leftover on-demand audio sources from the button. /// private void OnDisable() { for (int i = 0; i < onDemandAudioSources.Count; i++) { AudioSource source = onDemandAudioSources[i]; Destroy(source); onDemandAudioSources.RemoveAt(i); i--; } } /// /// Sets the text of the button. /// /// The text to use. public void SetText(string text) { if(StandardText != null) { StandardText.text = text; } #if TEXTMESHPRO_INSTALLED if (TMPText != null) { TMPText.text = text; } #endif } /// /// Gets or sets the standard Unity Text component of the button. /// public Text StandardText { get { return this.text; } set { this.text = value; } } #if TEXTMESHPRO_INSTALLED /// /// Gets or sets the TextMeshPro Text component of the button. /// public TMP_Text TMPText { get { return textMeshProText; } set { textMeshProText = value; } } #endif private void OnValidate() { #if TEXTMESHPRO_INSTALLED if (this.TMPText == null) { TMP_Text[] tmpTextComponents = GetComponentsInChildren(true); foreach (TMP_Text txt in tmpTextComponents) { if (txt.gameObject.name.Contains("Text") && txt.gameObject.name.Contains("TMP")) { this.TMPText = txt; break; } } } #endif } } }