State Pattern/FSM Quest-System

Currently I’m in the planning stages of a project I’m undertaking.
I’ve been put in charge of creating the quest-system, after having done some research I figured something that would make sense is to have the following:

A Quest Manager Class -
this predominantly has a List of Quest Objects (all possible, only those active - unsure atm)

A Quest Class -
This should have several states (inactive, active, success, failure) and be made up of a List of Tasks. It will also evaluate when each task is completed to see if the quest can change state and provide some type of reward (tbd)

A Task Class -
This will have states like with Class so it’s a bit Copy/Paste, and has an Evaluate() function to see when the task is complete - I thought about having subtask classes for killing tasks, navigation (go-to-here) tasks, gathering tasks (50 pelts), and interaction tasks (talk to John to start/finish). So you could have a quest made up of Speak to John (interaction), kill fred (kill), gather 10 daisies (gather), return to John (interaction). Once they’re all in the success state or potentially removed from the quest list the quest changes to successful.

My first question is just does all of the above actually make sense and work together?

My second question is that I’ve looked a little bit into ScriptableObjects and believe that’s what should be used for all of these as they’re not objects but really just containers of data that’s going to be manipulated based on events.

Finally, since Task & Class both require some form of State Machine/FSM design to them, what are the best ways about doing that? I’ve looked into tutorials and everytime I see “enum State[ ]” I feel that’s not a real expression of a State Machine. I’m looking for a way to implement this in a professional manner and enum just feels quite simple and not adaptable for later on should anything change. From reading Game Programming Patterns I know they advise on having either all states as seperate objects available but switching which is being pointed to (not really clear on this idea) OR having a single object that is deleted and instantiated over changes made (which makes a lot more sense to me). This is the biggest hang up I have as I’m not too sure where to start right now so any tutorials, advise or ELI5 guidance on this part will be a huge help!

Cheers
BZ

You do need a list of all the quest the player currently has.
You’ll also need a list of quests the player has already completed (Use a HashSet or a Dictionary for this one)
Finally, you need some way of giving the player new quests. (NPC’s can have a list of quests they offer)

I’d suggest making generic quest and task classes:

class Quest : ScriptableObject{
    string name;
    string description;
    Dictionary<string, string> requirements;
    Dictionary<string, Task> tasks;

    public void SendMessage(string msg, int value) {
        Task task;
        if (tasks.TryGetValue(msg, out task)) {
            task.currentProgress += value;
        }
    }
}

class Task : ScriptableObject {
    string description;
    int currentProgress;
    int targetProgress;
}

And then you create quests like this

Quest newQuest = ScriptableObject.CreateInstance<Quest>();
Task questTask1 = ScriptableObject.CreateInstance<Task>();
Task questTask2 = ScriptableObject.CreateInstance<Task>();

questTask1.description = "go-to-here";
questTask1.targetProgress = 1;

questTask2.description = "Get Pelt";
questTask2.targetProgress = 50;

newQuest.tasks.Add (questTask1.description, questTask1);
newQuest.tasks.Add (questTask2.description, questTask2);

And when the player does something that should be tracked by a quest:

foreach (Quest quest in playerQuests) {
    quest.SendMessage("Get Pelt", 3);
}

This should be a very generic and easy to work with system.

So instead of a List<> of Tasks within Quest you’d recommend a dictionary to hold all the types for quick indexing I’m assuming?

I’ve not used some of what you’ve laid out here, so not ever set up a dictionary. Also it only seems to account for a count but not of what item it relates to, nor a target for interacting with or killing. I’m assuming I can just attach a gameobject component to this and that should be able to account for it?

Quests are going to be created through the inspector by adding in new tasks until a button click and that should hold all the info discussed like gameobject to gather/talk to/kill and a count for gathering or killing multiple NPCs.

What I’m not sure on either is why you’ve a second dictionary for requirements as well. Mind breaking your throughout process down a little?

Cheers so much!
BZ

The item or target or whatever it relates to is in the task description. A quest that has an objective of killing 10 wolves, would have the description “Kill wolf”, and whenever if receives a message “Kill wolf”, it changes it’s progress. You don’t need to attach gameobjects or anything. You use the task description as a key to trigger the task.

The best way to go is to create a simple custom inspector for quests where you can click a button “Add Task” and it adds a new task to the quest with the description and everything else you want. Use a similar strategy for requirements.

Let’s say the Quest quest2 requires the player to have completed the Quest quest1. Then quest2 could have quest1 added in it’s requirements. The idea of a dictionary here is because you can have other types of requirements as well, such as level requirements, achievements, etc. You can just do requirements.Add (“level”, “5”).

This generic system should be better than having to drag objects around and creating some crazy Quest class that can handle all sorts of objects.

Or you can just go to the asset store and get one of the available quest systems there.

Speaking of which… :wink: I just got back from Unite, where we unveiled Quest Machine to very positive reception. (Info in my signature.) Here are some tips I picked up during its development:

Put your designers’ workflow first. Ask them what kind of quests you’ll need to support. For example, they might need multiple branches – e.g., to clear the forest of orcs, do you kill them or bribe them with gold? Is a single-state FSM adequate, or do the designers need multiple states to be active at the same time? What kinds of conditions will quests need to monitor (e.g., get items, talk to NPCs, defend areas, etc)? What kind of actions (e.g., activate a spawner or otherwise change the game world)? How much quest state needs to be included in saved games? Do you need to support language localization? How can you help them monitor and debug quests at runtime?

In short, the purpose is to empower designers to apply their full creativity to make enjoyable experiences for the player. Things that slow this down will impact the entire development process.

It’s quite possible that you’ll need to support polymorphism to accommodate new types of conditions and actions without risking changes to existing code. Unity’s native serialization doesn’t support polymorphism. You might want to check out Odin.

That said, there’s a lot to say for simplicity. Maybe your designers will only need a handful of rigid templates that they can mad-lib with different values. In that case, a full blown FSM with a node-based editor, etc., would be overkill. Less code means less to debug, test, and maintain.

Another idea is to use something like PlayMaker to get a battle-proven FSM editor, and just write custom quest-specific actions. But if all your designers are programmers, maybe a code-centric solution like what @MathiasDG suggests would work. (However, I prefer to keep code and data separate.)

Implementation-wise, Quest Machine works like what you described. There’s a manager class, and a quest class containing instances of linked quest node (task) classes. The quest as a whole has a state (inactive, active, success, etc.), as do each of the quest nodes.

I’m really going for the coding experience on this one, they want something that is re-usable so keeping to a simple template and expanding later is best right now.

I literally have the barest of specifications, I’m working off what was discussed as a hypothetical for about 5 minutes in a call which isn’t the most concrete information, but it does let me know I’m expected to work to what I feel is right. The system I came up with felt right. But when I look for how to implement FSM/States I just see quite advanced stuff with little explaining at all or that I can follow OR it’s people saying “try this really easy method” which uses Enums which I don’t feel is advanced as I’d like and it doesn’t seem to be expandable.

It’s just trying to find something that makes sense to me, the structure I understand and why I’d want to have it certain ways, obviously that’s impacted by the challenges in coding it, but it’s the actual doing it side that I don’t want to waste a wealth of time on doing it one way to find out it’s riddled with issues.

In that case, if you’re dead set on an FSM I recommend Unity’s FSM Tutorial. It avoids enums and puts each state neatly into its own class. Your implementation will have more fields exposed to the inspector for the designers to fill in, but otherwise it’s a good structure to start from. (Another FSM approach is to use the FSM infrastructure provided by Mecanim. This is a good tutorial on that.)

However, in your role as a tools developer, before starting your FSM code you might want to do them the favor of asking them to think about what they really want so they can provide more details. They’ll be happier in the end, and you’ll avoid the frustration of them asking you to wipe everything and start over from scratch in a different direction. Requirements-gathering is a facet of coding, too.

Cheers so much, I had seen that but wasn’t sure if it was fully what I needed.

Yea, having the most barebones feature requirements is a bit of a pain as I’m a little in limbo in regards to what constitutes a success, but ultimately working in a team and getting some more advanced coding experience and not having to have the drive for my own game is the bigger benefit here.
If it has to go through iterations then GitHub will at least be my friend to show the work done :slight_smile: