Files
OTG_4/Assets/_Scripts/DialogueManager.cs
T

405 lines
12 KiB
C#
Raw Normal View History

2026-06-04 19:34:38 +03:00
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);
}
}