first commit
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OTGIntegrated.Core;
|
||||
using OTGIntegrated.Player;
|
||||
using OTGIntegrated.Quests;
|
||||
using OTGIntegrated.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OTGIntegrated.Dialogue
|
||||
{
|
||||
public sealed class DialogueManager : MonoBehaviour
|
||||
{
|
||||
public DialogueUI dialogueUI;
|
||||
|
||||
private QuestManager questManager;
|
||||
private UIManager uiManager;
|
||||
private PlayerController playerController;
|
||||
private NPCInteract currentNpc;
|
||||
private DialogueNode currentNode;
|
||||
|
||||
public bool IsDialogueActive { get; private set; }
|
||||
|
||||
public void Initialize(QuestManager questManager, UIManager uiManager, PlayerController playerController)
|
||||
{
|
||||
this.questManager = questManager;
|
||||
this.uiManager = uiManager;
|
||||
this.playerController = playerController;
|
||||
if (dialogueUI == null)
|
||||
{
|
||||
dialogueUI = FindObjectOfType<DialogueUI>(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (IsDialogueActive && Input.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
EndDialogue();
|
||||
}
|
||||
}
|
||||
|
||||
public void StartDialogue(NPCInteract npc)
|
||||
{
|
||||
if (npc == null || npc.startNode == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currentNpc = npc;
|
||||
IsDialogueActive = true;
|
||||
questManager?.NotifyNpcTalked(npc.npcId);
|
||||
|
||||
if (uiManager != null)
|
||||
{
|
||||
uiManager.OpenDialogue();
|
||||
}
|
||||
else if (playerController != null)
|
||||
{
|
||||
playerController.SetInputEnabled(false);
|
||||
}
|
||||
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
ShowNode(npc.startNode);
|
||||
}
|
||||
|
||||
public void ShowNode(DialogueNode node)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
EndDialogue();
|
||||
return;
|
||||
}
|
||||
|
||||
currentNode = node;
|
||||
string speaker = !string.IsNullOrWhiteSpace(node.speakerName)
|
||||
? node.speakerName
|
||||
: currentNpc != null ? currentNpc.displayName : "NPC";
|
||||
|
||||
List<DialogueChoice> choices = BuildChoices(node);
|
||||
dialogueUI?.Show(speaker, node.npcLine, choices);
|
||||
}
|
||||
|
||||
public void EndDialogue()
|
||||
{
|
||||
if (!IsDialogueActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsDialogueActive = false;
|
||||
currentNpc = null;
|
||||
currentNode = null;
|
||||
dialogueUI?.Hide();
|
||||
|
||||
if (uiManager != null)
|
||||
{
|
||||
uiManager.CloseDialogue();
|
||||
}
|
||||
else if (playerController != null)
|
||||
{
|
||||
playerController.SetInputEnabled(true);
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
Cursor.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private List<DialogueChoice> BuildChoices(DialogueNode node)
|
||||
{
|
||||
List<DialogueChoice> choices = new List<DialogueChoice>();
|
||||
|
||||
if (currentNpc != null && currentNpc.offeredQuest != null && questManager != null)
|
||||
{
|
||||
QuestData quest = currentNpc.offeredQuest;
|
||||
if (!questManager.IsQuestActive(quest) && !questManager.IsQuestCompleted(quest))
|
||||
{
|
||||
choices.Add(new DialogueChoice($"Взять квест: {quest.displayName}", () =>
|
||||
{
|
||||
questManager.StartQuest(quest);
|
||||
EndDialogue();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (currentNpc != null && currentNpc.completionQuest != null && questManager != null)
|
||||
{
|
||||
QuestData quest = currentNpc.completionQuest;
|
||||
if (questManager.IsQuestReady(quest))
|
||||
{
|
||||
choices.Add(new DialogueChoice($"Завершить квест: {quest.displayName}", () =>
|
||||
{
|
||||
questManager.TryCompleteQuest(quest, currentNpc.npcId);
|
||||
EndDialogue();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (node.responses != null)
|
||||
{
|
||||
for (int i = 0; i < node.responses.Length; i++)
|
||||
{
|
||||
DialogueResponse response = node.responses[i];
|
||||
if (response == null || string.IsNullOrWhiteSpace(response.responseText))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
choices.Add(new DialogueChoice(response.responseText, () => SelectResponse(response)));
|
||||
}
|
||||
}
|
||||
|
||||
if (currentNpc != null && currentNpc.canOpenCrafting)
|
||||
{
|
||||
choices.Add(new DialogueChoice("Открыть крафт", () =>
|
||||
{
|
||||
EndDialogue();
|
||||
uiManager?.OpenCrafting();
|
||||
}));
|
||||
}
|
||||
|
||||
choices.Add(new DialogueChoice("Закончить разговор", EndDialogue));
|
||||
return choices;
|
||||
}
|
||||
|
||||
private void SelectResponse(DialogueResponse response)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
EndDialogue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.questToStart != null && questManager != null)
|
||||
{
|
||||
questManager.StartQuest(response.questToStart);
|
||||
}
|
||||
|
||||
if (response.questToComplete != null && currentNpc != null && questManager != null)
|
||||
{
|
||||
questManager.TryCompleteQuest(response.questToComplete, currentNpc.npcId);
|
||||
}
|
||||
|
||||
if (response.openCrafting)
|
||||
{
|
||||
EndDialogue();
|
||||
uiManager?.OpenCrafting();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.endsDialogue && response.nextNode == null)
|
||||
{
|
||||
ShowInformationalFollowUp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.endsDialogue || response.nextNode == null)
|
||||
{
|
||||
EndDialogue();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowNode(response.nextNode);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowInformationalFollowUp()
|
||||
{
|
||||
string speaker = currentNpc != null ? currentNpc.displayName : currentNode != null ? currentNode.speakerName : "NPC";
|
||||
string text = GetInformationalText();
|
||||
List<DialogueChoice> choices = new List<DialogueChoice>
|
||||
{
|
||||
new DialogueChoice("Назад", () => ShowNode(currentNode)),
|
||||
new DialogueChoice("Закончить разговор", EndDialogue)
|
||||
};
|
||||
|
||||
dialogueUI?.Show(speaker, text, choices);
|
||||
}
|
||||
|
||||
private string GetInformationalText()
|
||||
{
|
||||
string npcId = currentNpc != null ? currentNpc.npcId : string.Empty;
|
||||
switch (npcId)
|
||||
{
|
||||
case "Elder":
|
||||
return "Нужна обычная древесина из леса. Собери 5 Wood, затем вернись ко мне, чтобы завершить квест.";
|
||||
case "Guard":
|
||||
return "Гоблины стоят в лагере на юго-востоке. Уничтожь 3 Goblin оружием или способностями и возвращайся за наградой.";
|
||||
case "Blacksmith":
|
||||
return "Для Iron Axe нужны Wood x2, Stone x2 и Iron Ore x1. Открой верстак через E рядом со столом или клавишей C.";
|
||||
case "Mage":
|
||||
return "Fireball на 1 тратит ману и наносит урон. Heal на 2 лечит. Blink на 3 переносит вперёд. Таланты открываются на K.";
|
||||
default:
|
||||
return currentNode != null ? currentNode.npcLine : "Подробностей пока нет.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DialogueChoice
|
||||
{
|
||||
public string Text { get; }
|
||||
public Action Callback { get; }
|
||||
|
||||
public DialogueChoice(string text, Action callback)
|
||||
{
|
||||
Text = text;
|
||||
Callback = callback;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d7afb1a8b0b1bd1ca745e01c7173a1f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using OTGIntegrated.Quests;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OTGIntegrated.Dialogue
|
||||
{
|
||||
[CreateAssetMenu(fileName = "DialogueNode", menuName = "OTG Integrated/Dialogue Node")]
|
||||
public sealed class DialogueNode : ScriptableObject
|
||||
{
|
||||
public string nodeId;
|
||||
public string speakerName;
|
||||
[TextArea(3, 8)] public string npcLine;
|
||||
public DialogueResponse[] responses;
|
||||
public bool isEndNode;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nodeId))
|
||||
{
|
||||
nodeId = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class DialogueResponse
|
||||
{
|
||||
public string responseText;
|
||||
public DialogueNode nextNode;
|
||||
public bool endsDialogue;
|
||||
public QuestData questToStart;
|
||||
public QuestData questToComplete;
|
||||
public bool openCrafting;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a1de7da3c08add33ba7a3709b3c8fde
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,141 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace OTGIntegrated.Dialogue
|
||||
{
|
||||
public sealed class DialogueUI : MonoBehaviour
|
||||
{
|
||||
public GameObject panelRoot;
|
||||
public Text speakerText;
|
||||
public Text lineText;
|
||||
public Transform responseRoot;
|
||||
public Button closeButton;
|
||||
public Font uiFont;
|
||||
|
||||
private readonly List<GameObject> spawnedResponses = new List<GameObject>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ConfigureResponseLayout();
|
||||
|
||||
if (closeButton != null)
|
||||
{
|
||||
closeButton.onClick.RemoveAllListeners();
|
||||
closeButton.onClick.AddListener(() => FindObjectOfType<DialogueManager>()?.EndDialogue());
|
||||
}
|
||||
|
||||
Hide();
|
||||
}
|
||||
|
||||
public void Show(string speaker, string line, IReadOnlyList<DialogueChoice> choices)
|
||||
{
|
||||
if (panelRoot != null)
|
||||
{
|
||||
panelRoot.SetActive(true);
|
||||
}
|
||||
|
||||
if (speakerText != null)
|
||||
{
|
||||
speakerText.text = speaker;
|
||||
}
|
||||
|
||||
if (lineText != null)
|
||||
{
|
||||
lineText.text = line;
|
||||
}
|
||||
|
||||
ClearResponses();
|
||||
if (choices == null || responseRoot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < choices.Count; i++)
|
||||
{
|
||||
CreateChoiceButton(choices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
ClearResponses();
|
||||
if (panelRoot != null)
|
||||
{
|
||||
panelRoot.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateChoiceButton(DialogueChoice choice)
|
||||
{
|
||||
if (choice == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject root = new GameObject("DialogueChoice", typeof(RectTransform), typeof(Image), typeof(Button));
|
||||
root.transform.SetParent(responseRoot, false);
|
||||
spawnedResponses.Add(root);
|
||||
root.GetComponent<RectTransform>().sizeDelta = new Vector2(0f, 42f);
|
||||
LayoutElement layoutElement = root.AddComponent<LayoutElement>();
|
||||
layoutElement.preferredHeight = 42f;
|
||||
layoutElement.minHeight = 42f;
|
||||
layoutElement.flexibleWidth = 1f;
|
||||
root.GetComponent<Image>().color = new Color(0.11f, 0.14f, 0.18f, 0.94f);
|
||||
|
||||
Text label = CreateText(root.transform, "Text", choice.Text, 16, TextAnchor.MiddleLeft);
|
||||
label.rectTransform.anchorMin = Vector2.zero;
|
||||
label.rectTransform.anchorMax = Vector2.one;
|
||||
label.rectTransform.offsetMin = new Vector2(14f, 0f);
|
||||
label.rectTransform.offsetMax = new Vector2(-8f, 0f);
|
||||
|
||||
Button button = root.GetComponent<Button>();
|
||||
button.onClick.AddListener(() => choice.Callback?.Invoke());
|
||||
}
|
||||
|
||||
private void ConfigureResponseLayout()
|
||||
{
|
||||
if (responseRoot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VerticalLayoutGroup layout = responseRoot.GetComponent<VerticalLayoutGroup>();
|
||||
if (layout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
layout.childControlWidth = true;
|
||||
layout.childForceExpandWidth = true;
|
||||
layout.childControlHeight = false;
|
||||
layout.childForceExpandHeight = false;
|
||||
}
|
||||
|
||||
private Text CreateText(Transform parent, string name, string value, int fontSize, TextAnchor anchor)
|
||||
{
|
||||
GameObject textObject = new GameObject(name, typeof(RectTransform), typeof(Text));
|
||||
textObject.transform.SetParent(parent, false);
|
||||
Text text = textObject.GetComponent<Text>();
|
||||
text.font = uiFont != null ? uiFont : Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
text.fontSize = fontSize;
|
||||
text.alignment = anchor;
|
||||
text.color = new Color(0.94f, 0.93f, 0.88f, 1f);
|
||||
text.text = value;
|
||||
return text;
|
||||
}
|
||||
|
||||
private void ClearResponses()
|
||||
{
|
||||
for (int i = 0; i < spawnedResponses.Count; i++)
|
||||
{
|
||||
if (spawnedResponses[i] != null)
|
||||
{
|
||||
Destroy(spawnedResponses[i]);
|
||||
}
|
||||
}
|
||||
|
||||
spawnedResponses.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35cd056b2ce856f5fb88e5b86a77090a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
using OTGIntegrated.Player;
|
||||
using OTGIntegrated.Quests;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OTGIntegrated.Dialogue
|
||||
{
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public sealed class NPCInteract : MonoBehaviour, IInteractable
|
||||
{
|
||||
public string npcId;
|
||||
public string displayName = "NPC";
|
||||
public DialogueNode startNode;
|
||||
public QuestData offeredQuest;
|
||||
public QuestData completionQuest;
|
||||
public bool canOpenCrafting;
|
||||
|
||||
private DialogueManager dialogueManager;
|
||||
|
||||
public Transform InteractTransform => transform;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Collider ownCollider = GetComponent<Collider>();
|
||||
ownCollider.isTrigger = true;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
dialogueManager = FindObjectOfType<DialogueManager>();
|
||||
}
|
||||
|
||||
public string GetPrompt()
|
||||
{
|
||||
return $"E — поговорить: {displayName}";
|
||||
}
|
||||
|
||||
public bool CanInteract(GameObject interactor)
|
||||
{
|
||||
return startNode != null && dialogueManager != null;
|
||||
}
|
||||
|
||||
public void Interact(GameObject interactor)
|
||||
{
|
||||
if (dialogueManager != null)
|
||||
{
|
||||
dialogueManager.StartDialogue(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a592109dfa0273ec9b701c9ff20e1fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user