Draft Dialogue System and Started Quest System

This commit is contained in:
SHOUTING_PIRATE
2025-05-07 14:43:43 +01:00
parent 8fb8da882d
commit 4e0bc0a573
503 changed files with 13767 additions and 281708 deletions

View File

@@ -3,22 +3,5 @@ using System.Collections.Generic;
[System.Serializable]
public class Dialogue
{
public string npcId;
public string name;
public List<string> sentences;
public List<DialogueOption> options;
}
[System.Serializable]
public class DialogueOption
{
public string text;
public DialogueOutcome outcome;
}
[System.Serializable]
public class DialogueOutcome
{
public List<Quest> quests;
public Dialogue dialogue;
}

View File

@@ -0,0 +1,11 @@
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
[CreateAssetMenu(fileName = "DialogueDatabase", menuName = "Dialogue/Dialogue Database", order = 0)]
public class DialogueAssets : ScriptableObject
{
[SerializeField] private List<DialogueEntry> entries = new List<DialogueEntry>();
public List<DialogueEntry> Entries => entries;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 27a69891d053e674d962dfbb0e0eb283

View File

@@ -0,0 +1,24 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 27a69891d053e674d962dfbb0e0eb283, type: 3}
m_Name: DialogueDatabase
m_EditorClassIdentifier:
entries:
- npcId: Villager_01
npcName: Villager
dialogues:
- sentences:
- Welcome to the village boy, it is lovely and quiet, see to it that it stays
that way...
- If you are looking for something to do rather than bothering me, talk to
the farmer next to us. He is having some troubles I am sure you can help
with.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1c996dbde4d1b2840b324512a0dcc3f3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -13,7 +13,7 @@ public class DialogueEntry
[System.Serializable]
public class DialogueDatabase : MonoBehaviour
{
[SerializeField] private List<DialogueEntry> entries;
[SerializeField] private DialogueAssets dialogueAssets;
private Dictionary<string, DialogueEntry> dialogueDictionary;
private static DialogueDatabase instance;
@@ -23,7 +23,7 @@ public class DialogueDatabase : MonoBehaviour
{
if (instance == null)
{
instance = FindObjectOfType<DialogueDatabase>();
instance = FindFirstObjectByType<DialogueDatabase>();
if (instance == null)
{
GameObject obj = new GameObject("DialogueDatabase");
@@ -50,8 +50,14 @@ public class DialogueDatabase : MonoBehaviour
private void InitializeDictionary()
{
if (dialogueAssets == null)
{
Debug.LogError("DialogueAssets not assigned!");
return;
}
dialogueDictionary = new Dictionary<string, DialogueEntry>();
foreach (DialogueEntry entry in entries)
foreach (DialogueEntry entry in dialogueAssets.Entries)
{
dialogueDictionary[entry.npcId] = entry;
}
@@ -95,4 +101,13 @@ public class DialogueDatabase : MonoBehaviour
}
return "Unknown NPC";
}
public string GetNPCId(string npcId)
{
if (dialogueDictionary.TryGetValue(npcId, out DialogueEntry entry))
{
return entry.npcId;
}
return npcId;
}
}

View File

@@ -2,35 +2,51 @@ using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using UnityEngine.UI;
using TMPro;
using StarterAssets;
using UnityEngine.Events;
public class DialogueManager : MonoBehaviour
{
private static DialogueManager instance;
public UnityEvent OnDialogueComplete = new UnityEvent();
[Header("UI Elements")]
[SerializeField] private GameObject dialoguePanel;
[SerializeField] private Text dialogueText;
[SerializeField] private GameObject optionsPanel;
[SerializeField] private RectTransform optionsContainer;
[SerializeField] private GameObject optionPrefab;
[SerializeField] private TMP_Text dialogueText;
[Header("Dialogue Settings")]
[SerializeField] private float textSpeed = 0.05f;
[SerializeField] private float dialogueDelay = 1f;
private Queue<string> sentences;
private Dialogue currentDialogue;
private bool isTyping;
private bool isDialogueActive;
private ThirdPersonController playerController;
private void Start()
{
sentences = new Queue<string>();
dialoguePanel.SetActive(false);
optionsPanel.SetActive(false);
playerController = FindFirstObjectByType<ThirdPersonController>();
}
public void StartDialogue(string npcId)
{
Dialogue dialogue = DialogueDatabase.Instance.GetRandomDialogue(npcId);
// Get the NPC ID from the database
string actualNpcId = DialogueDatabase.Instance.GetNPCId(npcId);
// Freeze player controls
if (playerController != null)
{
playerController.enabled = false;
}
// Unlock mouse
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
Dialogue dialogue = DialogueDatabase.Instance.GetRandomDialogue(actualNpcId);
if (dialogue == null)
{
Debug.LogWarning($"No dialogue found for NPC with ID: {npcId}");
@@ -72,65 +88,63 @@ public class DialogueManager : MonoBehaviour
foreach (char letter in sentence.ToCharArray())
{
if (Input.GetMouseButtonDown(1)) // Right click skips
{
dialogueText.text = sentence;
break;
}
dialogueText.text += letter;
yield return new WaitForSeconds(textSpeed);
}
isTyping = false;
if (currentDialogue.options != null && currentDialogue.options.Count > 0)
{
ShowOptions(currentDialogue.options);
}
}
private void ShowOptions(List<DialogueOption> options)
{
optionsPanel.SetActive(true);
optionsContainer.sizeDelta = new Vector2(optionsContainer.sizeDelta.x, options.Count * 50);
foreach (Transform child in optionsContainer)
{
Destroy(child.gameObject);
}
foreach (DialogueOption option in options)
{
GameObject optionObj = Instantiate(optionPrefab, optionsContainer);
Button button = optionObj.GetComponent<Button>();
Text buttonText = optionObj.GetComponentInChildren<Text>();
buttonText.text = option.text;
button.onClick.AddListener(() => SelectOption(option));
}
}
private void SelectOption(DialogueOption option)
{
optionsPanel.SetActive(false);
if (option.outcome != null)
{
if (option.outcome.quests != null)
{
foreach (Quest quest in option.outcome.quests)
{
quest.StartQuest();
}
}
if (option.outcome.dialogue != null)
{
StartDialogue(option.outcome.dialogue.npcId);
}
}
}
private void EndDialogue()
{
dialoguePanel.SetActive(false);
optionsPanel.SetActive(false);
isDialogueActive = false;
dialoguePanel.SetActive(false);
// Reset player controls
if (playerController != null)
{
playerController.enabled = true;
}
// Lock mouse back
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
OnDialogueComplete.Invoke();
}
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else if (instance != this)
{
Destroy(gameObject);
}
}
private void OnEnable()
{
OnDialogueComplete.RemoveAllListeners();
}
private void Update()
{
if (isDialogueActive)
{
if (Input.GetMouseButtonDown(0))
{
DisplayNextSentence();
}
}
}
public bool IsDialogueActive()

View File

@@ -4,34 +4,103 @@ public class NPC : MonoBehaviour
{
[Header("NPC Settings")]
[SerializeField] private string npcId;
[SerializeField] private string npcName;
[SerializeField] private bool canTalk;
[SerializeField] private string initialDialogueId;
[SerializeField] private string repeatedDialogueId;
[SerializeField] public string npcName;
[SerializeField] public bool canTalk;
[SerializeField] public bool isInteractable;
[SerializeField] public float interactionRange = 2f;
[SerializeField] private Animator animator;
[SerializeField] private bool startQuest;
[SerializeField] private int questId;
private DialogueManager dialogueManager;
private bool hasShownInitialDialogue = false;
private QuestDatabase questDatabase;
private void Start()
{
dialogueManager = FindObjectOfType<DialogueManager>();
dialogueManager = FindAnyObjectByType<DialogueManager>();
questDatabase = QuestDatabase.Instance;
// Subscribe to quest completion event if this NPC is involved in quest completion
if (startQuest && questId != 0)
{
QuestData quest = questDatabase.GetQuest(questId);
if (quest != null)
{
// Add any additional quest start logic here
}
else
{
Debug.LogWarning($"Quest with ID {questId} not found for NPC {npcName}");
}
}
// Set default dialogue IDs if not specified
if (string.IsNullOrEmpty(initialDialogueId))
{
initialDialogueId = npcId + "_initial";
}
if (string.IsNullOrEmpty(repeatedDialogueId))
{
repeatedDialogueId = npcId + "_repeat";
}
}
private void OnMouseDown()
public bool IsInRange(Vector3 playerPosition)
{
if (canTalk && !dialogueManager.IsDialogueActive())
return Vector3.Distance(transform.position, playerPosition) <= interactionRange;
}
public void Interact()
{
if (canTalk && isInteractable && !dialogueManager.IsDialogueActive())
{
string dialogueId = hasShownInitialDialogue ? repeatedDialogueId : initialDialogueId;
if (string.IsNullOrEmpty(dialogueId))
{
Debug.LogWarning($"No dialogue ID specified for NPC {npcName}");
return;
}
// Use npcId as the base ID for dialogue lookup
dialogueManager.StartDialogue(npcId);
animator.SetTrigger("Talk");
// Only set hasShownInitialDialogue to true after the first dialogue
if (!hasShownInitialDialogue)
{
hasShownInitialDialogue = true;
}
// If this NPC starts a quest, add a callback to start the quest after dialogue
if (startQuest && questId != 0)
{
// Validate quest ID before adding listener
QuestData quest = questDatabase.GetQuest(questId);
if (quest != null)
{
dialogueManager.OnDialogueComplete.AddListener(() =>
{
questDatabase.UpdateQuestUI(quest);
// Add any additional quest start logic here
});
}
else
{
Debug.LogWarning($"Quest with ID {questId} not found for NPC {npcName}. Skipping quest start.");
}
}
}
}
private void OnMouseOver()
public void SetInteractable(bool interactable)
{
if (canTalk)
{
// Show interaction prompt
}
}
private void OnMouseExit()
{
// Hide interaction prompt
isInteractable = interactable;
hasShownInitialDialogue = false; // Reset dialogue state when interactable changes
}
}

View File

@@ -1,36 +1,69 @@
using UnityEngine;
using TMPro;
using UnityEditor.VisionOS;
public class PopUpManager : MonoBehaviour
{
[SerializeField] private TMP_Text _popUpText;
private Transform _target;
private bool _isShowing;
public static PopUpManager instance; // Singleton instance of PopUpManager
public void Awake()
private void Awake()
{
instance = this; // Assign the current instance to the static instance variable
}
public TMP_Text popUpText; // Reference to the TextMeshPro component for displaying the message
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else if (instance != this)
{
Destroy(gameObject);
}
if (_popUpText == null)
{
Debug.LogError("PopUpManager: TMP_Text component not assigned!");
}
}
// Update is called once per frame
void Update()
private void Update()
{
if (_isShowing && _target != null)
{
// Position the pop-up above the target object
Vector3 offset = new Vector3(0, 0.5f, 0); // Adjust the Y offset as needed
transform.position = _target.position + offset;
transform.rotation = Quaternion.Euler(0, transform.rotation.eulerAngles.y, 0); // Lock rotation to Y-axis
}
}
public void ShowPopUp(string objectName, string objectDesc, Transform targetObject)
public void ShowObjectPopUp(string objectName, string objectDescription, Transform target)
{
// Set the text of the pop-up to the provided message
popUpText.text = objectName + "\n" + objectDesc;
_target = target;
_isShowing = true;
_popUpText.text = $"{objectName}\n{objectDescription}";
UpdatePositionAndRotation();
}
public void ShowNPCPopUp(string npcName, Transform target)
{
_target = target;
_isShowing = true;
_popUpText.text = npcName;
UpdatePositionAndRotation();
}
private void UpdatePositionAndRotation()
{
if (_target == null)
{
return;
}
// Position the pop-up above the target object
Vector3 offset = new Vector3(0, 0.5f, 0); // Adjust the Y offset as needed
transform.position = targetObject.position + offset;
transform.position = _target.position + offset;
// Make the pop-up face the camera
Camera mainCamera = Camera.main;
@@ -41,8 +74,15 @@ public class PopUpManager : MonoBehaviour
transform.rotation = Quaternion.LookRotation(directionToCamera);
transform.rotation = Quaternion.Euler(0, transform.rotation.eulerAngles.y, 0); // Lock rotation to Y-axis
}
}
// Activate the pop-up GameObject to make it visible
gameObject.SetActive(true);
public void HidePopUp()
{
if (_isShowing)
{
_isShowing = false;
_target = null;
gameObject.SetActive(false);
}
}
}

View File

@@ -0,0 +1,11 @@
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
[CreateAssetMenu(fileName = "QuestDatabase", menuName = "Quest/Quest Database", order = 0)]
public class QuestAssets : ScriptableObject
{
[SerializeField] private List<QuestEntry> entries = new List<QuestEntry>();
public List<QuestEntry> Entries => entries;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f28f2357391dea543a0edc80401bce44

View File

@@ -0,0 +1,29 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f28f2357391dea543a0edc80401bce44, type: 3}
m_Name: QuestDatabase
m_EditorClassIdentifier:
entries:
- questId: 1
questName: Farmer's Woes
questDescription: Help the farmer resolve his woes
quests:
- id: 1
name: Talk To Farmer
description: Find out what the farmer's issue is
type: 2
targetAmount: 0
targetItem:
experienceReward: 0
goldReward: 0
isRepeatable: 0
requiredQuests:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 56924919879e6bc4bb2b99a727cddd8c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,323 @@
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using System;
using TMPro;
[System.Serializable]
public class QuestEntry
{
public int questId; // Changed from string to int
public string questName;
public string questDescription;
public List<QuestData> quests;
}
[System.Serializable]
public class QuestData
{
public int id;
public string name;
public string description;
public QuestType type;
public int targetAmount;
public string targetItem;
public int experienceReward;
public int goldReward;
public bool isRepeatable;
public List<int> requiredQuests; // Quest IDs that must be completed first
}
public enum QuestType
{
Collect,
Kill,
TalkTo,
Deliver,
Explore,
Reach
}
[System.Serializable]
public class QuestDatabase : MonoBehaviour
{
[SerializeField] private QuestAssets questAssets;
[SerializeField] private TMPro.TMP_Text questNameText;
[SerializeField] private TMPro.TMP_Text questObjectiveText;
private Dictionary<int, QuestEntry> questDictionary;
private static QuestDatabase instance;
public static QuestDatabase Instance
{
get
{
if (instance == null)
{
instance = FindFirstObjectByType<QuestDatabase>();
if (instance == null)
{
GameObject obj = new GameObject("QuestDatabase");
instance = obj.AddComponent<QuestDatabase>();
}
}
return instance;
}
}
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
InitializeDictionary();
}
else if (instance != this)
{
Destroy(gameObject);
}
}
private void InitializeDictionary()
{
questDictionary = new Dictionary<int, QuestEntry>();
if (questAssets != null)
{
foreach (var entry in questAssets.Entries)
{
questDictionary.Add(entry.questId, entry);
}
}
}
public QuestData GetQuest(int questId)
{
if (questDictionary.TryGetValue(questId, out var entry))
{
return entry.quests.Find(q => q.id == questId);
}
return null;
}
public List<QuestData> GetAvailableQuests(int npcId)
{
List<QuestData> availableQuests = new List<QuestData>();
foreach (var entry in questDictionary.Values)
{
foreach (var quest in entry.quests)
{
// Check prerequisites
bool prerequisitesMet = true;
foreach (int requiredQuest in quest.requiredQuests)
{
if (!IsQuestCompleted(requiredQuest))
{
prerequisitesMet = false;
break;
}
}
if (prerequisitesMet)
{
availableQuests.Add(quest);
}
}
}
// Debug log available quests
Debug.Log($"Available quests for NPC {npcId}:");
foreach (var quest in availableQuests)
{
Debug.Log($"- {quest.id}: {quest.name}");
}
return availableQuests;
}
private Dictionary<int, int> questProgress = new Dictionary<int, int>();
private Dictionary<int, bool> completedQuests = new Dictionary<int, bool>();
public void UpdateQuestProgress(int questId, int amount = 1)
{
if (questProgress.ContainsKey(questId))
{
questProgress[questId] += amount;
}
else
{
questProgress[questId] = amount;
}
// Check if quest is completed
QuestData quest = GetQuest(questId);
if (quest != null && questProgress[questId] >= quest.targetAmount)
{
CompleteQuest(questId);
}
}
public void CompleteQuest(int questId)
{
if (completedQuests.ContainsKey(questId))
{
if (completedQuests[questId])
{
return;
}
}
completedQuests[questId] = true;
QuestData quest = GetQuest(questId);
if (quest != null)
{
// Grant rewards
// TODO: Implement reward system
// Update UI
if (questNameText != null)
{
questNameText.text = quest.name + " (Completed)";
questNameText.gameObject.SetActive(false);
}
if (questObjectiveText != null)
{
questObjectiveText.text = "Quest completed!";
questObjectiveText.gameObject.SetActive(false);
}
// Check for any quests that require this quest as a prerequisite
foreach (var entry in questDictionary.Values)
{
foreach (var q in entry.quests)
{
if (q.requiredQuests.Contains(questId))
{
// TODO: Make these quests available
}
}
}
// Check for any quests that require this quest as a prerequisite
foreach (var entry in questDictionary.Values)
{
foreach (var q in entry.quests)
{
if (q.requiredQuests.Contains(questId))
{
// TODO: Make these quests available
}
}
}
}
}
public bool IsQuestCompleted(int questId)
{
return completedQuests.ContainsKey(questId) && completedQuests[questId];
}
public int GetQuestProgress(int questId)
{
return questProgress.ContainsKey(questId) ? questProgress[questId] : 0;
}
public void ResetQuestProgress(int questId)
{
questProgress[questId] = 0;
completedQuests[questId] = false;
// Reset UI visibility
if (questNameText != null)
{
questNameText.gameObject.SetActive(true);
}
if (questObjectiveText != null)
{
questObjectiveText.gameObject.SetActive(true);
}
}
public void FailQuest(int questId)
{
if (completedQuests.ContainsKey(questId))
{
if (completedQuests[questId])
{
return;
}
}
completedQuests[questId] = true;
QuestData quest = GetQuest(questId);
if (quest != null)
{
// Update UI
if (questNameText != null)
{
questNameText.text = quest.name + " (Failed)";
questNameText.gameObject.SetActive(false);
}
if (questObjectiveText != null)
{
questObjectiveText.text = "Quest failed!";
questObjectiveText.gameObject.SetActive(false);
}
}
}
public void CancelQuest(int questId)
{
if (completedQuests.ContainsKey(questId))
{
if (completedQuests[questId])
{
return;
}
}
completedQuests[questId] = true;
QuestData quest = GetQuest(questId);
if (quest != null)
{
// Update UI
if (questNameText != null)
{
questNameText.text = quest.name + " (Cancelled)";
questNameText.gameObject.SetActive(false);
}
if (questObjectiveText != null)
{
questObjectiveText.text = "Quest cancelled!";
questObjectiveText.gameObject.SetActive(false);
}
}
}
public void UpdateQuestUI(QuestData quest)
{
if (quest == null)
{
Debug.LogWarning("QuestDatabase: Attempted to update UI with null quest");
return;
}
if (questNameText != null)
{
questNameText.text = quest.name;
questNameText.gameObject.SetActive(true);
}
if (questObjectiveText != null)
{
int progress = GetQuestProgress(quest.id);
string objectiveText = $"{quest.description}";
questObjectiveText.text = objectiveText;
questObjectiveText.gameObject.SetActive(true);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2389feddc157cee42a502e282623daa5