Using C# for game scripts, part 1

Alright, today I'm going to continue where my previous posts (Loading Map/Game Data from XML) left off. Those posts showed you how to get game data into your game, but how do you define game behavior? Simple! With scripts.

Now, there are lots of solutions for scripting available with one of the mose popular being Lua. However, I want this to be simple and easy, so we're going to be using C#. I mean, what's the point of doing all this setup that needs to be done with Lua when we have a perfectly working compiler and development environment sitting at our fingertips? =)

Ok, so first things first. When we are creating scripts that are going to be run, we need to have some time of entry point into the script; that is, we need to know how to execute the script. One simple way is to create and interface or a base class that defines the available methods that we can actually call on our script.

Continuing with the example of a text-based adventure, let's figure out how we might allow for developers to create commands the user can type to interact with the game. All we need to do this will be the name of the command and an entry point into the script.

    1:  public abstract class Command {
    2:      private string name;
    3:   
    4:      public string Name {
    5:          get { return this.name; }
    6:          set { this.name = value; }
    7:      }
    8:   
    9:      public virtual void Execute(Game game, string arguments) {
   10:      }
   11:  }

Now we have the base command in place, next we need a script that actually does something interesting. With this, you're just going to have to bear with me as I won't be posting the details of the Game class, but just assume the properties on it exist. =) Besides, this is more of a proof-of-concept.

    1:  public class MoveCommand : Command {
    2:      public override void Execute(Game game, string direction) {
    3:          direction = direction.ToLower();
    4:   
    5:          // Determine if the player can move the way they wanted to.
    6:          Area currentArea = game.Map[game.Player.Position];
    7:          if (currentArea.HasExit(direction)) {
    8:              if (direction == "north") game.Player.Move(0, 1);
    9:              else if (direction == "east") game.Player.Move(1, 0);
   10:              else if (direction == "south") game.Player.Move(0, -1);
   11:              else if (direction == "west") game.Player.Move(-1, 0);
   12:   
   13:              currentArea = game.Map[game.Player.Position];
   14:              game.Describe(currentArea);
   15:          }
   16:          else {
   17:              TextBuffer.AddTextLine("Invalid direction to move: " + direction);
   18:          }
   19:      }
   20:  }

It should be fairly obvious what this is script is supposed to do, after all it is called MoveCommand. ;) Basically we are just going to move the player in one of the given directions with the Area that we are in has an exit/door in that direction.

We know have two-thirds of the puzzle. The next question to answer is, "How the heck do I get that into my game?". The answer is much simpler than you might think:

    1:  // You need to define these somehow, maybe from a backing XML file. ;)
    2:  string scriptPath;
    3:  string scriptFileName;
    4:  string scriptTypeName;
    5:  string commandName;
    6:   
    7:  // Create the actual type in memory so it can be used.
    8:  CodeDomProvider provider = new CSharpCodeProvider();
    9:  CompilerParameters compilerParams = new CompilerParameters();
   10:  compilerParams.CompilerOptions = "/target:library";
   11:  compilerParams.GenerateExecutable = false;
   12:  compilerParams.GenerateInMemory = true;
   13:  compilerParams.IncludeDebugInformation = false;
   14:   
   15:  compilerParams.ReferencedAssemblies.Add("mscorlib.dll");
   16:  compilerParams.ReferencedAssemblies.Add("YourGame.exe");
   17:   
   18:  CompilerResults result = provider.CompileAssemblyFromFile(compilerParams, Path.Combine(scriptPath, scriptFileName));
   19:  if (!result.Errors.HasErrors) {
   20:      Type type = result.CompiledAssembly.GetType(scriptTypeName);
   21:      Command cmd = Activator.CreateInstance(type) as Command;
   22:      if (cmd != null) {
   23:          cmd.Name = commandName;
   24:          this.availableCommands.Add(cmd.Name.ToLower(), cmd);
   25:      }
   26:  }

Simple huh? Heh, well... if you know what all those lines of code do then it is. Basically all we are doing is having the CodeDom compile that piece of code for us. This results in an assembly which we can then use reflection to retrieve an instance of the MoveCommand from this newly compiled assembly.

You might be wondering where you put this in your game code. Well, I put in my Initialize method which is one of the first methods that gets called. I actually wrap it in a LoadCommands method which goes through and compiles all of my game scripts, which are of course, defined using an XML file. =)

This approach works fine and dandy for script files that are independent of one another, but what if we want scripts to be able to use methods defines in other scripts? Well, for that we'll need to compile all of the scripts into a single assembly. I'll cover how to do that in my next post.