Welcome to MSDN Blogs Sign in | Join | Help

David's Blog

So you want to learn something new?
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!

Posted: Wednesday, April 18, 2007 12:02 AM by dowens
Filed under:

Comments

Max Battcher said:

Here's a quick tip for setting up that Scripts Class Library...  Visual Studio does not provide a decent UI for this, but MSBuild supports wild-cards.  So, create your Project in Visual Studio and then open up the project's .csproj file in the text editor of choice.  Then you can edit in your choice of Compile tags like:

<Compile Include="*.cs" />

This way you don't need to worry about adding each and every script into the process and all of the latest files will be included on build.  

(Quick Caveats: Visual Studio won't refresh it's Project Explorer all that often, and I would suggest not using Visual Studio to Add/Remove any further items after adding in your wild card.)

# April 18, 2007 10:19 PM

Walter Stiers - Academic Relations Team (BeLux) said:

David's Blog has some recent entries I would like to refer: Using C# for game scripts, part 2 Using C#

# April 20, 2007 8:22 AM

zygote said:

Very nice work indeed. I have written a scripting engine in c# myself for XNA. I have yet to write an article on it however :)

Ziggy

Ziggyware.com XNA News and Tutorials

# April 23, 2007 7:21 PM

kamil said:

thank you very much

# April 27, 2007 5:42 PM

Bubu said:

How are such scripts debugged???

# May 12, 2007 12:52 PM

Digini said:

I just wanted to point out that if you use this technique as it stands you should be careful not to change the scripts too often while the program is running.

There is currently no way in the CLR to unload an Assembly from an AppDomain after it has been loaded.  You have to throw away the whole domain and start over.  So, this is why the technique David talks about here is cool as long as the scripts are loaded at startup and never change.  If you anticipate the need to change scripts a lot during runtime you need to load these dynamic script DLLs in a seperate AppDomain so you can unload it.

If you do use a seperate AppDomain you should be aware that there is some overhead associated with cross domain method calls since the calls actually go through a proxy-stub to ensure the preservation of the security context of the domain.

# May 14, 2007 1:30 AM

Geron said:

Is there any way to restrict a compiled script from using file I/O etc (or certain namespaces). The compiled assembly will run under the same credentials as the host application I believe. So I want some way to restrict a user from writing "malicious" scripts. Simply scanning the script file and removing all occurrences of "#using System.IO;" etc doesn't feel secure enough. A user might even use ImportDLL-declarations to access unmanaged code!

# June 18, 2007 10:44 AM

dowens said:

Geron, this is actually one of the biggest drawbacks to this approach; you are not allowed to limit the namespaces that are available. You could probably run the scripts in a lesser access than the app that is running, but I don't know of a way to actually limit the libraries that can be used in a full-proof manner.

# June 18, 2007 2:00 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

  
Enter Code Here: Required

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Page view tracker