Quest/Mission System

    This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

    • Quest/Mission System

      Hey Rez,

      I am trying to figure out how I should go about doing a mission system, I remember you mentioning once how you did it in script for the Sims Medieval.

      The way I had thought about doing it was to have a script which listened for mission events, and then just mark them as completed etc. But what I am having trouble coming up with is, does this listener listen for just one event? What I mean is, if I have a mission which says, "Find 3 apples", how do I go about this? Each time you collect an apple you send a new apple found event? Or would this be a generic event for all mission types? What is the best way to go about doing this?
      PC - Custom Built
      CPU: 3rd Gen. Intel i7 3770 3.4Ghz
      GPU: ATI Radeon HD 7959 3GB
      RAM: 16GB

      Laptop - Alienware M17x
      CPU: 3rd Gen. Intel i7 - Ivy Bridge
      GPU: NVIDIA GeForce GTX 660M - 2GB GDDR5
      RAM: 8GB Dual Channel DDR3 @ 1600mhz
    • RE: Quest/Mission System

      The answer really depends on what you need from a quest system. The quest system we had in The Sims Medieval was extremely complex and was one engineer's full-time job for the duration of the project. At least half of the design team was tasked with building quests full time. We couldn't get away with simple fetch-it quests (it's a Sims game after all) so every quest had to be unique and deep.

      The basic system revolved around a graph structure where each node represented something that could happen. A node might trigger a role Sim to spawn or a dialog box to notify the player, or a reward to be given to the player. Going through a quest was just a traversal of this graph.

      In terms of setting up quest events, there are really two ways to handle it. One is a polling method and the other is a push method. The first is to use a polling method where the quest system polls various systems for the data it needs. This way, the quest knows which nodes are currently active and every so often will ask the appropriate systems whether or not the condition is satisfied. The second method is to use a push method, where other systems send events to the quest system whenever something interesting happens and the quest system either ignores the event or handles it.

      The polling method is best when you have a lot of possible conditions you want to test, but you're usually only testing a small amount at a time. This keeps you from having to throw events all over the place and consolidates that logic in one place. The disadvantage is that you have a slight performance cost of checking for quest conditions every frame, although this is usually very small, especially if you don't have to do it every frame (once a second is probably fine). It also means that your quest system needs to know about a number of other systems, although the reverse isn't true. In other words, your inventory doesn't have to care about the quest system, it's just the quest system that asks if the inventory contains the objects it wants. This is what we did on The Sims Medieval.

      The pushing method is best when you have a lot of simultaneous conditions you're checking for, making the performance cost of polling every frame not worth it. This is how a lot of achievement systems work (included the one in The Sims Medieval). It also has the advantage of decoupling the systems. For your apple example, the inventory would just send an event every time a new object was added. Any number of systems could listen for this event, including the quest system. The disadvantage to this system is that you're spreading the performance costs around to other systems by forcing them to constantly send events.

      The decision really comes down to what you think will happen the most. If you only have a few things you're checking for and you're almost always checking for them, the push method is going to be the best since you only incur the cost of checking when necessary. A lot of games only have one or two things they're looking for. So, if all of your quests are object based, this is probably the way to go. Even if there are a few things, like maybe objects as well as monsters, then pushing is the way to go. You can even create a special component for quest items and quest monsters that send the event so that normal items and monsters don't.

      If you're more like The Sims Medieval where ANY interaction, object, location, NPC, or even time can trigger a quest update, then you're probably better off using the polling method. Otherwise, you'll end up triggering an event for pretty much everything and ignoring almost all of them. On The Sims Medieval, we were usually only looking for a handful of things, so checking every second or so was just fine, and probably faster then the overhead of all those events flying around.

      Regarding script vs code, we used C# as our scripting language. The entire quest system was in C#, with the exception of the quest data loading system. I'm pretty sure that went through our normal XML loader, which was in C++. We also had a quest tool which was a stand-alone tool written in C# that spit out the appropriate XML to be consumed by the game.

      If I were to make a similar system in my own engine, I would write the core quest system in Lua. The quest manager and quest nodes would all live there. I have a general rule where I don't tick the script every frame. There's no Update() method in Lua that ever gets called, Lua only responds to events, which causes some script code to be run. All per-frame updates happen in C++, so I would decouple the quest polling from the system itself. Quest polling would be a C++ process that's spun up from Lua. The quest system would update this process with the appropriate conditions to listen for and the process would check for them every 500 - 1000 ms (I would tune it so it felt right). Whenever a condition was met, it would send an event to the quest system, which would process the results.

      One of the challenges here would be to set up a generic(ish) polling system that allowed me to attach conditions that didn't require me to call into Lua. Calling Lua from C++ is VERY expensive so you want to do it as little as possible. I would want to be able to have a quest set up the appropriate conditions and then only be called when those conditions were met. C++ needs to be able to evaluate those conditions without calling into script at all.

      I think the trick is to find ways to simplify. On The Sims Medieval, we had the concept of Buffs, which was carried over The Sims 3. A buff was really just a flag that could have a gameplay effect. It could add to your mood, modify the AI behavior, etc. Buffs were generally a visible icon on the UI, but many buffs were totally invisible or used for special purposes. For example, a Sim's religion was a buff. One of the quest nodes we created was the ability to set, clear, and test for the existence of buffs. It was a way to attach and read generic flags on a Sim. It also served as a way to reward the Sim since some of them effected gameplay and mood. You should design a dozen or quests that are representative of the final quests you'd have in the game before you write even a single line of code. This will help you focus in on the things you really want. With luck, you should be able to distill it down to just two or three core things you want to be able to do with a quest.

      Of course, this is true for any system. In the simulation side project I just completed, everything that was related to gameplay was a statistic. A statistic was really just a number that could decay at some rate. The decay itself could also decay. Decay rates could be based on other stats and all AI decisions were based on stats. I wrote a statistic component that I attached to entities that defined its stats, wrote a system that handled the simulation of those statistics, and the rest was based on it.

      Anyway, I'm probably just rambling at this point. Let me know if you have any other questions.

      -Rez