CapersProject/Assets/Plugins/Pixel Crushers/Dialogue System/Scripts/UI/Unity UI/Dialogue/UnityUIResponseMenuControls.cs

543 lines
22 KiB
C#
Raw Normal View History

// Copyright (c) Pixel Crushers. All rights reserved.
using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System.Collections.Generic;
namespace PixelCrushers.DialogueSystem
{
/// <summary>
/// Response menu controls for UnityUIDialogueUI.
/// </summary>
[System.Serializable]
public class UnityUIResponseMenuControls : AbstractUIResponseMenuControls
{
/// <summary>
/// The panel containing the response menu controls. A panel is optional, but you may want one
/// so you can include a background image, panel-wide effects, etc.
/// </summary>
[Tooltip("The panel containing the response menu controls. A panel is optional, but you may want one so you can include a background image, panel-wide effects, etc.")]
public UnityEngine.UI.Graphic panel;
/// <summary>
/// The PC portrait image to show during the response menu.
/// </summary>
[Tooltip("The PC portrait image to show during the response menu.")]
public UnityEngine.UI.Image pcImage;
/// <summary>
/// The label that will show the PC name.
/// </summary>
[Tooltip("The label that will show the PC name.")]
public UnityEngine.UI.Text pcName;
/// <summary>
/// The reminder of the last subtitle.
/// </summary>
[Tooltip("The reminder of the last subtitle.")]
public UnityUISubtitleControls subtitleReminder;
/// <summary>
/// The (optional) timer.
/// </summary>
[Tooltip("The (optional) timer.")]
public UnityEngine.UI.Slider timer;
/// <summary>
/// If ticked, then select the currently-focused response on timeout.
/// </summary>
[Tooltip("Select the currently-focused response on timeout.")]
public bool selectCurrentOnTimeout = false;
/// <summary>
/// The response buttons, if you want to specify buttons at design time.
/// </summary>
[Tooltip("Design-time positioned response buttons")]
public UnityUIResponseButton[] buttons;
[Tooltip("Template from which to instantiate response buttons; optional to use instead of positioned buttons above")]
public UnityUIResponseButton buttonTemplate;
[Tooltip("If using Button Template, instantiated buttons are parented under this GameObject")]
public UnityEngine.UI.Graphic buttonTemplateHolder;
[Tooltip("Optional scrollbar if the instantiated button holder is in a scroll rect")]
public UnityEngine.UI.Scrollbar buttonTemplateScrollbar;
[Tooltip("Reset the scroll bar to this value when preparing the response menu")]
public float buttonTemplateScrollbarResetValue = 1;
[Tooltip("Automatically set up explicit navigation for instantiated template buttons instead of using Automatic navigation")]
public bool explicitNavigationForTemplateButtons = true;
[Tooltip("If explicit navigation is enabled, loop around when navigating past end of menu")]
public bool loopExplicitNavigation = false;
public UIAutonumberSettings autonumber = new UIAutonumberSettings();
public UIAnimationTransitions animationTransitions = new UIAnimationTransitions();
public UnityEvent onContentChanged = new UnityEvent();
/// <summary>
/// The instantiated buttons. These are only valid during a specific response menu,
/// and only if you're using templates. Each showing of the response menu clears
/// this list and re-populates it with new buttons.
/// </summary>
[HideInInspector]
public List<GameObject> instantiatedButtons = new List<GameObject>();
/// <summary>
/// Assign this delegate if you want it to replace the default timeout handler.
/// </summary>
public System.Action TimeoutHandler = null;
public bool isVisible
{
get
{
return (panel != null) ? panel.gameObject.activeInHierarchy : false;
}
}
private UIShowHideController m_showHideController = null;
public UIShowHideController showHideController
{
get
{
if (m_showHideController == null)
{
m_showHideController = new UIShowHideController(null, panel, animationTransitions.transitionMode, animationTransitions.debug);
m_showHideController.state = UIShowHideController.State.Hidden;
}
return m_showHideController;
}
}
private UnityUITimer unityUITimer = null;
private Sprite pcPortraitSprite = null;
private string pcPortraitName = null;
private Animator animator = null;
private bool lookedForAnimator = false;
/// <summary>
/// Sets the PC portrait name and sprite to use in the response menu.
/// </summary>
/// <param name="portraitSprite">Portrait sprite.</param>
/// <param name="portraitName">Portrait name.</param>
public override void SetPCPortrait(Sprite portraitSprite, string portraitName)
{
pcPortraitSprite = portraitSprite;
pcPortraitName = portraitName;
}
/// <summary>
/// Sets the Portrait sprite to use in the response menu if the named actor is the player.
/// This is used to immediately update the GUI control if the SetPortrait() sequencer
/// command changes the Portrait sprite.
/// </summary>
/// <param name="actorName">Actor name in database.</param>
/// <param name="portraitSprite">Portrait sprite.</param>
public override void SetActorPortraitSprite(string actorName, Sprite portraitSprite)
{
if (string.Equals(actorName, pcPortraitName))
{
var actorPortraitSprite = AbstractDialogueUI.GetValidPortraitSprite(actorName, portraitSprite);
pcPortraitSprite = actorPortraitSprite;
if ((pcImage != null) && (DialogueManager.masterDatabase.IsPlayer(actorName)))
{
pcImage.sprite = actorPortraitSprite;
}
}
}
public override AbstractUISubtitleControls subtitleReminderControls
{
get { return subtitleReminder; }
}
/// <summary>
/// Sets the controls active/inactive, except this method never activates the timer. If the
/// UI's display settings specify a timeout, then the UI will call StartTimer() to manually
/// activate the timer.
/// </summary>
/// <param name='value'>
/// Value (<c>true</c> for active; otherwise inactive).
/// </param>
public override void SetActive(bool value)
{
subtitleReminder.SetActive(value && subtitleReminder.HasText);
Tools.SetGameObjectActive(buttonTemplate, false);
foreach (var button in buttons)
{
if (button != null)
{
if (value == true)
{
Tools.SetGameObjectActive(button, button.visible);
}
else
{
button.clickable = false;
}
}
}
Tools.SetGameObjectActive(timer, false);
Tools.SetGameObjectActive(pcName, value);
Tools.SetGameObjectActive(pcImage, value);
if (value == true)
{
if ((pcImage != null) && (pcPortraitSprite != null)) pcImage.sprite = pcPortraitSprite;
if ((pcName != null) && (pcPortraitName != null)) pcName.text = pcPortraitName;
Tools.SetGameObjectActive(panel, true);
animationTransitions.ClearTriggers(showHideController);
showHideController.Show(animationTransitions.showTrigger, false, null);
if (explicitNavigationForTemplateButtons) SetupTemplateButtonNavigation();
}
else
{
if (isVisible && CanTriggerAnimation(animationTransitions.hideTrigger))
{
animationTransitions.ClearTriggers(showHideController);
showHideController.Hide(animationTransitions.hideTrigger, DeactivateUIElements);
}
else
{
if (panel != null) Tools.SetGameObjectActive(panel, false);
}
}
}
private void DeactivateUIElements()
{
if (panel != null) Tools.SetGameObjectActive(panel, false);
ClearResponseButtons();
}
/// <summary>
/// Clears the response buttons.
/// </summary>
protected override void ClearResponseButtons()
{
DestroyInstantiatedButtons();
if (buttons != null)
{
for (int i = 0; i < buttons.Length; i++)
{
if (buttons[i] == null) continue;
buttons[i].Reset();
buttons[i].visible = showUnusedButtons;
}
}
}
/// <summary>
/// Sets the response buttons.
/// </summary>
/// <param name='responses'>
/// Responses.
/// </param>
/// <param name='target'>
/// Target that will receive OnClick events from the buttons.
/// </param>
protected override void SetResponseButtons(Response[] responses, Transform target)
{
DestroyInstantiatedButtons();
if ((buttons != null) && (responses != null))
{
int buttonNumber = 0;
// Add explicitly-positioned buttons:
for (int i = 0; i < responses.Length; i++)
{
if (responses[i].formattedText.position != FormattedText.NoAssignedPosition)
{
int position = responses[i].formattedText.position;
if (0 <= position && position < buttons.Length && buttons[position] != null)
{
SetResponseButton(buttons[position], responses[i], target, buttonNumber++);
}
else
{
Debug.LogWarning(DialogueDebug.Prefix + ": Buttons list doesn't contain a button for position " + position);
}
}
}
if ((buttonTemplate != null) && (buttonTemplateHolder != null))
{
// Reset scrollbar to top:
if (buttonTemplateScrollbar != null)
{
buttonTemplateScrollbar.value = buttonTemplateScrollbarResetValue;
}
// Instantiate buttons from template:
for (int i = 0; i < responses.Length; i++)
{
if (responses[i].formattedText.position != FormattedText.NoAssignedPosition) continue;
GameObject buttonGameObject = GameObject.Instantiate(buttonTemplate.gameObject) as GameObject;
if (buttonGameObject == null)
{
Debug.LogError(string.Format("{0}: Couldn't instantiate response button template", DialogueDebug.Prefix));
}
else
{
instantiatedButtons.Add(buttonGameObject);
buttonGameObject.transform.SetParent(buttonTemplateHolder.transform, false);
buttonGameObject.SetActive(true);
UnityUIResponseButton responseButton = buttonGameObject.GetComponent<UnityUIResponseButton>();
SetResponseButton(responseButton, responses[i], target, buttonNumber++);
if (responseButton != null) buttonGameObject.name = "Response: " + responseButton.Text;
}
}
}
else
{
// Auto-position remaining buttons:
if (buttonAlignment == ResponseButtonAlignment.ToFirst)
{
// Align to first, so add in order to front:
for (int i = 0; i < Mathf.Min(buttons.Length, responses.Length); i++)
{
if (responses[i].formattedText.position == FormattedText.NoAssignedPosition)
{
int position = Mathf.Clamp(GetNextAvailableResponseButtonPosition(0, 1), 0, buttons.Length - 1);
SetResponseButton(buttons[position], responses[i], target, buttonNumber++);
}
}
}
else
{
// Align to last, so add in reverse order to back:
for (int i = Mathf.Min(buttons.Length, responses.Length) - 1; i >= 0; i--)
{
if (responses[i].formattedText.position == FormattedText.NoAssignedPosition)
{
int position = Mathf.Clamp(GetNextAvailableResponseButtonPosition(buttons.Length - 1, -1), 0, buttons.Length - 1);
SetResponseButton(buttons[position], responses[i], target, buttonNumber++);
}
}
}
}
}
NotifyContentChanged();
}
private void SetResponseButton(UnityUIResponseButton button, Response response, Transform target, int buttonNumber)
{
if (button != null)
{
button.visible = true;
button.clickable = response.enabled;
button.target = target;
if (response != null) button.SetFormattedText(response.formattedText);
button.response = response;
// Auto-number:
if (autonumber.enabled)
{
button.Text = string.Format(autonumber.format, buttonNumber + 1, button.Text);
var keyTrigger = button.GetComponent<UIButtonKeyTrigger>();
if (autonumber.regularNumberHotkeys)
{
if (keyTrigger == null) keyTrigger = button.gameObject.AddComponent<UIButtonKeyTrigger>();
keyTrigger.key = (KeyCode)((int)KeyCode.Alpha1 + buttonNumber);
}
if (autonumber.numpadHotkeys)
{
if (autonumber.regularNumberHotkeys || keyTrigger == null) keyTrigger = button.gameObject.AddComponent<UIButtonKeyTrigger>();
keyTrigger.key = (KeyCode)((int)KeyCode.Keypad1 + buttonNumber);
}
}
}
}
private int GetNextAvailableResponseButtonPosition(int start, int direction)
{
if (buttons != null)
{
int position = start;
while ((0 <= position) && (position < buttons.Length))
{
if (buttons[position].visible && buttons[position].response != null)
{
position += direction;
}
else
{
return position;
}
}
}
return 5;
}
public void SetupTemplateButtonNavigation()
{
// Assumes buttons are active (since uses GetComponent), so call after activating panel.
if (instantiatedButtons == null || instantiatedButtons.Count == 0) return;
for (int i = 0; i < instantiatedButtons.Count; i++)
{
var button = instantiatedButtons[i].GetComponent<UnityUIResponseButton>().button;
var above = (i == 0) ? (loopExplicitNavigation ? instantiatedButtons[instantiatedButtons.Count - 1].GetComponent<UnityUIResponseButton>().button : null)
: instantiatedButtons[i - 1].GetComponent<UnityUIResponseButton>().button;
var below = (i == instantiatedButtons.Count - 1) ? (loopExplicitNavigation ? instantiatedButtons[0].GetComponent<UnityUIResponseButton>().button : null)
: instantiatedButtons[i + 1].GetComponent<UnityUIResponseButton>().button;
var navigation = new UnityEngine.UI.Navigation();
navigation.mode = UnityEngine.UI.Navigation.Mode.Explicit;
navigation.selectOnUp = above;
navigation.selectOnLeft = above;
navigation.selectOnDown = below;
navigation.selectOnRight = below;
button.navigation = navigation;
}
}
public void DestroyInstantiatedButtons()
{
foreach (var instantiatedButton in instantiatedButtons)
{
GameObject.Destroy(instantiatedButton);
}
instantiatedButtons.Clear();
NotifyContentChanged();
}
public void NotifyContentChanged()
{
onContentChanged.Invoke();
}
/// <summary>
/// Starts the timer.
/// </summary>
/// <param name='timeout'>
/// Timeout duration in seconds.
/// </param>
public override void StartTimer(float timeout)
{
if (timer != null)
{
if (unityUITimer == null)
{
Tools.SetGameObjectActive(timer, true);
unityUITimer = timer.GetComponent<UnityUITimer>();
if (unityUITimer == null) unityUITimer = timer.gameObject.AddComponent<UnityUITimer>();
Tools.SetGameObjectActive(timer, false);
}
if (unityUITimer != null)
{
Tools.SetGameObjectActive(timer, true);
unityUITimer.StartCountdown(timeout, OnTimeout);
}
else
{
if (DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: No UnityUITimer component found on timer", DialogueDebug.Prefix));
}
}
}
/// <summary>
/// This method is called if the timer runs out. It selects the first response.
/// </summary>
public virtual void OnTimeout()
{
if (TimeoutHandler != null)
{
TimeoutHandler.Invoke();
}
else
{
DefaultTimeoutHandler();
}
}
public void DefaultTimeoutHandler()
{
if (selectCurrentOnTimeout || DialogueManager.displaySettings.inputSettings.responseTimeoutAction == ResponseTimeoutAction.ChooseCurrentResponse)
{
var currentButton = (UnityEngine.EventSystems.EventSystem.current != null && UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject != null)
? UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject.GetComponent<UnityUIResponseButton>() : null;
if (currentButton != null)
{
currentButton.OnClick();
return;
}
}
DialogueManager.instance.SendMessage(DialogueSystemMessages.OnConversationTimeout);
}
/// <summary>
/// Auto-focuses the first response. Useful for gamepads.
/// </summary>
public void AutoFocus(GameObject lastSelection = null, bool allowStealFocus = true)
{
if (UnityEngine.EventSystems.EventSystem.current == null) return;
var currentSelection = UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject;
if (currentSelection == null)
{
currentSelection = lastSelection;
UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(lastSelection);
}
// If anything is selected and we're not allowed to steal focus, stop:
if (currentSelection != null && !allowStealFocus) return;
// If a button is already auto-focused, keep it focused and stop there:
if (instantiatedButtons.Find(x => x.gameObject == currentSelection) != null) return;
for (int i = 0; i < buttons.Length; i++)
{
if (buttons[i] != null && buttons[i].gameObject == currentSelection) return;
}
if (instantiatedButtons.Count > 0)
{
// Otherwise try to focus an instantiated button:
UITools.Select(instantiatedButtons[0].GetComponent<UnityEngine.UI.Button>(), allowStealFocus);
}
else
{
// Failing that, focus a designed button:
for (int i = 0; i < buttons.Length; i++)
{
if (buttons[i] != null && buttons[i].clickable)
{
UITools.Select(buttons[i].button, allowStealFocus);
return;
}
}
}
}
private bool CanTriggerAnimation(string triggerName)
{
return CanTriggerAnimations() && !string.IsNullOrEmpty(triggerName);
}
private bool CanTriggerAnimations()
{
if ((animator == null) && !lookedForAnimator)
{
lookedForAnimator = true;
if (panel != null) animator = panel.GetComponentInParent<Animator>();
}
return (animator != null) && (animationTransitions != null);
}
}
}