Mar 17, 2010

The Dragon Age of mainstream game scripting

It's amazing how much depth great role-playing games can have to them. Some of the quests, which start as usual pre-canned in-game experience, can suddenly spill themselves onto higher layers of reality, sometimes in quite unanticipated ways.

I had been enjoying Dragon Age: Origins for quite a while already, before I found myself engaged into one of such meta-quests, which I'd like to tell about.

So, I went to Orzammar, the underground dwarven capital, where the "A Paragon of Her Kind" storyline subplot started to unwrap. There was quite intricate political struggle for the crown between two houses going on there, and apparently I was expected to eventually side with one of the parties.

In quite a boilerplate way, the two emissaries were giving tasks to be fullfilled in order to prove the loyalty to either of houses. Apparently, it was also anticipated by the developers that player might want to try playing both sides in a double-agent kind of way, and that's what I've tried doing...
just to find out that the whole plot got stuck into a logical deadlock, which turned out to be a bug. Of course, I did not have the recent save games to reload from (of course!), so I became disappointed and unmotivated.

Luckily, Bioware released the Dragon Age Toolkit, which any owner of the game can download and use to modify the game to his taste. It's a great move from their side to have it released, which me and I believe a lot of other people really appreciate. So, I downloaded it, installed and digged straight into the scripts trying to find the place which could be patched to allow me keep playing.

Long story short, I succeeded at hacking the game plot script and could enjoy the game again. It would be a perfect time for the happy end, perhaps, but the whole "fix the quest scripts to allow continuing playing the game" meta-quest spawned even new set of quests in different layers of reality.

The first quest was about learning how the DA Toolset works, getting insight into the tools and workflow which developers used to produce this great game, examining its strengths and weaknesses. Very useful experience.

But there was another thing, which somewhat stroke me. In the process of digging the scripts I've got the impression that the script code structure is quite a bit more complicated than it could be. The logical deadlock bug in the scripts, mentioned above, turned out to be not as surprising as one would expect, given that author had to anticipate the combinatorial explosion of all the many possible developments in the plot, and the language used for that did not seem helping much in that... and I wonder how much QA efforts and bugfixes  it needed before getting into the shippable state.

This another meta-quest was about understanding the current state of higher-level game programming DSLs (aka "game scripting languages"), which in turn made me also sidetracked into such side-quests as trying to learn about the Prolog programming language, semantic networks and what else... but I digress.

In case of Dragon Age, the scripting language has a simple C-like structure, and workflow in scripts is centered around events coming from the engine and other scripts.

When it comes to the story development, scripts collaborate closely with the conversation editor, which is used to build the dialog trees (which are not actually trees, but rather DAGs, still represented in a tree view in the editor). Each branch in the dialog tree can be masked out by some global flag, as well as it can change some global flag if player picks this dialog branch in the conversation.

This leads to the following patterns:
  • there is a rather extensive set of global flags, related to the given storyline. Sometimes these flags can be quite complicated (e.g. "A_HAPPENED_BUT_B_HAVENT_HAPPENED_YET_AND_PLAYER_DID_C")
  • when flag changes (triggered by the player picking certain dialog branch or for some other reason), it fires the event. Events are handled in the scripts, in rather big switch/case blocks.
  • every case block can have complex and nested conditions checks, which may change other flags as the result, for example enabling/disabling some other dialog tree branches.
All in all, amount of bookkeeping-style code compared to the actual logic code seems to be incredibly high, and logic code itself is spread over different places, the code being heavy on side-effects.  And it's not due to the bad coding, obviously, but rather because of the traits of the scripting language and framework itself.

It seems that one of the main criterias in picking the scripting language for game development still remains familiarity, which most of the time just means syntax familiarity.
Familiarity is considered to be important, so that designers and generally people not having "extensive programming background" can use it without much problems.

And I guess it makes sense. For example, in case of Dragon Age, majority of the scripts appear to be written by intern and fresh from the college folks (one can track the author names from the scripts themselves). Now, by no means I am trying to imply that it somehow correlates with the code quality - years of experience quite often mean nothing, and no doubt those people coming from college are brilliant. But I believe it still kind of shows the general attitude in the industry. Which is: "Game logic scripting is a beginner level, routine task".

Still, the question arises: is this really the most productive level of abstraction to work with? Could be  low-level imperative manipulation with global flags and multiply nested blocks of if-then-else logic, giant case switches with side effects replaced with something which maps better into the domain area? Something, which would allow to do more complex, rich, interesting, emergent things without need focusing attention on the spaghetti logic and fighting the peculiarities of the scripting language?..

Despite all the interest, research and developments in this area, it seems that on the industrial, mainstream level, the technologies which serve as the game designer's medium for creating the high-level game playing experience, remain grossly underdeveloped. But it would be unfair to call it "the stone age", of course. It is developing, albeit slowly. And, no matter what tools are used to achieve it, in the end of the day it's the end result which matters.

Thus I dub the present as "The dragon age of mainstream game scripting".

11 comments :

Jim d said...

I've always wondered why javascript isn't used more often for game scripting. It's incredibly powerful and most people are familiar with the syntax.

Beyond that, there are now several open source implementations with large, well-funded teams that are singularly focused on speed/memory optimizations (V8, Jagermonkey, squirrelfish etc.)

Andrew P. Lentvorski, Jr. said...

But I believe it still kind of shows the general attitude in the industry. Which is: "Game logic scripting is a beginner level, routine task".

While the industry may regard game logic scripting as beginner level (I don't agree, but ...), I would point out that "game logic scripting" is one of the most uncoupled tasks in game development.

Consequently, if you have a new programmer who doesn't yet know much, your best bet is to dump him into the game logic scripting tasks and let him come up to speed.

ruslans said...

Jim,
I agree that javascript is incredibly powerful and may be a viable alternative. Things like prototype-based inheritance (which lua does as well, btw) certainly sound interesting in this context.

And I know that people actually did already start integrating V8 into the game engines, I believe we will hear quite a bit of success stories in the near future.

The problem is, that it's not just about the language, but also rather about the framework in which this language is used.

Even given the same language (say, the mentioned C-like custom language), the levels of abstraction in which designers express themselves can be different.

In this particular case people did not seem to have much choice - the whole global-flags/switch-case-event-handlers/side-effects/complex-conditionals was primarily dictated by the framework, not the language.

ruslans said...

Andrew,
I agree with that being one of the pragmatic rationales as well.

The problem with this (in my opinion, of course), is that getting up to speed in an uncoupled environment, without guidance and lateral experience, is not that effective.

One has to get his hands dirty into the things which involve working with other people as close as possible. By failing at them, seeing how things are connected together and affect other people, one gets wider perspective much more quickly.

But regardless, whatever the real reasons are, they don't really discard the observation.

Jim d said...

Ruslan,

I agree that the environment seems too down-in-the-weeds for something like scripting game logic. I can easily see a developer (regardless of skill) getting lost in the mechanics of case statements and ANDing constants and not being able to rise above it to see the bigger picture.

Great article, by the way!

die schnalle said...

i think the framework depends a lot on how the game is structured (though i think handling game state through constants is definitely a nightmarishly bad idea).

are the goals strictly hierarchical (you have to do a && b before c becomes available รก la GTA4?) or is it an open-world like system (elder scrolls morrowind allows speedruns in 4 minutes, going straight for the boss and killing him).

an interesting question:
could one create a system that handles sets of quest/storyline dependencies in a way it'd be possible to automatically find unhandled dead ends?

@jim: i doubt scripting logic depends on super high performance language implementations, so i'd rather use the Lua interpreter instead of LuaJIT, because the interpreter is more portable. same for javascript - tracemonkey is a lot more complex than spidermonkey, while speed gains in game scripting languages through tracing is *VERY* unlikely. better keep it simple :) (V8 is very interesting though)

Sven said...

Ruslans,
nice article.

It remembers me a discipline I have teached on the last year from the book "Concepts of Programming Languages" by SEBESTA.

When you talk about the tradeoffs, in doing a choice between programming in a more abstract way or not, by using the logic paradigm or the imperative paradigm, and when you wrote about the syntax familiarity in programming languages that could be interpreted for the writability or readability criterious that SEBESTA articulates in his book.

Now I can use your article as an example in future classes.

See ya!

Dan said...

It's an interesting article but I'd like more technical depth :D

A few examples of the C style approach and where the bug appears would be awesome!

Laurent said...

My comment is partially off topic but I couldn't resist, my apologies in advance Ruslan :).

I am not too sure the amount of boilerplate code needed is mainly due to framework limitations, it seems to me rather a deficiency of the language used (or the way it's used, cf later on). Functional languages such as Haskell are flexible enough to allow to abstract away the boilerplate code and reduce it to a (few) function call(s). I've read jaw-dropping examples of it on the web here and there.
However that requires extensive knowledge of functional programming.

If I'm not mistaken, Javascript (and possibly Lua) also allows that kind of abstraction but given the approach to scripting that most game companies seem to have (ie, delegate that task to non coders) I don't think we can hope to see any improvement there unless companies start training their scripters extensively. If one only knows basic programming they are not going to be able to write functionally by magic.
In my opinion that's not going to happen voluntarily - even gradually, most game companies are risk averse and my discussions with coders there have shown clearly that drifting away from object oriented techniques scares the bejesus out of them.

So far, the only person I've read who seems to be aware of these problems seems to be Tim Sweeney. My understanding however is that his brightest ideas seem to be mostly ignored in the game development community, probably because he dwells too much on the functional side of programming which the huge majority of coders seem to have real problems grasping not to mention even just seriously just giving an interested look at.

On a side note it's funny that they are at the same time partially embracing some aspects of functional programming with recent c#/.net extensions and declarative UI (WPF) while not realizing entirely why these approaches are more efficient.

ruslans said...

Laurent,

Pleased to see you commenting here, and thanks for the insightful message!

There is certainly a whole bunch of things I'd love to discuss after having read that.

Like, a chicken and an egg problem:
http://en.wikipedia.org/wiki/Linguistic_relativity

Is it problem of languages, or is it a problem of people, affected by languages (who affect languages, which affect people, who are affected by languages...)?

And that all the mentioned paradigms are different ways to see the same things, the complexity being managed by focusing on as few things as possible.

Like a principal component analysis, in a sense... the amount of eigenvalues may be much smaller than the matrix dimension.

Declarative programming - functional, logic, constraint programming, DSLs; you name it - it's all pretty much mental crutches which help to find those eigenvalues, or at least reduce the dimension of the problem matrix.

What indeed is amusing, is that people have been exploring these problems since long time ago, and there is already a bunch of achievements in that area.

Prolog is 40 years old already. There is Inform 7 (http://inform7.com) and whatever else (http://www.interactivestory.net/technology/).

So there is definitely a bunch of knowledge on the topic.

...the kind of knowledge lacking being a knowledge about how to get this knowledge into a large-scale, corporate game development.

saim john said...
This comment has been removed by a blog administrator.