Creating an flexible quest/mission system

Hello. I’m looking into ways to create a nice flexible quest/mission system that includes dialogues, cutscenes and checkpoints. Now, the standard way I did quests was having a script for each mission, and missionStage variable that stores the stage of the mission as an integer. When the player completes a part of it, I simply increase the missionStage variable and proceed into the next stage. If there’s a cutscene, I play a Timeline and then create a method in the mission script to proceed to the stage AFTER the cutscene like StageFive() and then use a signal receiver to listen to an signal triggered by the end of the cutscene. It works, but I don’t like how it’s structured, and the idea of a checkpoint system using this structure is hell because I depend on values that has been set BEFORE the player entered that specific checkpoint (Like the places where the NPCs are).

I already have a DialogueManager that reads lines from a JSON file so that’s not an issue.

I’m looking for a better way to create such a mission system, which is cleaner and easier to implement checkpoints in.

Extra note: In the GTA games(3D universe), they use an intermediate language for their game scripts found in main.scm file. Is it possible to use an intermediate language just for scripting missions in Unity? Like Lua or Python for example?

Thank you!

using UnityEngine;
using System;
using System.Collections.Generic;

// Define enum for mission states
public enum MissionState {
    Inactive,
    Active,
    Completed
}

// Define event for mission completion
public class MissionCompletedEventArgs : EventArgs {
    public string MissionID { get; private set; }

    public MissionCompletedEventArgs(string missionID) {
        MissionID = missionID;
    }
}

public class MissionManager : MonoBehaviour {
    // Event for mission completion
    public static event EventHandler<MissionCompletedEventArgs> MissionCompleted;

    // Dictionary to store mission states
    private Dictionary<string, MissionState> missionStates = new Dictionary<string, MissionState>();

    // Dictionary to store mission checkpoints
    private Dictionary<string, int> missionCheckpoints = new Dictionary<string, int>();

    // Start a mission
    public void StartMission(string missionID) {
        missionStates[missionID] = MissionState.Active;
        missionCheckpoints[missionID] = 0; // Initialize checkpoint
        Debug.Log("Mission started: " + missionID);

        // Trigger mission started event
        // You can hook into this event to handle mission-specific initialization, like showing UI or starting dialogue
        // For simplicity, we're not showing that here
    }

    // Complete a mission
    public void CompleteMission(string missionID) {
        missionStates[missionID] = MissionState.Completed;
        Debug.Log("Mission completed: " + missionID);

        // Trigger mission completed event
        OnMissionCompleted(missionID);
    }

    // Check if mission is active
    public bool IsMissionActive(string missionID) {
        return missionStates.ContainsKey(missionID) && missionStates[missionID] == MissionState.Active;
    }

    // Check if mission is completed
    public bool IsMissionCompleted(string missionID) {
        return missionStates.ContainsKey(missionID) && missionStates[missionID] == MissionState.Completed;
    }

    // Trigger mission completed event
    protected virtual void OnMissionCompleted(string missionID) {
        EventHandler<MissionCompletedEventArgs> handler = MissionCompleted;
        if (handler != null) {
            handler(this, new MissionCompletedEventArgs(missionID));
        }
    }

    // Save mission states for checkpoints
    public void SaveMissionStates() {
        PlayerPrefs.SetString("MissionStates", JsonUtility.ToJson(missionStates));
        PlayerPrefs.SetString("MissionCheckpoints", JsonUtility.ToJson(missionCheckpoints));
        PlayerPrefs.Save();
    }

    // Load mission states for checkpoints
    public void LoadMissionStates() {
        string jsonString = PlayerPrefs.GetString("MissionStates", "");
        missionStates = JsonUtility.FromJson<Dictionary<string, MissionState>>(jsonString);

        jsonString = PlayerPrefs.GetString("MissionCheckpoints", "");
        missionCheckpoints = JsonUtility.FromJson<Dictionary<string, int>>(jsonString);
    }

    // Save mission checkpoint
    public void SaveMissionCheckpoint(string missionID, int checkpointIndex) {
        if (missionCheckpoints.ContainsKey(missionID)) {
            missionCheckpoints[missionID] = checkpointIndex;
        }
    }

    // Load mission checkpoint
    public int LoadMissionCheckpoint(string missionID) {
        return missionCheckpoints.ContainsKey(missionID) ? missionCheckpoints[missionID] : 0;
    }
}

This script provides a complete mission system with support for checkpoints. You can use this system in your game to manage missions, track their states, and handle checkpoints for saving and loading progress.

using UnityEngine;
using System;
using System.Collections.Generic;

// Define enum for mission states
public enum MissionState {
    Inactive,
    Active,
    Completed
}

// Define event for mission completion
public class MissionCompletedEventArgs : EventArgs {
    public string MissionID { get; private set; }

    public MissionCompletedEventArgs(string missionID) {
        MissionID = missionID;
    }
}

public class MissionManager : MonoBehaviour {
    // Event for mission completion
    public static event EventHandler<MissionCompletedEventArgs> MissionCompleted;

    // Dictionary to store mission states
    private Dictionary<string, MissionState> missionStates = new Dictionary<string, MissionState>();

    // Dictionary to store mission checkpoints
    private Dictionary<string, int> missionCheckpoints = new Dictionary<string, int>();

    // Dictionary to store NPC positions
    private Dictionary<string, Vector3> npcPositions = new Dictionary<string, Vector3>();

    // Dictionary to store player inventory (guns, ammo)
    private Dictionary<string, int> playerInventory = new Dictionary<string, int>();

    // Dictionary to store player position
    private Vector3 playerPosition;

    // Start a mission
    public void StartMission(string missionID) {
        missionStates[missionID] = MissionState.Active;
        missionCheckpoints[missionID] = 0; // Initialize checkpoint
        Debug.Log("Mission started: " + missionID);

        // Trigger mission started event
        // You can hook into this event to handle mission-specific initialization, like showing UI or starting dialogue
        // For simplicity, we're not showing that here
    }

    // Complete a mission
    public void CompleteMission(string missionID) {
        missionStates[missionID] = MissionState.Completed;
        Debug.Log("Mission completed: " + missionID);

        // Trigger mission completed event
        OnMissionCompleted(missionID);
    }

    // Check if mission is active
    public bool IsMissionActive(string missionID) {
        return missionStates.ContainsKey(missionID) && missionStates[missionID] == MissionState.Active;
    }

    // Check if mission is completed
    public bool IsMissionCompleted(string missionID) {
        return missionStates.ContainsKey(missionID) && missionStates[missionID] == MissionState.Completed;
    }

    // Trigger mission completed event
    protected virtual void OnMissionCompleted(string missionID) {
        EventHandler<MissionCompletedEventArgs> handler = MissionCompleted;
        if (handler != null) {
            handler(this, new MissionCompletedEventArgs(missionID));
        }
    }

    // Save mission states for checkpoints
    public void SaveMissionStates() {
        PlayerPrefs.SetString("MissionStates", JsonUtility.ToJson(missionStates));
        PlayerPrefs.SetString("MissionCheckpoints", JsonUtility.ToJson(missionCheckpoints));
        PlayerPrefs.SetString("NPCPositions", JsonUtility.ToJson(npcPositions));
        PlayerPrefs.SetString("PlayerInventory", JsonUtility.ToJson(playerInventory));
        PlayerPrefs.SetString("PlayerPosition", JsonUtility.ToJson(playerPosition));
        PlayerPrefs.Save();
    }

    // Load mission states for checkpoints
    public void LoadMissionStates() {
        string jsonString = PlayerPrefs.GetString("MissionStates", "");
        missionStates = JsonUtility.FromJson<Dictionary<string, MissionState>>(jsonString);

        jsonString = PlayerPrefs.GetString("MissionCheckpoints", "");
        missionCheckpoints = JsonUtility.FromJson<Dictionary<string, int>>(jsonString);

        jsonString = PlayerPrefs.GetString("NPCPositions", "");
        npcPositions = JsonUtility.FromJson<Dictionary<string, Vector3>>(jsonString);

        jsonString = PlayerPrefs.GetString("PlayerInventory", "");
        playerInventory = JsonUtility.FromJson<Dictionary<string, int>>(jsonString);

        jsonString = PlayerPrefs.GetString("PlayerPosition", "");
        playerPosition = JsonUtility.FromJson<Vector3>(jsonString);
    }

    // Track player action
    public void TrackPlayerAction(string actionName) {
        // Implement logic to track player actions
        Debug.Log("Player performed action: " + actionName);
    }

    // Store NPC position
    public void StoreNPCPosition(string npcID, Vector3 position) {
        if (!npcPositions.ContainsKey(npcID)) {
            npcPositions[npcID] = position;
        } else {
            npcPositions[npcID] = position;
        }
    }

    // Store player inventory (guns, ammo)
    public void StorePlayerInventory(string itemName, int quantity) {
        if (!playerInventory.ContainsKey(itemName)) {
            playerInventory[itemName] = quantity;
        } else {
            playerInventory[itemName] += quantity;
        }
    }

    // Store player position
    public void StorePlayerPosition(Vector3 position) {
        playerPosition = position;
    }

    // Get player position
    public Vector3 GetPlayerPosition() {
        return playerPosition;
    }
}

This updated MissionManager class includes methods for tracking player actions (TrackPlayerAction), storing NPC positions (StoreNPCPosition), storing player inventory (StorePlayerInventory), and storing player position (StorePlayerPosition). Additionally, it provides methods for saving and loading this information along with mission states and checkpoints.