Part of these game systems:
Quest System
Lightweight quest tracker with ScriptableObject definitions, objectives, progress tracking, and reward callbacks.
How to Use
1
Create quest ScriptableObjects: Assets > Create > Quests > Quest Data
2
Define objectives (Collect 5 coins, Kill 3 enemies, Talk to NPC)
3
Attach QuestSystem to a persistent manager
4
Accept quest: QuestSystem.Instance.AcceptQuest(questData)
5
Report progress: QuestSystem.Instance.ReportProgress("kill_goblin")
6
Hook OnQuestCompleted to give rewards
7
Check quest status: QuestSystem.Instance.HasQuest(quest)
Source Code
C#
using UnityEngine;
using UnityEngine.Events;
using System.Collections.Generic;
/// <summary>
/// ScriptableObject quest definition.
/// </summary>
[CreateAssetMenu(menuName = "Quests/Quest Data")]
public class QuestData : ScriptableObject
{
public string questName = "New Quest";
[TextArea(2, 4)] public string description;
public QuestObjective[] objectives;
public int experienceReward;
}
/// <summary>
/// A single objective within a quest.
/// </summary>
[System.Serializable]
public class QuestObjective
{
public string objectiveId;
public string description;
public ObjectiveType type;
public int requiredAmount = 1;
public enum ObjectiveType { Collect, Kill, Talk, Reach, Custom }
}
/// <summary>
/// Runtime quest instance tracking progress.
/// </summary>
[System.Serializable]
public class QuestInstance
{
public QuestData data;
public int[] objectiveProgress;
public bool isComplete;
public QuestInstance(QuestData questData)
{
data = questData;
objectiveProgress = new int[questData.objectives.Length];
isComplete = false;
}
public float GetProgress()
{
if (data.objectives.Length == 0) return 1f;
int completed = 0;
for (int i = 0; i < data.objectives.Length; i++)
{
if (objectiveProgress[i] >= data.objectives[i].requiredAmount)
completed++;
}
return (float)completed / data.objectives.Length;
}
}
/// <summary>
/// Quest manager. Tracks active quests and objective progress.
/// </summary>
public class QuestSystem : MonoBehaviour
{
public static QuestSystem Instance { get; private set; }
[Header("Events")]
public UnityEvent<QuestData> OnQuestAccepted;
public UnityEvent<QuestData> OnQuestCompleted;
public UnityEvent<string, int> OnObjectiveProgress; // objectiveId, newProgress
private List<QuestInstance> activeQuests = new List<QuestInstance>();
private List<QuestData> completedQuests = new List<QuestData>();
/// <summary>Currently active quests.</summary>
public IReadOnlyList<QuestInstance> ActiveQuests => activeQuests;
/// <summary>Completed quest data.</summary>
public IReadOnlyList<QuestData> CompletedQuests => completedQuests;
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
/// <summary>Accept a new quest.</summary>
public bool AcceptQuest(QuestData quest)
{
if (HasQuest(quest) || IsQuestCompleted(quest))
return false;
QuestInstance instance = new QuestInstance(quest);
activeQuests.Add(instance);
OnQuestAccepted?.Invoke(quest);
return true;
}
/// <summary>
/// Report progress on an objective. Call this when player collects, kills, etc.
/// </summary>
public void ReportProgress(string objectiveId, int amount = 1)
{
// Iterate backwards to safely remove completed quests
for (int q = activeQuests.Count - 1; q >= 0; q--)
{
var quest = activeQuests[q];
if (quest.isComplete) continue;
for (int i = 0; i < quest.data.objectives.Length; i++)
{
if (quest.data.objectives[i].objectiveId == objectiveId)
{
quest.objectiveProgress[i] = Mathf.Min(
quest.objectiveProgress[i] + amount,
quest.data.objectives[i].requiredAmount
);
OnObjectiveProgress?.Invoke(objectiveId, quest.objectiveProgress[i]);
CheckQuestCompletion(quest);
}
}
}
}
/// <summary>Check if a quest is currently active.</summary>
public bool HasQuest(QuestData quest)
{
return activeQuests.Exists(q => q.data == quest);
}
/// <summary>Check if a quest has been completed.</summary>
public bool IsQuestCompleted(QuestData quest)
{
return completedQuests.Contains(quest);
}
/// <summary>Get the runtime instance of an active quest.</summary>
public QuestInstance GetQuestInstance(QuestData quest)
{
return activeQuests.Find(q => q.data == quest);
}
/// <summary>Abandon an active quest.</summary>
public void AbandonQuest(QuestData quest)
{
activeQuests.RemoveAll(q => q.data == quest);
}
private void CheckQuestCompletion(QuestInstance quest)
{
for (int i = 0; i < quest.data.objectives.Length; i++)
{
if (quest.objectiveProgress[i] < quest.data.objectives[i].requiredAmount)
return;
}
quest.isComplete = true;
activeQuests.Remove(quest);
completedQuests.Add(quest.data);
OnQuestCompleted?.Invoke(quest.data);
}
}