DialogueComponent doodles
This commit is contained in:
@@ -1,63 +1,466 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Core;
|
||||
using Interactions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using PuzzleS;
|
||||
|
||||
namespace Dialogue
|
||||
{
|
||||
[AddComponentMenu("Apple Hills/Dialogue/Dialogue Component")]
|
||||
public class DialogueComponent : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private RuntimeDialogueGraph runtimeGraph;
|
||||
private Dictionary<string, RuntimeDialogueNode> _nodeLookup = new Dictionary<string, RuntimeDialogueNode>();
|
||||
private RuntimeDialogueNode _currentNode;
|
||||
|
||||
private void Start()
|
||||
[SerializeField] private RuntimeDialogueGraph dialogueGraph;
|
||||
|
||||
private RuntimeDialogueNode currentNode;
|
||||
private int currentLineIndex;
|
||||
private bool isWaitingForCondition;
|
||||
|
||||
// Events
|
||||
public event Action<string, string> OnDialogueLineChanged; // speaker name, dialogue text
|
||||
public event Action OnDialogueCompleted;
|
||||
public event Action<bool> OnConditionStatusChanged; // Whether the condition is now met
|
||||
|
||||
// References to managers
|
||||
private ItemManager itemManager;
|
||||
private PuzzleManager puzzleManager;
|
||||
|
||||
// Properties
|
||||
public bool IsActive { get; private set; }
|
||||
public bool IsCompleted { get; private set; }
|
||||
public string CurrentSpeakerName => dialogueGraph?.speakerName;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
foreach (var node in runtimeGraph.allNodes)
|
||||
{
|
||||
_nodeLookup[node.nodeID] = node;
|
||||
}
|
||||
// Auto-injection of managers
|
||||
itemManager = FindFirstObjectByType<ItemManager>();
|
||||
puzzleManager = FindFirstObjectByType<PuzzleManager>();
|
||||
|
||||
if(string.IsNullOrEmpty(runtimeGraph.entryNodeID))
|
||||
if (itemManager == null)
|
||||
Debug.LogWarning("DialogueComponent: ItemManager not found in scene!");
|
||||
|
||||
if (puzzleManager == null)
|
||||
Debug.LogWarning("DialogueComponent: PuzzleManager not found in scene!");
|
||||
}
|
||||
|
||||
public void StartDialogue()
|
||||
{
|
||||
if (dialogueGraph == null)
|
||||
{
|
||||
EndDialogue();
|
||||
Debug.LogError("DialogueComponent: No dialogue graph assigned!");
|
||||
return;
|
||||
}
|
||||
|
||||
ShowNode(runtimeGraph.entryNodeID);
|
||||
// Reset state
|
||||
IsActive = true;
|
||||
IsCompleted = false;
|
||||
isWaitingForCondition = false;
|
||||
currentLineIndex = 0;
|
||||
|
||||
// Set to entry node
|
||||
currentNode = dialogueGraph.GetNodeByID(dialogueGraph.entryNodeID);
|
||||
|
||||
// Register for events based on current node
|
||||
RegisterForEvents();
|
||||
|
||||
// Try to process the current node
|
||||
ProcessCurrentNode();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
|
||||
public bool CanAdvance()
|
||||
{
|
||||
if(Mouse.current.leftButton.wasPressedThisFrame && _currentNode != null)
|
||||
{
|
||||
if(string.IsNullOrEmpty(_currentNode.nextNodeID))
|
||||
{
|
||||
EndDialogue();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowNode(_currentNode.nextNodeID);
|
||||
}
|
||||
}
|
||||
// Can't advance if dialogue is not active or is completed
|
||||
if (!IsActive || IsCompleted) return false;
|
||||
|
||||
// Check if we're waiting for a condition
|
||||
if (isWaitingForCondition) return false;
|
||||
|
||||
// Check if we have more lines in the current node
|
||||
if (currentLineIndex < currentNode.dialogueLines.Count - 1 ||
|
||||
(currentNode.loopThroughLines && currentNode.dialogueLines.Count > 0))
|
||||
return true;
|
||||
|
||||
// Check if we have a next node
|
||||
return !string.IsNullOrEmpty(currentNode.nextNodeID);
|
||||
}
|
||||
|
||||
private void ShowNode(string nodeID)
|
||||
|
||||
public void Advance()
|
||||
{
|
||||
if (!_nodeLookup.ContainsKey(nodeID))
|
||||
if (!CanAdvance()) return;
|
||||
|
||||
// If we have more lines in the current node, advance to the next line
|
||||
if (currentLineIndex < currentNode.dialogueLines.Count - 1)
|
||||
{
|
||||
EndDialogue();
|
||||
currentLineIndex++;
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine());
|
||||
return;
|
||||
}
|
||||
|
||||
_currentNode = _nodeLookup[nodeID];
|
||||
Debug.Log($"{runtimeGraph.speakerName}: {_currentNode.dialogueLine}");
|
||||
// If we should loop through lines, reset the index
|
||||
if (currentNode.loopThroughLines && currentNode.dialogueLines.Count > 0)
|
||||
{
|
||||
currentLineIndex = 0;
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine());
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, move to the next node
|
||||
MoveToNextNode();
|
||||
}
|
||||
|
||||
private void EndDialogue()
|
||||
|
||||
public void AdvanceToNextNode()
|
||||
{
|
||||
Application.Quit();
|
||||
if (!IsActive || IsCompleted) return;
|
||||
|
||||
// Force move to the next node, regardless of current line index
|
||||
MoveToNextNode();
|
||||
}
|
||||
|
||||
public string GetCurrentDialogueLine()
|
||||
{
|
||||
if (currentNode == null || currentNode.dialogueLines.Count == 0) return string.Empty;
|
||||
|
||||
if (currentLineIndex < 0 || currentLineIndex >= currentNode.dialogueLines.Count)
|
||||
return string.Empty;
|
||||
|
||||
return currentNode.dialogueLines[currentLineIndex];
|
||||
}
|
||||
|
||||
// Methods to handle dialogue responses for item slots
|
||||
public void HandleItemSlotInteraction(string slotID, string itemID, bool isForbiddenItem)
|
||||
{
|
||||
if (!IsActive || IsCompleted || currentNode == null ||
|
||||
currentNode.nodeType != RuntimeDialogueNodeType.WaitOnSlot)
|
||||
return;
|
||||
|
||||
// If this is the slot we're waiting for
|
||||
if (currentNode.slotItemID == slotID)
|
||||
{
|
||||
// If correct item is slotted, move to next node
|
||||
if (itemID == slotID)
|
||||
{
|
||||
MoveToNextNode();
|
||||
}
|
||||
// If it's a forbidden item, show the forbidden dialogue
|
||||
else if (isForbiddenItem && currentNode.forbiddenItemLines.Count > 0)
|
||||
{
|
||||
ShowResponseLines(currentNode.forbiddenItemLines, currentNode.loopThroughForbiddenLines);
|
||||
}
|
||||
// Otherwise show incorrect item dialogue
|
||||
else if (currentNode.incorrectItemLines.Count > 0)
|
||||
{
|
||||
ShowResponseLines(currentNode.incorrectItemLines, currentNode.loopThroughIncorrectLines);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowResponseLines(List<string> lines, bool loopThrough)
|
||||
{
|
||||
StartCoroutine(ShowResponseRoutine(lines, loopThrough));
|
||||
}
|
||||
|
||||
private IEnumerator ShowResponseRoutine(List<string> lines, bool loopThrough)
|
||||
{
|
||||
// Store original node and line index
|
||||
var originalNode = currentNode;
|
||||
var originalLineIndex = currentLineIndex;
|
||||
|
||||
// Show each response line
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
// Break if dialogue state has changed
|
||||
if (currentNode != originalNode || !IsActive || IsCompleted)
|
||||
break;
|
||||
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, lines[i]);
|
||||
|
||||
// Wait for input to continue
|
||||
yield return new WaitForSeconds(2f); // Wait time between lines, can be adjusted
|
||||
|
||||
// If we should loop and we're at the end, start over
|
||||
if (loopThrough && i == lines.Count - 1)
|
||||
i = -1;
|
||||
|
||||
// Break after first iteration if not looping
|
||||
if (!loopThrough && i == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Restore original dialogue line
|
||||
if (currentNode == originalNode && IsActive && !IsCompleted)
|
||||
{
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine());
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveToNextNode()
|
||||
{
|
||||
// Unregister from events based on current node
|
||||
UnregisterFromEvents();
|
||||
|
||||
// If there's no next node, complete the dialogue
|
||||
if (string.IsNullOrEmpty(currentNode.nextNodeID))
|
||||
{
|
||||
IsActive = false;
|
||||
IsCompleted = true;
|
||||
OnDialogueCompleted?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to the next node
|
||||
currentNode = dialogueGraph.GetNodeByID(currentNode.nextNodeID);
|
||||
currentLineIndex = 0;
|
||||
|
||||
// Register for events based on new node
|
||||
RegisterForEvents();
|
||||
|
||||
// Process the new node
|
||||
ProcessCurrentNode();
|
||||
}
|
||||
|
||||
private void ProcessCurrentNode()
|
||||
{
|
||||
if (currentNode == null)
|
||||
{
|
||||
Debug.LogError("DialogueComponent: Current node is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle different node types
|
||||
switch (currentNode.nodeType)
|
||||
{
|
||||
case RuntimeDialogueNodeType.Dialogue:
|
||||
isWaitingForCondition = false;
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine());
|
||||
break;
|
||||
|
||||
case RuntimeDialogueNodeType.WaitOnPuzzleStep:
|
||||
HandlePuzzleStepNode();
|
||||
break;
|
||||
|
||||
case RuntimeDialogueNodeType.WaitOnPickup:
|
||||
HandlePickupNode();
|
||||
break;
|
||||
|
||||
case RuntimeDialogueNodeType.WaitOnSlot:
|
||||
HandleSlotNode();
|
||||
break;
|
||||
|
||||
case RuntimeDialogueNodeType.End:
|
||||
IsActive = false;
|
||||
IsCompleted = true;
|
||||
OnDialogueCompleted?.Invoke();
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.LogError($"DialogueComponent: Unknown node type {currentNode.nodeType}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePuzzleStepNode()
|
||||
{
|
||||
if (puzzleManager == null)
|
||||
{
|
||||
Debug.LogError("DialogueComponent: PuzzleManager is required for WaitOnPuzzleStep nodes!");
|
||||
MoveToNextNode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the puzzle step is already completed
|
||||
if (IsPuzzleStepComplete(currentNode.puzzleStepID))
|
||||
{
|
||||
isWaitingForCondition = false;
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine());
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, wait for the puzzle step
|
||||
isWaitingForCondition = true;
|
||||
OnConditionStatusChanged?.Invoke(false);
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine());
|
||||
}
|
||||
|
||||
private void HandlePickupNode()
|
||||
{
|
||||
if (itemManager == null)
|
||||
{
|
||||
Debug.LogError("DialogueComponent: ItemManager is required for WaitOnPickup nodes!");
|
||||
MoveToNextNode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the item is already picked up
|
||||
if (IsItemPickedUp(currentNode.pickupItemID))
|
||||
{
|
||||
isWaitingForCondition = false;
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine());
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, wait for the item pickup
|
||||
isWaitingForCondition = true;
|
||||
OnConditionStatusChanged?.Invoke(false);
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine());
|
||||
}
|
||||
|
||||
private void HandleSlotNode()
|
||||
{
|
||||
if (itemManager == null)
|
||||
{
|
||||
Debug.LogError("DialogueComponent: ItemManager is required for WaitOnSlot nodes!");
|
||||
MoveToNextNode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the slot already has the correct item
|
||||
if (IsItemSlotted(currentNode.slotItemID))
|
||||
{
|
||||
isWaitingForCondition = false;
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine());
|
||||
MoveToNextNode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, wait for the correct item to be slotted
|
||||
isWaitingForCondition = true;
|
||||
OnConditionStatusChanged?.Invoke(false);
|
||||
OnDialogueLineChanged?.Invoke(CurrentSpeakerName, GetCurrentDialogueLine());
|
||||
}
|
||||
|
||||
private void RegisterForEvents()
|
||||
{
|
||||
if (currentNode == null) return;
|
||||
|
||||
switch (currentNode.nodeType)
|
||||
{
|
||||
case RuntimeDialogueNodeType.WaitOnPuzzleStep:
|
||||
if (puzzleManager != null)
|
||||
puzzleManager.OnStepCompleted += OnStepCompleted;
|
||||
break;
|
||||
|
||||
case RuntimeDialogueNodeType.WaitOnPickup:
|
||||
if (itemManager != null)
|
||||
itemManager.OnItemPickedUp += OnItemPickedUp;
|
||||
break;
|
||||
|
||||
case RuntimeDialogueNodeType.WaitOnSlot:
|
||||
if (itemManager != null)
|
||||
itemManager.OnCorrectItemSlotted += OnCorrectItemSlotted;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterFromEvents()
|
||||
{
|
||||
if (currentNode == null) return;
|
||||
|
||||
switch (currentNode.nodeType)
|
||||
{
|
||||
case RuntimeDialogueNodeType.WaitOnPuzzleStep:
|
||||
if (puzzleManager != null)
|
||||
puzzleManager.OnStepCompleted -= OnStepCompleted;
|
||||
break;
|
||||
|
||||
case RuntimeDialogueNodeType.WaitOnPickup:
|
||||
if (itemManager != null)
|
||||
itemManager.OnItemPickedUp -= OnItemPickedUp;
|
||||
break;
|
||||
|
||||
case RuntimeDialogueNodeType.WaitOnSlot:
|
||||
if (itemManager != null)
|
||||
itemManager.OnCorrectItemSlotted -= OnCorrectItemSlotted;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Event handlers for PuzzleManager
|
||||
private void OnStepCompleted(PuzzleStepSO step)
|
||||
{
|
||||
if (!IsActive || !isWaitingForCondition || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnPuzzleStep)
|
||||
return;
|
||||
|
||||
if (step.stepId == currentNode.puzzleStepID)
|
||||
{
|
||||
isWaitingForCondition = false;
|
||||
OnConditionStatusChanged?.Invoke(true);
|
||||
MoveToNextNode();
|
||||
}
|
||||
}
|
||||
|
||||
// Event handlers for ItemManager
|
||||
private void OnItemPickedUp(PickupItemData item)
|
||||
{
|
||||
if (!IsActive || !isWaitingForCondition || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnPickup)
|
||||
return;
|
||||
|
||||
if (item.itemId == currentNode.pickupItemID)
|
||||
{
|
||||
isWaitingForCondition = false;
|
||||
OnConditionStatusChanged?.Invoke(true);
|
||||
MoveToNextNode();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCorrectItemSlotted(PickupItemData slotDefinition, PickupItemData slottedItem)
|
||||
{
|
||||
if (!IsActive || !isWaitingForCondition || currentNode.nodeType != RuntimeDialogueNodeType.WaitOnSlot)
|
||||
return;
|
||||
|
||||
if (slotDefinition.itemId == currentNode.slotItemID)
|
||||
{
|
||||
isWaitingForCondition = false;
|
||||
OnConditionStatusChanged?.Invoke(true);
|
||||
MoveToNextNode();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
private bool IsPuzzleStepComplete(string stepID)
|
||||
{
|
||||
if (puzzleManager == null) return false;
|
||||
|
||||
// Use the public method instead of accessing the private field
|
||||
return puzzleManager.IsPuzzleStepCompleted(stepID);
|
||||
}
|
||||
|
||||
private bool IsItemPickedUp(string itemID)
|
||||
{
|
||||
if (itemManager == null) return false;
|
||||
|
||||
// Check if any picked up item has this ID
|
||||
foreach (var pickup in itemManager.Pickups)
|
||||
{
|
||||
if (pickup.isPickedUp && pickup.itemData != null && pickup.itemData.itemId == itemID)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsItemSlotted(string slotID)
|
||||
{
|
||||
if (itemManager == null) return false;
|
||||
|
||||
// Check if any slot has the correct item with this ID
|
||||
foreach (var slot in itemManager.ItemSlots)
|
||||
{
|
||||
if (slot.CurrentSlottedState == ItemSlotState.Correct &&
|
||||
slot.itemData != null && slot.itemData.itemId == slotID)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Editor functionality
|
||||
public void SetDialogueGraph(RuntimeDialogueGraph graph)
|
||||
{
|
||||
dialogueGraph = graph;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 749c3dece1c14b82845c175203a2e7dc
|
||||
timeCreated: 1758873871
|
||||
guid: 25bbad45f1fa4183b30ad76c62256fd6
|
||||
timeCreated: 1758891211
|
||||
@@ -4,19 +4,50 @@ using UnityEngine;
|
||||
|
||||
namespace Dialogue
|
||||
{
|
||||
[Serializable]
|
||||
public enum RuntimeDialogueNodeType
|
||||
{
|
||||
Dialogue,
|
||||
WaitOnPuzzleStep,
|
||||
WaitOnPickup,
|
||||
WaitOnSlot,
|
||||
End
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RuntimeDialogueGraph : ScriptableObject
|
||||
{
|
||||
public string entryNodeID;
|
||||
public string speakerName;
|
||||
public List<RuntimeDialogueNode> allNodes = new List<RuntimeDialogueNode>();
|
||||
|
||||
// Helper method to find a node by ID
|
||||
public RuntimeDialogueNode GetNodeByID(string id)
|
||||
{
|
||||
return allNodes.Find(n => n.nodeID == id);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RuntimeDialogueNode
|
||||
{
|
||||
public string nodeID;
|
||||
public string dialogueLine;
|
||||
public RuntimeDialogueNodeType nodeType;
|
||||
public string nextNodeID;
|
||||
|
||||
// Basic dialogue
|
||||
public List<string> dialogueLines = new List<string>();
|
||||
public bool loopThroughLines;
|
||||
|
||||
// Conditional nodes
|
||||
public string puzzleStepID; // For WaitOnPuzzleStep
|
||||
public string pickupItemID; // For WaitOnPickup
|
||||
public string slotItemID; // For WaitOnSlot
|
||||
|
||||
// For WaitOnSlot - different responses
|
||||
public List<string> incorrectItemLines = new List<string>();
|
||||
public bool loopThroughIncorrectLines;
|
||||
public List<string> forbiddenItemLines = new List<string>();
|
||||
public bool loopThroughForbiddenLines;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user