// This code is part of the Fungus library (http://fungusgames.com) maintained by Chris Gregan (http://twitter.com/gofungus).
// It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE)
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.EventSystems;
using System.Linq;
using MoonSharp.Interpreter;
namespace Fungus
{
///
/// Presents multiple choice buttons to the players.
///
public class MenuDialog : MonoBehaviour
{
[Tooltip("Automatically select the first interactable button when the menu is shown.")]
[SerializeField] protected bool autoSelectFirstButton = false;
protected Button[] cachedButtons;
protected Slider cachedSlider;
protected virtual void Awake()
{
Button[] optionButtons = GetComponentsInChildren();
cachedButtons = optionButtons;
Slider timeoutSlider = GetComponentInChildren();
cachedSlider = timeoutSlider;
if (Application.isPlaying)
{
// Don't auto disable buttons in the editor
Clear();
}
}
protected virtual void OnEnable()
{
// The canvas may fail to update if the menu dialog is enabled in the first game frame.
// To fix this we just need to force a canvas update when the object is enabled.
Canvas.ForceUpdateCanvases();
}
protected virtual IEnumerator WaitForTimeout(float timeoutDuration, Block targetBlock)
{
float elapsedTime = 0;
Slider timeoutSlider = GetComponentInChildren();
while (elapsedTime < timeoutDuration)
{
if (timeoutSlider != null)
{
float t = 1f - elapsedTime / timeoutDuration;
timeoutSlider.value = t;
}
elapsedTime += Time.deltaTime;
yield return null;
}
Clear();
gameObject.SetActive(false);
HideSayDialog();
if (targetBlock != null)
{
targetBlock.StartExecution();
}
}
#region Public members
///
/// Currently active Menu Dialog used to display Menu options
///
public static MenuDialog ActiveMenuDialog { get; set; }
///
/// Returns a menu dialog by searching for one in the scene or creating one if none exists.
///
public static MenuDialog GetMenuDialog()
{
if (ActiveMenuDialog == null)
{
// Use first Menu Dialog found in the scene (if any)
var md = GameObject.FindObjectOfType();
if (md != null)
{
ActiveMenuDialog = md;
}
if (ActiveMenuDialog == null)
{
// Auto spawn a menu dialog object from the prefab
GameObject prefab = Resources.Load("Prefabs/MenuDialog");
if (prefab != null)
{
GameObject go = Instantiate(prefab) as GameObject;
go.SetActive(false);
go.name = "MenuDialog";
ActiveMenuDialog = go.GetComponent();
}
}
}
return ActiveMenuDialog;
}
///
/// A cached list of button objects in the menu dialog.
///
/// The cached buttons.
public virtual Button[] CachedButtons { get { return cachedButtons; } }
///
/// A cached slider object used for the timer in the menu dialog.
///
/// The cached slider.
public virtual Slider CachedSlider { get { return cachedSlider; } }
///
/// Sets the active state of the Menu Dialog gameobject.
///
public virtual void SetActive(bool state)
{
gameObject.SetActive(state);
}
///
/// Clear all displayed options in the Menu Dialog.
///
public virtual void Clear()
{
StopAllCoroutines();
Button[] optionButtons = GetComponentsInChildren();
foreach (UnityEngine.UI.Button button in optionButtons)
{
button.onClick.RemoveAllListeners();
}
foreach (UnityEngine.UI.Button button in optionButtons)
{
if (button != null)
{
button.gameObject.SetActive(false);
}
}
Slider timeoutSlider = GetComponentInChildren();
if (timeoutSlider != null)
{
timeoutSlider.gameObject.SetActive(false);
}
}
///
/// Hides any currently displayed Say Dialog.
///
public virtual void HideSayDialog()
{
var sayDialog = SayDialog.GetSayDialog();
if (sayDialog != null)
{
sayDialog.FadeWhenDone = true;
}
}
///
/// Adds the option to the list of displayed options. Calls a Block when selected.
/// Will cause the Menu dialog to become visible if it is not already visible.
///
/// true , if the option was added successfully.
/// The option text to display on the button.
/// If false, the option is displayed but is not selectable.
/// Block to execute when the option is selected.
public virtual bool AddOption(string text, bool interactable, Block targetBlock)
{
bool addedOption = false;
foreach (Button button in cachedButtons)
{
if (!button.gameObject.activeSelf)
{
button.gameObject.SetActive(true);
button.interactable = interactable;
if (interactable && autoSelectFirstButton && !cachedButtons.Select((x) => x.gameObject).Contains(EventSystem.current.currentSelectedGameObject))
{
EventSystem.current.SetSelectedGameObject(button.gameObject);
}
Text textComponent = button.GetComponentInChildren();
if (textComponent != null)
{
textComponent.text = text;
}
var block = targetBlock;
button.onClick.AddListener(delegate {
EventSystem.current.SetSelectedGameObject(null);
StopAllCoroutines(); // Stop timeout
Clear();
HideSayDialog();
if (block != null)
{
#if UNITY_EDITOR
// Select the new target block in the Flowchart window
var flowchart = block.GetFlowchart();
flowchart.SelectedBlock = block;
#endif
gameObject.SetActive(false);
block.StartExecution();
}
});
addedOption = true;
break;
}
}
return addedOption;
}
///
/// Adds the option to the list of displayed options, calls a Lua function when selected.
/// Will cause the Menu dialog to become visible if it is not already visible.
///
/// true , if the option was added successfully.
public bool AddOption(string text, bool interactable, ILuaEnvironment luaEnv, Closure callBack)
{
if (!gameObject.activeSelf)
{
gameObject.SetActive(true);
}
bool addedOption = false;
foreach (Button button in CachedButtons)
{
if (!button.gameObject.activeSelf)
{
button.gameObject.SetActive(true);
button.interactable = interactable;
Text textComponent = button.GetComponentInChildren();
if (textComponent != null)
{
textComponent.text = text;
}
button.onClick.AddListener(delegate {
StopAllCoroutines(); // Stop timeout
Clear();
HideSayDialog();
if (callBack != null)
{
luaEnv.RunLuaFunction(callBack, true);
}
});
addedOption = true;
break;
}
}
return addedOption;
}
///
/// Show a timer during which the player can select an option. Calls a Block when the timer expires.
///
/// The duration during which the player can select an option.
/// Block to execute if the player does not select an option in time.
public virtual void ShowTimer(float duration, Block targetBlock)
{
if (cachedSlider != null)
{
cachedSlider.gameObject.SetActive(true);
gameObject.SetActive(true);
StopAllCoroutines();
StartCoroutine(WaitForTimeout(duration, targetBlock));
}
}
///
/// Show a timer during which the player can select an option. Calls a Lua function when the timer expires.
///
public IEnumerator ShowTimer(float duration, ILuaEnvironment luaEnv, Closure callBack)
{
if (CachedSlider == null ||
duration <= 0f)
{
yield break;
}
CachedSlider.gameObject.SetActive(true);
StopAllCoroutines();
float elapsedTime = 0;
Slider timeoutSlider = GetComponentInChildren();
while (elapsedTime < duration)
{
if (timeoutSlider != null)
{
float t = 1f - elapsedTime / duration;
timeoutSlider.value = t;
}
elapsedTime += Time.deltaTime;
yield return null;
}
Clear();
gameObject.SetActive(false);
HideSayDialog();
if (callBack != null)
{
luaEnv.RunLuaFunction(callBack, true);
}
}
///
/// Returns true if the Menu Dialog is currently displayed.
///
public virtual bool IsActive()
{
return gameObject.activeInHierarchy;
}
///
/// Returns the number of currently displayed options.
///
public virtual int DisplayedOptionsCount
{
get {
int count = 0;
foreach (Button button in cachedButtons)
{
if (button.gameObject.activeSelf)
{
count++;
}
}
return count;
}
}
#endregion
}
}