Thursday, August 6, 2015

Creating a Questsystem for my Game

The Greenbox System

How to best implement Quests seems to attract quite a few questions among
(new) game developers (see links below) and a good Quest system probably requires a lot of thought even from experienced game developers. Obviously, the quest system you choose strongly depends on the type of game you want to implement.

I will not talk about using an existing quest system that ships with an engine or hard-coding individual quests in this blog post. Instead, I like to describe my self-made approach towards a quest system which reads the data for each quest from an external resource. I will give code examples which hopefully can inspire other game developers facing similar issues.

Text, Code and Localization

A Quest consists of two parts: The story, which is written in natural language and meant to be read by the player, and some machine-readable instructions for the computer. These machine-readable instructions contain all the logic of the quest. In my case, a quest is not automatically fulfilled, but requires clicking on it to fulfill it. Typical quests include "go to place X", "give away item Y" and  "fight monster X at place Y".
The main goal of my quest system is  that quests should be read from an external resource (a file or database), which makes it easier to later add additional quests to my game. In order to be as flexible as possible with my quests, I decided to use a mini-language which describes the machine-readable aspect of the quest. Quest objectives would be described like this: "atPlace(1); giveItem(2);". By using a mini-language instead of writing pure python on my cards, I reduce security risks from errors on the cards' machine-readable code.
This code is then parsed by the server, which in addition generates a human-readable description of the quest objective. Writing code to create correct English sentences for such a code is quite tricky, but it is way easier than parsing human readable descriptions to generate machine-readable code. Trust me, I have tried it...
However, I most certainly want to generate the human readable description of the quest objective directly from the code instead of manually adding it for each quest, because this makes sure the description is consistent with the underlying logic (Something that is not true for all commercially available games and apps).
All quest logic is shown on green boxes of the cards displayed on screen, which is why I call my system "greenbox system".

This greenbox system can be extended to all sorts of logic appearing on quests, items or abilities. As my game originally was a card game, everything in my game is represented by cards: quests, items, abilities, occurrences and equipment. And each card can have greenboxes with machine-readable code.

Parser, Evaluator and evaluate()

The machine-readable mini-language is parsed using the following concept:
First of all, the string is separated at semicolon characters into individual instructions. Then the instruction is separated at the parenthesis characters into a function name and arguments.
Depending on the situation, only some instructions are allowed. It does not make sense for a quest objective to dispense a reward. For each of the main situations, I use a different evaluator class. In the case of objectives, it is the ObjectiveEvaluator. Each evaluator class contains a dictionary where the allowed instructions are stored and translated into a member function. (Luckily, python allows functions to be used as dictionary values.)

For each instruction in my mini-language, a member function of my evaluator object is called, which sets some attribute of the evaluator object. When parsing is complete and the green box is clicked by the player, I simply call the evaluate method of the evaluator object and the instructions are executed.
Typically each instruction will perform some database queries to check or modify the game state and return True on success and False on failure. If one instruction returns falls, the database is rolled back and a message is displayed informing the player what went wrong. If all instructions return True, the session is committed.
  
  class ObjectiveEvaluator:
    """A class to parse greenboxes of type Objective'"""
    def __init__(self): 
      self.allowedCommands={
              "atPlace":self.atPlace, 
              "giveItem":self.giveItem
              }
      self.conditions=[]
    def atPlace(self, arguments):
      """The player needs to go to a specific place."""
      self.conditions.append(PlaceChecker(arguments))
    def giveItem(self, arguments):
      """The player needs to give away an item."""
      self.conditions.append(GiveItemChecker(arguments))
    def evaluate(self, dbSession):
      """The code is executed (All conditions are evaluated)"""
      for condition in self.conditions:
        if not condition.evaluate(dbSession):
          dbSession.rollback()
          return False
      dbSession.commit()
      return True

From code to English


The second challenge of this approach is to produce a human-readable description of the quests objectives. I am not a native speaker of English, but I tried my best to produce quest descriptions without too many grammar errors.
In an English main sentence, there has to be at least one verb. In the case of quest objectives, it could be in the imperative form. Several main sentences can be connected by "and".
However, simply connecting all instructions with and does not result in a very nice sentence. Consider "Go to the old tree and give away a red handkerchief and pay 20 ducats". It would probably be better to write: "Give away a red handkerchief and pay 20 ducats at the old tree." This leads to the second type of grammatical construct we use except the main sentence: the prepositional phrase.
To avoid ambiguity we could also write: "Do the following at the old tree: *)give away a red handkerchief. *) Pay 20 ducats". However, I don't know the correct grammatical name for this third construct.

In my program I always translate instructions to prepositional phrases if the instruction describes a place and if there is at least one instruction translated to a main clause. If there are too many main clauses, I use the third construct.

In order to allow easier localization to other languages than English, I keep the code that creates the human readable interface separate from the rest of my code base. The translation from the mini-language to human readable language relies heavily on the member variables and some implementation details of the Evaluator classes. This means I have a high coupling between code from two different classes, which is suboptimal. As different languages have different grammatical rules, I cannot simply use a string in human readable language an fill in the blanks. Thus the only way to avoid this coupling would be to parse the original mini-language separately for the purpose of translation into human readable language, but this seems to introduce other design flaws...

Links

Discussions/ questions about Quest systems (external links)

No comments:

Post a Comment