Thursday, May 14, 2009

Scripting in XNA, Part 1: Introduction

So you want a scripting engine, do you? And you want one for your XNA Game Studio game? Well, you've come to the right place! This is a ten part tutorial series that will take you through every step in the scripting process, from building a bytecode virtual machine, to constructing a compiler that will translate your scripts into bytecode. It's a long arduous process, even for the simplest of scripting languages, but I find it very rewarding.

Unfortunately, we can't just dive right into the code, so this first part is devoted to an introduction to scripting, and how it applies to XNA Game Studio. If you're itching to dive right in, then feel free to skip to part 2, though I recommend reading this section anyway. There are some issues that need to be covered before we can begin working on a scripting engine. For example...

What is scripting?

Quake did it. Unreal did it. Neverwinter Nights did it. World of Warcraft does it. Just about every modern game does it. So what's the big deal about scripting?

Think about it like this. When you make a game, it takes a lot more than just code. You also need to create assets for your game. You need 3D models for your characters and environment objects, as well as textures, normal maps, and so on. You need sound effects and music. You need data files that describe your levels, weapons, character stats, etc. These are all assets that you send through the Content Pipeline and load into your game at runtime.

So while an MP3 is an asset for game music, and a 3D model is an asset for a renderable game entity, a script is an asset for game logic. The idea is that you should write your game engine to support the mechanisms behind the behavior for your game, but scripts actually execute that behavior. This can be an appealing design, because it segregates your game engine from game logic. Scripts are often used for different kinds of dynamic behavior, such as AI or cutscenes.

Do I need scripting in my game?

Probably not. Scripting is an extremely powerful concept, but frankly, it's overkill for most kinds of dynamic behavior that you would want to achieve in a game. There are many simpler options that are more robust and easier to implement than a scripting engine. For example, friendly XNA MVP jwatte has an excellent article explaining his path system for dynamic behavior. For most games, this kind of system is far easier to use, yet it is powerful enough to handle most of your dynamic behavior needs.

Also, consider some of the common reasons for why scripting is so popular among games. For example, most games are written in C++. And C++ compiles slowly, especially compared to C#. This is why there is such a strong culture against "hard-coding" game logic; every time you wanted to change one little detail, you have to re-compile. C#, however, compiles much faster, so re-building isn't as much of a hassle. Besides, if you're making an Xbox 360 game, you have to wait for deployment anyway, so the time that you would have gained is miniscule. (By the way, if you're looking for a really neat system to tweak in-game constants without re-compiling, take a look at this article by Shawn Hargreaves. A must read.)

So the moral is, scripting isn't as useful for XNA GS games as it is for traditional C++ games; there are so many ways to achieve the same thing with much less effort. Personally, I believe that scripting still has its place, particularly for complex games like RPGs. Scripting definitely isn't right for everyone, so if you're considering implementing a scripting engine for your game, I would strongly recommend looking at your other options before turning to scripting. You may be doing more harm than good by undertaking this endeavor.

What about IronPython/LuaInterface/etc.?

They won't work. Not on Xbox 360, anyway. Most scripting implementations available for C# rely in System.Reflection.Emit, which is used to generate types, methods, and IL code at runtime. Reflection.Emit is not available on the .NET Compact Framework on Xbox 360. To get around this problem, one option is to incorporate projects like LuaInterface into the Content Pipeline, which will only run in Windows. The now-defunct XNua project took this route. One disadvantage here is that you can't compile scripts at runtime on Xbox 360. Of course, most games won't need a feature like runtime script compilation.

The other option is to write your own compiler and virtual machine, entirely in C#. This is the route that this tutorial series will be taking.

What's next?

I think I've spent more than enough time talking about the rationale behind scripting. Once again, if you think you need scripting for your game, I urge you to look for an easier option before you turn to writing a scripting engine from scratch. This will not be an easy task, even for a relatively simple scripting language. However, if you're interesting in doing things the hard way just for the fun of it (like me), then welcome to the party! After all, how many people do you know who have written a compiler on their own? Not many, I'll bet.

Creating our scripting engine will involve many different tasks. Here is a quick preview of the upcoming tutorials in this series:
  1. Introduction - You're reading this right now!
  2. Designing a virtual machine - We'll start at the low level and discuss the bits that will actually be running the bytecode generated from our scripts.
  3. Implementing the virtual machine - At this point, we'll finally be ready to write some actual code. We'll implement the VM that we designed in part 2.
  4. Interop with C# - Scripts are useless in isolation, so we'll need to be able to call C# methods from our scripts.
  5. MiniLua - Now we're ready to design our language. It will be based off Lua, one of the most popular game scripting languages. We'll also touch on some basic compiler theory.
  6. Writing a lexer - Step one of compilation is lexing our script into tokens.
  7. Recursive-descent parsing - Next, we take the stream of tokens from the lexer and parse them into statements.
  8. Building a syntax tree - From our parser, we need to create an abstract representation of our script.
  9. Code generation - Finally, we walk through our syntax tree and generate the bytecode instructions that will be fed into the virtual machine.
  10. Putting it all together - Time to tie up a few loose ends. I'll also provide some resources for further study if compiler theory piques your interest.
We've got a long way to go from here. I'll try to keep up this series by posting one tutorial per week. We'll see if I can stay with that commitment. I'll see you all again next week, when we discuss the design of our virtual machine.

Bwah.