first commit
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user