405 lines
12 KiB
C#
405 lines
12 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
public class DialogueManager : MonoBehaviour
|
|
{
|
|
public static DialogueManager Instance { get; private set; }
|
|
|
|
[Header("UI")]
|
|
public GameObject dialoguePanel;
|
|
public Text speakerText;
|
|
public Text npcText;
|
|
public Transform responseContainer;
|
|
public ScrollRect responseScrollRect;
|
|
public GameObject responseButtonPrefab;
|
|
public Text hintText;
|
|
public Text statusText;
|
|
|
|
[Header("Scene References")]
|
|
public Inventory playerInventory;
|
|
public SimplePlayerController playerController;
|
|
|
|
[Header("Typewriter")]
|
|
public float characterDelay = 0.025f;
|
|
|
|
public bool IsDialogueActive { get; private set; }
|
|
|
|
private DialogueNode currentNode;
|
|
private string currentNpcLine;
|
|
private Coroutine typewriterRoutine;
|
|
private bool isTyping;
|
|
private string overrideSpeakerName;
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance != null && Instance != this)
|
|
{
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
|
|
Instance = this;
|
|
|
|
if (dialoguePanel != null)
|
|
dialoguePanel.SetActive(false);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!IsDialogueActive)
|
|
return;
|
|
|
|
if (isTyping && (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0)))
|
|
SkipTypewriter();
|
|
|
|
if (Input.GetKeyDown(KeyCode.Escape))
|
|
EndDialogue();
|
|
}
|
|
|
|
public void StartDialogue(DialogueNode startNode, string npcDisplayName = null)
|
|
{
|
|
if (IsDialogueActive || startNode == null)
|
|
return;
|
|
|
|
IsDialogueActive = true;
|
|
overrideSpeakerName = npcDisplayName;
|
|
|
|
if (dialoguePanel != null)
|
|
dialoguePanel.SetActive(true);
|
|
|
|
SetHint(string.Empty);
|
|
SetStatus(string.Empty);
|
|
|
|
if (playerController != null)
|
|
playerController.SetInputEnabled(false);
|
|
else
|
|
{
|
|
Cursor.visible = true;
|
|
Cursor.lockState = CursorLockMode.None;
|
|
}
|
|
|
|
ShowNode(startNode);
|
|
}
|
|
|
|
public void ShowNode(DialogueNode node)
|
|
{
|
|
if (node == null)
|
|
{
|
|
EndDialogue();
|
|
return;
|
|
}
|
|
|
|
currentNode = node;
|
|
currentNpcLine = string.IsNullOrEmpty(node.npcLine) ? string.Empty : node.npcLine;
|
|
|
|
if (speakerText != null)
|
|
{
|
|
string speaker = !string.IsNullOrEmpty(node.speakerName) ? node.speakerName : overrideSpeakerName;
|
|
speakerText.text = string.IsNullOrEmpty(speaker) ? "NPC" : speaker;
|
|
}
|
|
|
|
ClearResponses();
|
|
|
|
if (typewriterRoutine != null)
|
|
StopCoroutine(typewriterRoutine);
|
|
|
|
typewriterRoutine = StartCoroutine(TypeLine(currentNpcLine));
|
|
}
|
|
|
|
public void EndDialogue()
|
|
{
|
|
if (!IsDialogueActive)
|
|
return;
|
|
|
|
if (typewriterRoutine != null)
|
|
{
|
|
StopCoroutine(typewriterRoutine);
|
|
typewriterRoutine = null;
|
|
}
|
|
|
|
isTyping = false;
|
|
currentNode = null;
|
|
ClearResponses();
|
|
|
|
if (dialoguePanel != null)
|
|
dialoguePanel.SetActive(false);
|
|
|
|
if (playerController != null)
|
|
playerController.SetInputEnabled(true);
|
|
else
|
|
{
|
|
Cursor.visible = false;
|
|
Cursor.lockState = CursorLockMode.Locked;
|
|
}
|
|
|
|
IsDialogueActive = false;
|
|
SetStatus("Диалог завершён");
|
|
}
|
|
|
|
public void OnResponseSelected(DialogueResponse response)
|
|
{
|
|
if (response == null)
|
|
{
|
|
EndDialogue();
|
|
return;
|
|
}
|
|
|
|
if (isTyping)
|
|
{
|
|
SkipTypewriter();
|
|
return;
|
|
}
|
|
|
|
if (!CheckRequirements(response))
|
|
{
|
|
string requiredName = response.requiredItem != null ? response.requiredItem.itemName : "предмет";
|
|
SetStatus(!string.IsNullOrEmpty(response.unavailableText) ? response.unavailableText : "Нужен предмет: " + requiredName);
|
|
return;
|
|
}
|
|
|
|
ApplyResponseEffects(response);
|
|
|
|
if (response.endsDialogue || response.nextNode == null)
|
|
EndDialogue();
|
|
else
|
|
ShowNode(response.nextNode);
|
|
}
|
|
|
|
public void CreateResponseButton(DialogueResponse response, bool interactable, string buttonText)
|
|
{
|
|
if (responseContainer == null)
|
|
return;
|
|
|
|
GameObject buttonObject = responseButtonPrefab != null
|
|
? Instantiate(responseButtonPrefab, responseContainer)
|
|
: CreateFallbackButton(responseContainer);
|
|
|
|
buttonObject.name = interactable ? "ResponseButton" : "ResponseButton_Disabled";
|
|
buttonObject.SetActive(true);
|
|
|
|
Text label = buttonObject.GetComponentInChildren<Text>(true);
|
|
if (label != null)
|
|
{
|
|
label.text = buttonText;
|
|
label.color = interactable ? Color.white : new Color(0.68f, 0.7f, 0.73f, 1f);
|
|
}
|
|
|
|
Button button = buttonObject.GetComponent<Button>();
|
|
if (button == null)
|
|
button = buttonObject.AddComponent<Button>();
|
|
|
|
button.interactable = interactable;
|
|
button.onClick.RemoveAllListeners();
|
|
|
|
if (interactable)
|
|
button.onClick.AddListener(() => OnResponseSelected(response));
|
|
|
|
Image image = buttonObject.GetComponent<Image>();
|
|
if (image != null && !interactable)
|
|
image.color = new Color(0.12f, 0.13f, 0.15f, 0.88f);
|
|
|
|
if (responseScrollRect != null)
|
|
responseScrollRect.verticalNormalizedPosition = 1f;
|
|
}
|
|
|
|
public bool CheckRequirements(DialogueResponse response)
|
|
{
|
|
if (response == null || response.requiredItem == null)
|
|
return true;
|
|
|
|
if (playerInventory == null)
|
|
return false;
|
|
|
|
return playerInventory.HasItem(response.requiredItem, Mathf.Max(1, response.requiredItemAmount));
|
|
}
|
|
|
|
public void ApplyResponseEffects(DialogueResponse response)
|
|
{
|
|
if (response == null || playerInventory == null)
|
|
return;
|
|
|
|
if (response.consumeRequiredItem && response.requiredItem != null)
|
|
{
|
|
int amount = Mathf.Max(1, response.requiredItemAmount);
|
|
if (playerInventory.RemoveItem(response.requiredItem, amount))
|
|
SetStatus("Списан предмет: " + response.requiredItem.itemName);
|
|
}
|
|
|
|
if (response.rewardItem != null && response.rewardAmount > 0)
|
|
{
|
|
if (playerInventory.AddItem(response.rewardItem, response.rewardAmount))
|
|
SetStatus("Получен предмет: " + response.rewardItem.itemName);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(response.eventId))
|
|
Debug.Log("Dialogue event fired: " + response.eventId);
|
|
}
|
|
|
|
public void SkipTypewriter()
|
|
{
|
|
if (!isTyping)
|
|
return;
|
|
|
|
if (typewriterRoutine != null)
|
|
{
|
|
StopCoroutine(typewriterRoutine);
|
|
typewriterRoutine = null;
|
|
}
|
|
|
|
if (npcText != null)
|
|
npcText.text = currentNpcLine;
|
|
|
|
isTyping = false;
|
|
BuildResponseButtons();
|
|
}
|
|
|
|
public void ClearResponses()
|
|
{
|
|
if (responseContainer == null)
|
|
return;
|
|
|
|
for (int i = responseContainer.childCount - 1; i >= 0; i--)
|
|
Destroy(responseContainer.GetChild(i).gameObject);
|
|
}
|
|
|
|
public void SetStatus(string message)
|
|
{
|
|
if (statusText != null)
|
|
{
|
|
statusText.text = message;
|
|
SetTextRootActive(statusText, !string.IsNullOrEmpty(message));
|
|
}
|
|
|
|
if (DialogueHUD.Instance != null && !string.IsNullOrEmpty(message))
|
|
DialogueHUD.Instance.SetStatus(message);
|
|
}
|
|
|
|
private IEnumerator TypeLine(string line)
|
|
{
|
|
isTyping = true;
|
|
|
|
if (npcText != null)
|
|
npcText.text = string.Empty;
|
|
|
|
for (int i = 0; i < line.Length; i++)
|
|
{
|
|
if (npcText != null)
|
|
npcText.text = line.Substring(0, i + 1);
|
|
|
|
yield return new WaitForSeconds(characterDelay);
|
|
}
|
|
|
|
isTyping = false;
|
|
typewriterRoutine = null;
|
|
BuildResponseButtons();
|
|
}
|
|
|
|
private void BuildResponseButtons()
|
|
{
|
|
ClearResponses();
|
|
|
|
if (currentNode == null)
|
|
return;
|
|
|
|
int visibleCount = 0;
|
|
int interactableCount = 0;
|
|
DialogueResponse[] responses = currentNode.responses;
|
|
|
|
if (responses != null)
|
|
{
|
|
foreach (DialogueResponse response in responses)
|
|
{
|
|
if (response == null)
|
|
continue;
|
|
|
|
bool requirementsMet = CheckRequirements(response);
|
|
if (!requirementsMet && response.hideIfRequirementsNotMet)
|
|
continue;
|
|
|
|
string text = response.responseText;
|
|
if (!requirementsMet)
|
|
text = !string.IsNullOrEmpty(response.unavailableText) ? response.unavailableText : BuildUnavailableText(response);
|
|
|
|
CreateResponseButton(response, requirementsMet, text);
|
|
visibleCount++;
|
|
|
|
if (requirementsMet)
|
|
interactableCount++;
|
|
}
|
|
}
|
|
|
|
if (visibleCount == 0 || interactableCount == 0 || currentNode.isEndNode)
|
|
{
|
|
DialogueResponse closeResponse = new DialogueResponse
|
|
{
|
|
responseText = "До свидания.",
|
|
endsDialogue = true
|
|
};
|
|
CreateResponseButton(closeResponse, true, "До свидания");
|
|
}
|
|
}
|
|
|
|
private string BuildUnavailableText(DialogueResponse response)
|
|
{
|
|
if (response.requiredItem == null)
|
|
return "Недоступно";
|
|
|
|
int amount = Mathf.Max(1, response.requiredItemAmount);
|
|
return amount > 1
|
|
? "Недоступно: нужен предмет " + response.requiredItem.itemName + " x" + amount
|
|
: "Недоступно: нужен предмет " + response.requiredItem.itemName;
|
|
}
|
|
|
|
private void SetHint(string message)
|
|
{
|
|
if (hintText != null)
|
|
{
|
|
hintText.text = message;
|
|
SetTextRootActive(hintText, !string.IsNullOrEmpty(message));
|
|
}
|
|
|
|
if (DialogueHUD.Instance != null)
|
|
{
|
|
if (string.IsNullOrEmpty(message))
|
|
DialogueHUD.Instance.HideHint();
|
|
else
|
|
DialogueHUD.Instance.ShowHint(message);
|
|
}
|
|
}
|
|
|
|
private GameObject CreateFallbackButton(Transform parent)
|
|
{
|
|
GameObject buttonObject = new GameObject("ResponseButton", typeof(RectTransform), typeof(Image), typeof(Button));
|
|
buttonObject.transform.SetParent(parent, false);
|
|
|
|
GameObject labelObject = new GameObject("Text", typeof(RectTransform), typeof(Text));
|
|
labelObject.transform.SetParent(buttonObject.transform, false);
|
|
|
|
RectTransform labelRect = labelObject.GetComponent<RectTransform>();
|
|
labelRect.anchorMin = Vector2.zero;
|
|
labelRect.anchorMax = Vector2.one;
|
|
labelRect.offsetMin = new Vector2(16f, 6f);
|
|
labelRect.offsetMax = new Vector2(-16f, -6f);
|
|
|
|
Text label = labelObject.GetComponent<Text>();
|
|
label.color = Color.white;
|
|
label.alignment = TextAnchor.MiddleLeft;
|
|
label.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
|
|
|
return buttonObject;
|
|
}
|
|
|
|
private static void SetTextRootActive(Text text, bool active)
|
|
{
|
|
if (text == null)
|
|
return;
|
|
|
|
Transform parent = text.transform.parent;
|
|
if (parent != null && parent.GetComponent<Image>() != null)
|
|
parent.gameObject.SetActive(active);
|
|
else
|
|
text.gameObject.SetActive(active);
|
|
}
|
|
}
|