When simple text generation is simply not enough: Part 2
In the last installment I illustrated the immediate difficulties game designers run into when they try to dynamically create text output that takes random game objects into account. Grammar gets in the way…
Today I want to show you how I approached the problem to create my Grammar script package for Unity3D. To understand the approach and its solution, it is best to a look at a sentence in a more abstract form. When we have sentence like this
You can break it down into its parts.
We can now easily create thousands of sentences, using this exact structure by simply substituting its elements. We could, for example, say
Different sentence—same sentence structure. The definite article is dropped here because Frodo is a proper name, the verb is now to grab, and the object has become a dagger. Not a whole lot different than the previous sentence, but the fact that the proper name doesn’t need an article is worth remembering.
Another example could look like this…
Same structure, but a few more rules kicked into action. Because the orcs is a plural form, the verb form changes as well. The s from grabs has been dropped to reflect the plural. In addition, because swords is also a plural form, the correct indefinite article is now the word some. (It could be omitted altogether just as well, depending on your preference.)
The easiest way to accommodate these adjustments in dynamic text is to create an engine that can generate the necessary sentence elements by itself. In order to do that, it needs some information about the objects in question.
For that purpose, I am setting up a data structure to hold the most basic grammatically-relevant attributes. This GrammarAttr data structure contains info, like the name of the object, as well as its gender, a count and plural flag, a flag to indicate whether it’s a proper name, and a few others.
The name in the structure is using a special format. Because it is not enough to simply drop in the name itself, I needed a format that allows me to generate correct singular and plural names of an object. Since no rules exist for this in any language, it needs to be hard-coded. To make it work I am using a format that consists of the word base, then the extension for the singular form and then the extension for the plural form, all separated by a period. Here are some examples.
To generate the singular form for woman, I would therefore concatenate the word base wom with the singular ending an. Whereas the plural would consist of the word base and the ending en to create women.
Identically, when creating the singular form for orc, I would take the word base orc and add the singular extension to it, which is empty, so the word remains orc. To generate the plural, we concatenate orc and s.
It is a very simple and very effective process, really, that I’ve been using unchanged for over 20 years. To make thing more convenient, my implementation also lets you drop the periods, if you have a proper name, for example, that does not have a plural form, so Frodo could be simply entered in the system as Frodo, without any periods.
With all the attributed found in the GrammarAttr data structure, we can now go about designing actual tags that we can use to mark up the source text. I decided to enclose all tags with square brackets. This keeps things readable and it allows me to parse tags in the string very easily with a simple Regular Expression.
Since I want to make sure these tags can be easily understood and will help the translation of text into other languages, I decided to use natural language for these tags. In order to dynamically generate sentences like the ones above, my source string would, therefore, look like this
Immediately, I am sure, you grasp the elegance of this because I am certain that just by looking at it, you will instantly understand how it works. But, you may also notice some of the inherent ambiguity.
The sentence contains the tags [The-name] and [a-name]. But how does the program know, which is which? This is where the GrammarAttr data comes in. When parsing the text to generate the final output string, I will pass a list of the referenced objects into the function as a parameter. This means that I will have a GrammarAttr object for the orc, and one for the sword.
Now it becomes a simple matter of reference. In order to tell the software, which object is which, we simply extend the tag and write this instead.
As you can see, some of the tags have a prefix now—once again, separated by a period—creating a grammar context. At the beginning of the sentence, I set the context to the first object (the orc) so the software will generate the output The orc. Note how the capitalized T in The-name indicates that I want the output text to start with a capital letter as well.
The Grammar module will first read the reference, grab the attributes for the first object and extract the name. Then it will slap a definite article in front of it—provided it is not a proper name, which does not require an article—and generates the output.
Naturally, if we were to set that data attributes to indicate that we are dealing with multiple orcs through the use of the plural flag or the actual count variable, the program would generate the respective plural form of the name.
As we move on, we hit the word/tag combination grab[s]. Since we have previously set a grammatical context—we are referring to the first object in the list of GrammarAttr parameters—the program can now check to see if there is one orc or many. It will then add the letter s to the verb grab, depending on the status of that flag. This will therefore generate either the sentence fragments The orc grabs or The orcs grab.
The next tag has a prefix once again, creating a new context. We are now referring to the second GrammarAttr object in the parameter list, and we want it to print the name of that object, complete with an indefinite article.
Easy enough. Grab the attributes, generate the name, generate the correct article, depending on the name and the other attribute flags, and voilà, we have a full sentence that says
As I pointed out before, by simply changing the attributes in the GrammarAttr data structure, we can now create sentences addressing thousands and thousands of different objects, if we wish, using the same source sentence, all showing up with the correct grammatical rules applied.
Granted, this is a very simple sentence and a very small example, but it illustrates the approach I am taking to generate grammar-aware text strings. If you join me next time, I will show you how other tags allow us to create much more complex sentences, and in a future post, I will also explain, how all this fits into making localization easy and safe. I’ll see you soon…