I've just started playing Elder Scrolls IV: Oblivion a few days ago, and one thing I cannot help but wonder about is how the quest system is handled programmatically?
Specifically, there are many dozens (hundreds?) of quests and even sub-quests in the game, all of which are reflected in the game's environment in numerous ways: from scripted events that happen in-game at specific times/places between specific people (assuming they're still alive), to related quest items (some of which are associated with particular NPCs and states of the quest), to varying dialog with different NPCs (again, with a complex tree that can be altered by dialog with other NPCs & the quest's general state).
In addition, you can swap between active/inactive quests at any point, so these complex dynamic behaviors that radically affect the environment around you are taken one step further by being completely interchangeable.
It seems like a logical nightmare, and I'm having a hard time grasping how something so deep and rich can be defined programmatically, so seemingly flawlessly.
Is there anyone with experience in this sort of thing that can explain (broadly) what goes into these sort of systems?
I'm just guessing here. Haven't done this kind of programming but I've thought a lot about how they were doing it while playing RuneScape.
Probably there are a bunch of flags and variables associated with your account, and as you progress through a quest, their values change. At first, Character X is marked as alive, and in Location Y. Later, she's marked as in Location Z. Then, she's dead. So, when you enter Location Y, it checks the variable to see if she's there, and puts her object there, or not. Meanwhile, another player who hasn't started the quest can be in the same area and see something else entirely.
I'm no game designer but I can see something that may be at work here...
Object Oriented Programming allows for this kind of complexity and dynamism by encapsulating data and logic neatly within many objects. These objects can interact 'verbally', using inter-object messaging to delegate tasks to one another. The sender of the message need not know how the receiver will interpret the message, leaving the method of action entirely up to the receiver. Just like in the real world, delegation allows things to run more smoothly.
For instance, when you order a mushroom risotto from Del Posto in New York City, do you order it by walking back to the kitchen and talking to the chef directly? Do you put on an apron and prepare the risotto yourself? Do you drive to the nearest mushroom farm and pick your own mushrooms? No, you don't. You simply delegate these tasks to the waiter, the team of chefs, and the produce supplier, respectively. To get your mushroom risotto, all you need to do is tell the waiter that you want it. The rest is done automatically, through a chain of delegation. This same kind of delegation likely exists in your game.
Now, back to objects. Some objects inherit data and logic from parent objects, and in this way, can have a great deal of data/logic in common; let us call these siblings. In the example of the game quest, each quest is likely its own 'quest object', with baseline data and logic inherited from the parent 'quest parent'. Quest siblings can be differentiated from one another by tacking-on extra data/logic that pertains to the specific quests.
Depending on your actions in the game (and your selections in the game's menu, perhaps), the game object can swap out one the active quest object for one of its siblings. Often times, this can be done with a really simple command (in pseudo-code, not modeled after any particular programming language):
gameObject.activeQuest -> getCurrentObject();
//returns the object containing all of the data/logic of the current active quest:
Gibbons_GoldenArtifacts
//Let's say the quest description is "Scour the Catacombs of Gibbon for a
mysterious treasure"
gameObject.activeQuest -> setCurrentObject(Gibbon_DefeatGhost)
//sets the activeQuest object (note that QUEST OBJECT contains baseline data/logic)
//assuming, say, Gibbon_DefeatGhost is an object like so:
Gibbon_DefeatGhost={QUEST OBJECT};
Gibbon_DefeatGhost.extend(
description="Defeat Gibbon's ghost to retrieve his golden artifacts";
objective="Defeat Gibbon's ghost";
questNPC="Gibbon's ghost";
questLocation="Gibbon's Inner Sanctum"
questTriggers[1]="When PLAYER enters Gibbon's Inner Sanction: Release Gibbon's ghost";
questTriggers[2]="When Gibbon is slain: Drop Gibbon's golden artifacts"
)
From that point on, the game object itself will probably interact with the active quest object the same as it always has, but the active quest object may perceive those interactions in ways that are different from the ways of sibling quest objects, resulting in a different game experience.
Hope I'm not totally fudging the clown on this one...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With