Over the past week, I’ve been working super hard on putting this together for my RPG project, and I’m so proud of how it’s turned out.
I spent a long time thinking about how my quest system “should” work, and ran into a few technical roadblocks along the way, until eventually I decided to take inspiration from @sampattuzzi’s Dialogue Editor prototype from github and create something similar. I’d never made a custom editor window before, so it was a steep learning curve, but gradually I got there.
How it works:
- Quests are saved as ScriptableObject assets, containing a list of Objectives where each Objective is a simple (serializable) object not derived from a monobehaviour or a scriptable object. The QuestEditor window has access to the currently selected Quest, and loops through each of its Objectives to draw all the relevant nodes.
- Each Objective, when it is constructed by adding a new node, is given an ID which is unique (within that Quest), and the links from “parent” to “child” objectives is also stored as a list of ID pairs on the Quest SO (this is so that the links can be serialized and saved properly).
- At run-time, any Quest attached to a Journal monobehaviour on the Player loops through all of its objectives and, if they do not have any pre-requisite objectives (as defined through the links on the graph), attaches an ObjectiveBehaviour script to a manager GameObject. These monobehaviours are what allows the game to check for objective completion (i.e. whether the player has come within a certain range of a TravelObjective desination)
- Objectives trigger an event on completion, and any Objectives which are dependent on this completion are subscribed to this event. Objectives can have multiple pre-requisites, and will not start (and instantiate the relevant behaviour) until all the previous Objectives are complete; or completing a single objective can trigger multiple subsequent objectives in parallel. Once every Objective from a Quest has triggered their completion event, the Quest triggers its own completion event which is detected by the player’s Journal component.
There was a lot to work around while putting this together, especially with understanding how assets are serialized and saved, to make sure that all the required data was saved. However, one thing I’m pleased with is that objects from the scene can be assigned as the targets for objectives, even though the scriptable object is not in the scene. These are saved on the asset file string-referenced by their name (I might get this to use some sort of GUID instead) and then restored to the objective using GameObject.Find().
I think this is really neat and tidy, and minimises the clutter of saving tons of Quests and Objectives as their own separate assets, and allows for better parenting/linking of Objectives than having them as monobehaviours and filling up the scene with quest gameobjects.
For now, I’ve only created two types of objectives, but I’ll add more as I think of what would work.
Thanks for reading, hope you like it!
~ Jonny