ゲーム用スクリプト

ゲーム作成ツールでは独自のスクリプト言語を用意しているものが多いです。GUIで部品を切り貼りするだけではかえって手間が掛かる部分もありますし。しかし、簡単なスクリプトなら兎も角、クラスが作れるくらい本格的なモノを作成しようとすると、結構な労力を使うことになりそうです。

独自のスクリプトを用意する動機としては、

  1. ユーザの費用がかからない
  2. ユーザの手間が掛からない
  3. 覚えやすい(簡易スクリプトなら)
  4. 安全(スクリプトの範囲なら一般保護エラーが出ない)
  5. 制作者の趣味(実行環境&言語作成は憧れるよね〜)

私は実際に作っている人じゃないので想像ですが、こんな感じでしょうか。
この中で1〜4を考えてみます。1.はフリーなコンパイラが沢山あります。2.はユーザにコンパイル環境を作成させるのは難しいので今でも動機になります。3.は機能を絞り込んだりユーザが比較的見慣れているHTMLなどに似せているものなどは、その通りですが、クラスまで使えるくらい本格的になるとそれなりに覚えるのは大変そう。4.はC/C++なら兎も角JavaVBなら危険は無いと考えて良いと思います。
ということは、JavaVBのような(比較的)安全な言語をユーザの手を煩わせることなく使えるように出来れば良いことになります。

以上、長い前振りでした(^^;

で、本題。.NETの開発者はSDKをインストールし、ユーザはラインタイムをインストールします。なので開発するにはSDKが必要になると考えがちですが、実はC#コンパイラもライブラリの一部なのでランタイムでもインストールされるのです。更にライブラリなのでプログラムの中から実行中にコンパイラを呼ぶ出すことも可能なのです。ユーザにはC#で書かれたひな形を提供し、それをいじって貰います。で、ゲーム実行時にメモリ上でそのソースファイルをコンパイルして実行すれば手間を掛けずにC#と互換性100%のスクリプトを提供出来ることになるのです!(詐欺だ・・・)

実際にどうやってメモリ上でコンパイルするかですが、その昔、@ITに投稿したサンプルを使い回して紹介(^^;

using System;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;

public class ArrayTemplate {
  // テンプレートコード(T_を置換してメモリ上でコンパイルする)
  private const string code = @"
    using System;
    public class T_Array {
      private T_ buffer;
      private T_ sum_x;
      private int number;

      public T_Array() {
        buffer = new T_[1000];
        number = 0;
        sum_x = 0; 
      } 
      public void Add(T_ x) {
        buffer[number++] = x;
        sum_x += x; 
      } 
      public int Number() { 
        return number; 
      } 
      public double Average() { 
        return (double)sum_x/number; 
      } 
    }";

  public static object Create(Type type) {
    // ソースコードのT_を型に置換して、指定した型専用のArrayクラスにする
    string src = code.Replace("T_", type.Name);

    // C#コンパイラを作成
    ICodeCompiler cc = new CSharpCodeProvider().CreateCompiler();
    CompilerParameters cp = new CompilerParameters();
    // アセンブリの参照を設定
    cp.ReferencedAssemblies.AddRange(new string {"mscorlib.dll","System.dll"});
    // メモリ上でコンパイル
    cp.GenerateInMemory = true;
    CompilerResults cr = cc.CompileAssemblyFromSource(cp, src);
    if(cr.Errors.HasErrors) {
      return null;
    }
    // インスタンスを返す
    return cr.CompiledAssembly.CreateInstance(type.Name + "Array");
  }

}

class Wrapper {
  private object _o;

  public Wrapper(object o) {
    _o = o;
  }

  public object Invoke(string name) {
    return this.Invoke(name, null);
  }

  public object Invoke(string name, params object args) {
    MethodInfo mi = _o.GetType().GetMethod(name);
    if (mi == null)
      return null;
    return mi.Invoke(_o, args);
  }
}

class MyApp {

  static void ArrayTest(object arr, params object args) {
    Wrapper wrap = new Wrapper(arr);

    foreach(object o in args) 
      wrap.Invoke("Add", o);
            
    Console.WriteLine("Number  : " + wrap.Invoke("Number"));
    Console.WriteLine("Average : " + wrap.Invoke("Average"));
  }

  static void Main(string[] args) {
    // int型のArrayクラスを生成
    object iArr = ArrayTemplate.Create(typeof(int));
    // double型のArrayクラスを生成
    object dArr = ArrayTemplate.Create(typeof(double));

    // テスト
    Console.WriteLine(iArr.GetType().Name);
    ArrayTest(iArr, 1,1,2,3,5);
    Console.WriteLine(dArr.GetType().Name);
    ArrayTest(dArr, 1.1,2.2,3.3,4.4);
  }
}