Custom Quest Editor

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

2 Likes

Wow. That is very impressive! Congrats!

I’m really impressed! Well done on taking the prototype I put out there and running with it.

Out of interest, how are you referencing things like the enemies and transforms. Is the quest asset linked to a particular scene?

Thanks Sam!

The quest asset sits outside of any particular scene - the thing that allows this linking to scene objects is that the EditorGUILayout functions have an overload which allows for a boolean argument:

EditorGUILayout.ObjectField(Object obj, Type objType, **bool allowSceneObjects**)

I… don’t actually know how or why this works, but this is what makes relevant scene objects appear in the popup dialog.

However, the references to the scene objects can’t be saved to the asset (they either just get forgotten or you get “Type exception” or “Missing reference” when you try to reload), so once the scene object has been chosen within the editor, I just save the name of the object instead, and access the actual scene object when needed using GameObject.Find().

In that sense, I suppose the quest asset is “tied” to a scene, in that it can’t have objectives which reference objects from different scenes, but I think there might even be ways around that with some more clever storing/restoring of the objects.

You might have a problem with uniqueness of name in that case. I would recommend implementing a UUID system on the relevant assets. Good job though on finding out about that boolean option. Cool stuff.

Privacy & Terms