using Ink.Runtime; using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class DialogueManager : MonoBehaviour, IObserver { private static DialogueManager instance; [Header("Prefab Dialogue UI Objects: ")] [SerializeField] private GameObject dialoguePanelPrefab; [SerializeField] private Button buttonPrefab; [SerializeField] private Transform canvasTransform; private VerticalLayoutGroup buttonContainer; // Container for buttons [Header("Observer Type: ")] [SerializeField] private NotificationType notificationType; private Story currentStory; private TextMeshProUGUI dialogueText; private PlayerController playerController; private bool isDisplaying = false; private GameObject dialoguePanelInstance; public Story CurrentStory => currentStory; //Properties public static DialogueManager Instance => instance; private void Awake() { if (instance != null && instance != this) { Destroy(gameObject); // Ensures there's only one instance } else { instance = this; } } // Start is called before the first frame update void Start() { //canvasTransform = GameObject.Find("Canvas").transform; playerController = PlayerController.Instance; } // Update is called once per frame void Update() { //Stops the player movement if the dialogue is active if (!isDisplaying) { playerController.CanMove = true; return; } playerController.CanMove = false; if (Input.GetKeyDown(KeyCode.Return)) { //Continues the story ContinueStory(); } } private IEnumerator WaitForAnimation() { InitDialogue(); // Start the dialogue once the story is set Animator anim = dialoguePanelInstance.GetComponent<Animator>(); float speed = anim.GetCurrentAnimatorStateInfo(0).speed; AnimatorClipInfo[] clipInfo = anim.GetCurrentAnimatorClipInfo(0); float duration = clipInfo[0].clip.length / speed; yield return new WaitForSeconds(duration); ContinueStory(); // Start the story immediately } //Starts the dialogue public void InitDialogue() { dialoguePanelInstance = Instantiate(dialoguePanelPrefab, canvasTransform); buttonContainer = dialoguePanelInstance.GetComponentInChildren<VerticalLayoutGroup>(); dialogueText = dialoguePanelInstance.GetComponentInChildren<TextMeshProUGUI>(); isDisplaying = true; } private void ContinueStory() { if (currentStory.canContinue) { //Set text for the current dialogue line dialogueText.text = currentStory.Continue(); //Display choices, if any, for the current dialogue DisplayChoices(); } else { //Exits the Dialogue if doesn't have more text lines to display StartCoroutine(ExitDialogue()); } } //Exits the dialogue private IEnumerator ExitDialogue() { Animator anim = dialoguePanelInstance.GetComponent<Animator>(); anim.SetTrigger("Close"); float speed = anim.GetCurrentAnimatorStateInfo(0).speed; AnimatorClipInfo[] clipInfo = anim.GetCurrentAnimatorClipInfo(0); float duration = clipInfo[0].clip.length / speed; yield return new WaitForSeconds(duration); isDisplaying = false; dialogueText = null; buttonContainer = null; currentStory = null; Destroy(dialoguePanelInstance); } //Display Choices of the current story dialogue private void DisplayChoices() { //Get the current story choices List<Choice> currentChoices = currentStory.currentChoices; List<GameObject> buttonGameObjects = new List<GameObject>(); ClearChoices(); //if any if (currentChoices.Count > 0) { Debug.Log($"Number of choices: {currentChoices.Count}"); //Create buttons for each choice for (int i = 0; i < currentChoices.Count; i++) { Button button = Instantiate(buttonPrefab, buttonContainer.transform); button.GetComponentInChildren<TextMeshProUGUI>().text = currentChoices[i].text; buttonGameObjects.Add(button.gameObject); // Add click listener to button int choiceIndex = i; // Capture the correct index for the closure button.onClick.AddListener(() => OnChoiceSelected(choiceIndex)); } StartCoroutine(SelectedFirstChoice(buttonGameObjects)); } } // Clear previous dialogue choice buttons private void ClearChoices() { foreach (Transform child in buttonContainer.transform) { Destroy(child.gameObject); // Destroy each button } } // Handle when a choice is selected private void OnChoiceSelected(int choiceIndex) { currentStory.ChooseChoiceIndex(choiceIndex); ContinueStory(); } private IEnumerator SelectedFirstChoice(List<GameObject> choices) { //Event System requires we clear it first, then wait //For at least one frame before we set the current selected object EventSystem.current.SetSelectedGameObject(null); yield return new WaitForEndOfFrame(); EventSystem.current.SetSelectedGameObject(choices[0]); } //Notify the observer public void OnNotify(ObserverNotification notification) { //Check for the notification type if (notification.NotificationType == notificationType) { if (currentStory == null) { //Check if contains the necessary components if (notification.ContainsKey("Story")) { //Adds a story currentStory = notification.GetData<Story>("Story"); //Sets the name of the NPC currentStory.variablesState["npc_name"] = notification.GetData<string>("NPCname"); StartCoroutine(WaitForAnimation()); } } else if(notification.ContainsKey("ExitStory")) { StartCoroutine(ExitDialogue()); } } } }