Using C# for game scripts, part 2

In my last post I showed you how to get scripts to be compiled dynamically at run-time. This makes things super easy, but I left you guys hanging on two fronts.

  1. I didn't show you how to compile multiple scripts into the same assembly.
  2. I totally spaced on the fact that this won't work when using the compact framework (i.e. on the Xbox 36).

Since #1 is incredibly easy, I'll combine both of these answers into this post.

Alright... compiling multiple scripts into the same assembly. When I first was looking at this my eye balls must have been blury form all the time staring at the monitor or something because I totally didn't see what the parameter arguments are for the CompileAssemblyFromFile method. As it turns out, the second parameter is 'params string[] files'. For some strange reason I thought it only took a single file. So all we need to do to get our scripts into the same assembly would be something like this:

    1:  // You need to define these somehow, maybe from a backing XML file. ;)
    2:  string scriptPath;
    3:  string[] scriptFileNames;
    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, scriptFileNames);
   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:  }

You should notice that are only two real differences with this code and the code from part 1:

  1. scriptFileName is now an array and is called scriptFileNames.
  2. scriptFileName now contains the path information as well as the name of the script.

Ok, with the easy stuff out of the way, let's talk about pre-compiling our scripts. When we precompile our scripts we get some benefits. One of the most obvious and most beneficial is that we get useful compile errors. These compile errors greatly help with debugging stupid mistakes in our script code. Another wonderful benefit is that we reduce the amount of time the game takes to initialize as we don't have to compile every script every time we run the game. This might not seem like a lot now, but if you have hundreds of scripts, the time can add up. The last benefit we'll address is that fact that it will work everywhere; that is, it will work on both a Windows PC and the Xbox 360.

So how do we achieve this magic? Well, the answer is actually MUCH simplier than our solution for yesterday, at least in the terms of code that we need to add in order to support it. The "difficult" part is that you need to setup your own class library project in Visual Studio (or Visual C# Express/XNA Game Studio Express). Then you'll need to rename the DLL to something that our game will load, unless of course you provide a means to customize how the script DLLs are loaded. After that, simply add the commands to your project and add a reference to your game DLL and you should be ready to go (you need the reference so you can find the Command class that your own commands will be deriving from).

Assuming that you were able to get a class library built of your commands (I know, I kind of glossed over that so ask questions if something wasn't clear or you couldn't figure something out), you now need to load that DLL in your game so that we can create the instances of the commands that we need.

Here it is:

    1:  // You need to define these somehow, maybe from a backing XML file. ;)
    2:  string dllPath;
    3:  string[] scriptTypeNames;
    4:  string[] commandNames;
    5:   
    6:  Assembly scriptDll = Assembly.LoadFrom(dllPath); 
    7:   
    8:  for (int i = 0; i < scriptTypeNames.Length; i++) {
    9:      Type type = scriptDll.GetType(scriptTypeNames[i]);
   10:      Command cmd = Activator.CreateInstance(type) as Command;
   11:      if (cmd != null) {
   12:          cmd.Name = commandNames[i];
   13:          this.availableCommands.Add(cmd.Name.ToLower(), cmd);
   14:      }
   15:  }

That's it... you now know one possible way of adding both non-compiled and pre-compiled scripts, and therefore a cross-platform (Windows and Xbox 360) method of adding scripts to your game.

Good luck!