ProjectDDD/Assets/Plugins/Pixel Crushers/Dialogue System/Scripts/UI/Unity UI/Dialogue/UnityUIDialogueUI.cs

439 lines
16 KiB (Stored with Git LFS)
C#

// Copyright (c) Pixel Crushers. All rights reserved.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace PixelCrushers.DialogueSystem
{
/// <summary>
/// This component implements IDialogueUI using Unity UI. It's based on
/// CanvasDialogueUI and compiles the Unity UI versions of the controls defined in
/// UnityUISubtitleControls, UnityUIResponseMenuControls, UnityUIAlertControls, etc.
///
/// To use this component, build a UI layout (or drag a pre-built one in the Prefabs folder
/// into your scene) and assign the UI control properties. You must assign a scene instance
/// to the DialogueManager; you can't use prefabs with Unity UI dialogue UIs.
///
/// The required controls are:
/// - NPC subtitle line
/// - PC subtitle line
/// - Response menu buttons
///
/// The other control properties are optional. This component will activate and deactivate
/// controls as they are needed in the conversation.
/// </summary>
[AddComponentMenu("")] // Use wrapper.
public class UnityUIDialogueUI : CanvasDialogueUI
{
/// <summary>
/// The UI root.
/// </summary>
[HideInInspector]
public UnityUIRoot unityUIRoot;
/// <summary>
/// The dialogue controls used in conversations.
/// </summary>
public UnityUIDialogueControls dialogue;
/// <summary>
/// QTE (Quick Time Event) indicators.
/// </summary>
public UnityEngine.UI.Graphic[] qteIndicators;
/// <summary>
/// The alert message controls.
/// </summary>
public UnityUIAlertControls alert;
/// <summary>
/// Set <c>true</c> to always keep a control focused; useful for gamepads.
/// </summary>
[Tooltip("Always keep a control focused; useful for gamepads and keyboard.")]
public bool autoFocus = false;
/// <summary>
/// Allow the dialogue UI to steal focus if a non-dialogue UI panel has it.
/// </summary>
[Tooltip("Allow the dialogue UI to steal focus if a non-dialogue UI panel has it.")]
public bool allowStealFocus = false;
/// <summary>
/// If auto focusing, check on this frequency in seconds that the control is focused.
/// </summary>
[Tooltip("If auto focusing, check on this frequency in seconds that the control is focused.")]
public float autoFocusCheckFrequency = 0.5f;
/// <summary>
/// Set <c>true</c> to look for OverrideUnityUIDialogueControls on actors.
/// </summary>
[Tooltip("Look for OverrideUnityUIDialogueControls on actors.")]
public bool findActorOverrides = true;
/// <summary>
/// Set <c>true</c> to add an EventSystem if one isn't in the scene.
/// </summary>
[Tooltip("Add an EventSystem if one isn't in the scene.")]
public bool addEventSystemIfNeeded = true;
private UnityUIQTEControls m_qteControls;
private float m_nextAutoFocusCheckTime = 0;
private GameObject m_lastSelection = null;
public override AbstractUIRoot uiRootControls
{
get { return unityUIRoot; }
}
public override AbstractDialogueUIControls dialogueControls
{
get { return dialogue; }
}
public override AbstractUIQTEControls qteControls
{
get { return m_qteControls; }
}
public override AbstractUIAlertControls alertControls
{
get { return alert; }
}
private class QueuedAlert
{
public string message;
public float duration;
public QueuedAlert(string message, float duration)
{
this.message = message;
this.duration = duration;
}
}
private Queue<QueuedAlert> alertQueue = new Queue<QueuedAlert>();
// References to the original controls in case an actor temporarily overrides them:
protected UnityUISubtitleControls originalNPCSubtitle;
protected UnityUISubtitleControls originalPCSubtitle;
protected UnityUIResponseMenuControls originalResponseMenu;
// Caches overrides by actor so we only need to search an actor once:
private Dictionary<Transform, OverrideUnityUIDialogueControls> overrideCache = new Dictionary<Transform, OverrideUnityUIDialogueControls>();
private bool isShowingNpcSubtitle = false;
private bool isShowingPcSubtitle = false;
private bool isShowingResponses = false;
#region Initialization
/// <summary>
/// Sets up the component.
/// </summary>
public override void Awake()
{
base.Awake();
FindControls();
alert.DeactivateUIElements();
dialogue.DeactivateUIElements();
Tools.DeprecationWarning(this, "Use StandardDialogueUI instead.");
}
#if !(UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2 || UNITY_5_3)
public virtual void OnEnable()
{
UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded;
}
public virtual void OnDisable()
{
UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
}
#endif
/// <summary>
/// Logs warnings if any critical controls are unassigned.
/// </summary>
private void FindControls()
{
if (addEventSystemIfNeeded) UITools.RequireEventSystem();
m_qteControls = new UnityUIQTEControls(qteIndicators);
if (DialogueDebug.logErrors)
{
if (DialogueDebug.logWarnings)
{
if (dialogue.npcSubtitle.line == null) Debug.LogWarning(string.Format("{0}: UnityUIDialogueUI NPC Subtitle Line needs to be assigned.", DialogueDebug.Prefix));
if (dialogue.pcSubtitle.line == null) Debug.LogWarning(string.Format("{0}: UnityUIDialogueUI PC Subtitle Line needs to be assigned.", DialogueDebug.Prefix));
if (dialogue.responseMenu.buttons.Length == 0 && dialogue.responseMenu.buttonTemplate == null) Debug.LogWarning(string.Format("{0}: UnityUIDialogueUI Response buttons need to be assigned.", DialogueDebug.Prefix));
if (alert.line == null) Debug.LogWarning(string.Format("{0}: UnityUIDialogueUI Alert Line needs to be assigned.", DialogueDebug.Prefix));
}
}
originalNPCSubtitle = dialogue.npcSubtitle;
originalPCSubtitle = dialogue.pcSubtitle;
originalResponseMenu = dialogue.responseMenu;
}
public OverrideUnityUIDialogueControls FindActorOverride(Transform actor)
{
if (actor == null) return null;
if (!overrideCache.ContainsKey(actor))
{
overrideCache.Add(actor, (actor != null) ? actor.GetComponentInChildren<OverrideUnityUIDialogueControls>() : null);
}
return overrideCache[actor];
}
#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2 || UNITY_5_3
public void OnLevelWasLoaded(int level)
{
if (addEventSystemIfNeeded) UITools.RequireEventSystem();
}
#else
public void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode)
{
if (addEventSystemIfNeeded) UITools.RequireEventSystem();
}
#endif
public override void Open()
{
overrideCache.Clear();
base.Open();
dialogue.npcSubtitle.CheckSubtitlePortrait(CharacterType.NPC);
dialogue.pcSubtitle.CheckSubtitlePortrait(CharacterType.PC);
}
#endregion
#region Alerts
public override void ShowAlert(string message, float duration)
{
if (alert.queueAlerts)
{
alertQueue.Enqueue(new QueuedAlert(message, duration));
}
else
{
StartShowingAlert(message, duration);
}
}
private void ShowNextQueuedAlert()
{
if (alertQueue.Count > 0)
{
var queuedAlert = alertQueue.Dequeue();
StartShowingAlert(queuedAlert.message, queuedAlert.duration);
}
}
private void StartShowingAlert(string message, float duration)
{
base.ShowAlert(message, duration);
if (autoFocus) alert.AutoFocus();
}
#endregion
#region Subtitles
public override void ShowSubtitle(Subtitle subtitle)
{
SetIsShowingSubtitle(subtitle, true);
if (findActorOverrides && subtitle != null)
{
var overrideControls = (subtitle.speakerInfo != null) ? FindActorOverride(subtitle.speakerInfo.transform) : null;
if (overrideControls != null) overrideControls.ApplyToDialogueUI(this);
if (subtitle.speakerInfo == null || subtitle.speakerInfo.characterType == CharacterType.NPC)
{
dialogue.npcSubtitle = (overrideControls != null) ? overrideControls.subtitle : originalNPCSubtitle;
}
else
{
dialogue.pcSubtitle = (overrideControls != null) ? overrideControls.subtitle : originalPCSubtitle;
}
}
HideResponses();
CheckForSupercededSubtitle(subtitle.speakerInfo.characterType);
base.ShowSubtitle(subtitle); // Calls UnityUISubtitleControls.ShowSubtitle().
ClearSelection();
CheckSubtitleAutoFocus(subtitle);
}
protected void CheckForSupercededSubtitle(CharacterType characterType)
{
var otherSubtitle = (characterType == CharacterType.NPC) ? dialogue.pcSubtitle : dialogue.npcSubtitle;
if (UITools.CanBeSuperceded(otherSubtitle.uiVisibility) && otherSubtitle.isVisible)
{
otherSubtitle.ForceHide();
}
}
public void CheckSubtitleAutoFocus(Subtitle subtitle)
{
if (autoFocus)
{
if (subtitle.speakerInfo.isPlayer)
{
dialogue.pcSubtitle.AutoFocus(allowStealFocus);
}
else
{
dialogue.npcSubtitle.AutoFocus(allowStealFocus);
}
}
}
protected void SetIsShowingSubtitle(Subtitle subtitle, bool value)
{
if (subtitle == null) return;
if (subtitle.speakerInfo.isNPC)
{
isShowingNpcSubtitle = value;
}
else
{
isShowingPcSubtitle = value;
}
}
public override void HideSubtitle(Subtitle subtitle)
{
SetIsShowingSubtitle(subtitle, false);
base.HideSubtitle(subtitle);
}
#endregion
#region Responses
public override void ShowResponses(Subtitle subtitle, Response[] responses, float timeout)
{
isShowingResponses = true;
if (findActorOverrides)
{
// Use speaker's (NPC's) world space canvas for subtitle reminder, and for menu if set:
var overrideControls = (subtitle != null && subtitle.speakerInfo != null) ? FindActorOverride(subtitle.speakerInfo.transform) : null;
var subtitleReminder = (overrideControls != null) ? overrideControls.subtitleReminder : originalResponseMenu.subtitleReminder;
if (overrideControls != null && overrideControls.responseMenu.panel != null)
{
dialogue.responseMenu = (overrideControls != null && overrideControls.responseMenu.panel != null) ? overrideControls.responseMenu : originalResponseMenu;
}
else
{
// Otherwise use PC's world space canvas for menu if set:
overrideControls = (subtitle != null && subtitle.listenerInfo != null) ? FindActorOverride(subtitle.listenerInfo.transform) : null;
dialogue.responseMenu = (overrideControls != null && overrideControls.responseMenu.panel != null) ? overrideControls.responseMenu : originalResponseMenu;
}
// Either way, use speaker's (NPC's) subtitle reminder:
dialogue.responseMenu.subtitleReminder = subtitleReminder;
}
if (dialogue.responseMenu.showHideController.state == UIShowHideController.State.Hiding)
{
StartCoroutine(ShowResponsesAfterHidden(subtitle, responses, timeout));
}
else
{
base.ShowResponses(subtitle, responses, timeout);
ClearSelection();
CheckResponseMenuAutoFocus();
}
}
private IEnumerator ShowResponsesAfterHidden(Subtitle subtitle, Response[] responses, float timeout)
{
var safeguardTime = Time.realtimeSinceStartup + 5f;
while (dialogue.responseMenu.showHideController.state == UIShowHideController.State.Hiding && Time.realtimeSinceStartup < safeguardTime)
{
yield return null;
}
base.ShowResponses(subtitle, responses, timeout);
ClearSelection();
CheckResponseMenuAutoFocus();
}
public void CheckResponseMenuAutoFocus()
{
if (autoFocus) dialogue.responseMenu.AutoFocus(m_lastSelection, allowStealFocus);
}
public override void HideResponses()
{
isShowingResponses = false;
dialogue.responseMenu.DestroyInstantiatedButtons();
base.HideResponses();
if (isShowingNpcSubtitle && dialogue.responseMenu.subtitleReminder.panel == dialogue.npcSubtitle.panel)
{
dialogue.npcSubtitle.ForceShow(); // We hid the NPC subtitle that's supposed to stay visible. Show it.
}
}
public void ClearSelection()
{
if (autoFocus)
{
UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(null);
m_lastSelection = null;
}
}
#endregion
#region Update
protected int alertQueueCount = 0;
protected bool alertIsVisible;
protected bool alertIsHiding;
public override void Update() // Handle alert queue and auto-focus.
{
base.Update();
alertQueueCount = alertQueue.Count;
alertIsVisible = alert.IsVisible;
alertIsHiding = alert.IsHiding;
// Check alert queue:
if (alertQueue.Count > 0 && alert.queueAlerts && !alert.IsVisible && !(alert.waitForHideAnimation && alert.IsHiding))
{
ShowNextQueuedAlert();
}
// Auto focus dialogue:
if (autoFocus && isOpen)
{
if (UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject != null)
{
m_lastSelection = UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject;
}
if (autoFocusCheckFrequency > 0.001f && Time.realtimeSinceStartup > m_nextAutoFocusCheckTime)
{
m_nextAutoFocusCheckTime = Time.realtimeSinceStartup + autoFocusCheckFrequency;
if (isShowingResponses)
{
dialogue.responseMenu.AutoFocus(m_lastSelection, allowStealFocus);
}
else if (isShowingPcSubtitle)
{
dialogue.pcSubtitle.AutoFocus(allowStealFocus);
}
else if (isShowingNpcSubtitle)
{
dialogue.npcSubtitle.AutoFocus(allowStealFocus);
}
}
}
}
#endregion
}
}