目次

Moonsharpとは

一言でいえば、レベルデザイン必要なゲームを開発するなら、入れておきなさいツール。

わかりやすくいうと、これを入れない設計でゲームをつくると、「ちょっ変更するだけなのにビルド時間こんなにかかるの?!」という事態になってしまう。

UnityでLuaを使えるようにするプラグインである。 Asset Storeを開き、

MoonSharp

と検索すると出てきます。

https://assetstore.unity.com/packages/tools/moonsharp-33776?locale=ja-JP

MoonSharp?は、.NET、Mono、Xamarin、Unity3Dプラットフォーム向けに完全にC#で書かれた完全なLuaソリューションです。

特徴

MoonSharp?と標準Luaの違い

http://www.moonsharp.org/moonluadifferences.html を参照してください。

ダウンロード、情報、チュートリアルなどについては、http://www.moonsharp.org をご覧ください。

ライセンス

プログラムとライブラリは、3条項BSDライセンスの下でリリースされています - ライセンスセクションを参照してください。

文字列ライブラリの一部は、KopiLua?プロジェクト(https://github.com/NLua/KopiLua)に基づいています。デバッガーのアイコンは、Eclipseプロジェクト(https://www.eclipse.org/)からのものです。

使用方法

ライブラリの使用は次のように簡単です:

double MoonSharpFactorial()
{
    string script = @"    
        -- 階乗関数を定義
        function fact (n)
            if (n == 0) then
                return 1
            else
                return n*fact(n - 1)
            end
        end

    return fact(5)";

    DynValue res = Script.RunString(script);
    return res.Number;
}

より詳細なチュートリアル、サンプルなどについては、http://www.moonsharp.org/getting_started.html を参照してください。

公式サイト

https://www.moonsharp.org/

Github

https://github.com/moonsharp-devs/moonsharp

チュートリアル

本家のチュートリアルを翻訳しました。

元記事

https://www.moonsharp.org/getting_started.html

チュートリアルをはじめるにあたって

このチュートリアルでは、UnityでMoonSharp?の簡単さと強力さを感じていただけるでしょう。MoonSharp?を使用するには他にも良い方法がありますが、これが最も簡単な方法です。他のチュートリアルではさらに掘り下げて、UnityでMoonSharp?を最大限に活用する方法を学ぶことができます。

とりあえず、始めましょう。

このチュートリアルでは、LuaとC#を知っていることを前提としています。Luaの基礎を知らず、C#について十分な知識がないと、サンプルを理解するのは難しいでしょう。

ステップ1:UnityでMoonSharp?を入手する

最初のステップは、UnityでMoonSharp?を入手することです。

Unity Asset Storeのパッケージは現在承認待ちで、承認され次第このURLから入手できるようになります。承認されたら、これを使用するのがUnityでMoonSharp?をインストールするのに推奨される方法になります。

MoonSharp?配布物のinterpreter/net35フォルダに含まれているMoonSharp?.Interpreter.dllをAssets/Pluginsフォルダに入れます。

Windows StoreアプリやWindows Phoneのサポートが必要な場合は、MoonSharp?配布物のinterpreter/portable-net40フォルダにあるMoonSharp?.Interpreter.dllをAssets/Plugins/WSAフォルダにコピーします。

次に、このガイドに従ってください。 その後、Unity EditorからMoonSharp?.Interpreter.dllを参照として追加します。

注:IL2CPPプロジェクトでMoonSharp?を使用する場合は、以下の内容でAssetsディレクトリ内にlink.xmlを作成または編集してください。

<linker>
    <assembly fullname="MoonSharp.Interpreter">
        <type fullname="MoonSharp.*" preserve="all" />
    </assembly>
</linker> 

ステップ2:名前空間のインポート

コードの先頭に以下の行を追加して、MoonSharp?.Interpreter名前空間をコードにインポートします。

using MoonSharp.Interpreter;

ステップ3:スクリプトの呼び出し

ここでは、MoonSharp?を使用して階乗を計算するMoonSharpFactorial?関数を作成します。

double MoonSharpFactorial()
{
  string script = @"
  -- 階乗関数を定義
  function fact (n)
    if (n == 0) then
      return 1
    else
      return n*fact(n - 1)
    end
  end
  Copy code	return fact(5)";
 
  DynValue res = Script.RunString(script);
  return res.Number;
}

ステップ4:完了!

もちろん、コードを実行するには、Unity C#スクリプトの他の場所でMoonSharpFactorial?関数を呼び出すだけです。

これで、UnityでMoonSharp?を使うための準備が整いました。他のチュートリアルに進んで、UnityでMoonSharp?を最大限に活用する方法を学びましょう。

スクリプトを保持する

前回のチュートリアルでは、MoonSharp?に初めて触れました。スクリプトを文字列に入れ、コードを実行し、その出力を取得しました。これは時には便利かもしれませんが、ほとんどのユースケースに必要な相互運用性には、CLRコードとMoonSharp?の間でもう少し統合が必要です。

ステップ1: 前回のチュートリアルで行ったことをすべてやり直す

真面目な話、ここではもう少し高度な概念を学んでいますが、最初の試みで捨てるべきものはほとんどありません。それは素晴らしい出発点です。

ステップ2: コードを変更してScriptオブジェクトを作成する

最初の変更点は、静的メソッドを使用する代わりにScriptオブジェクトを作成することです。ロケット科学ではありませんが、次の進化に向けて準備をしています。

double MoonSharpFactorial()
{
  string scriptCode = @"    
    -- 階乗関数を定義
    function fact (n)
      if (n == 0) then
        return 1
      else
        return n*fact(n - 1)
      end
    end

    return fact(5)";

  Script script = new Script();
  DynValue res = script.DoString(scriptCode);
  return res.Number;
}

ステップ2: グローバル環境にアクセスする

スクリプトオブジェクトを持っているので、関数が動作するグローバル環境を変更できます。

例えば、fact関数への入力を変更して、プログラムがどの数の階乗を計算するかを指定できるようにしたいかもしれません。

double MoonSharpFactorial()
{
  string scriptCode = @"    
    -- 階乗関数を定義
    function fact (n)
      if (n == 0) then
        return 1
      else
        return n*fact(n - 1)
      end
    end

    return fact(mynumber)";

  Script script = new Script();

  script.Globals["mynumber"] = 7;

  DynValue res = script.DoString(scriptCode);
  return res.Number;
}

ご覧の通り、script.Globalsテーブルを単純な構文で参照するだけで、数値をMoonSharp?スクリプトに注入できます。実際には数値以上のものを渡すことができますが、今のところは数値、ブール値、文字列に限定しましょう。関数やオブジェクトを渡す方法は後で見ていきます。

ステップ2: 関数を直接呼び出す

外部から選択した数値の階乗をMoonSharp?で計算する方法を学びました。

でも、そのようにやるのは汚いハックのように見えます(それでも、これからよく使う重要なテクニックです)。

以下は、C#からLua関数を呼び出す方法です。

double MoonSharpFactorial2()
{
  string scriptCode = @"    
    -- 階乗関数を定義
    function fact (n)
      if (n == 0) then
        return 1
      else
        return n*fact(n - 1)
      end
    end";

  Script script = new Script();

  script.DoString(scriptCode);

  DynValue res = script.Call(script.Globals["fact"], 4);

  return res.Number;
}

何をしたのか見てみましょう。

まず、スクリプトの最後のreturnを削除しました。

残しておくこともできましたが、任意のパラメータでfactを呼び出したいので、無駄になるはずでした。

この時点で、

script.DoString(...)

の呼び出しはファイルを実行し、グローバル環境にfact関数を残します。

そして、この行を導入しました。

DynValue res = script.Call(script.Globals["fact"], 4);

これは、スクリプトのグローバルからfact関数を取得し、4をパラメータとして呼び出します。

もっと良い(つまり、パフォーマンスの良い)方法がありますが、時間に敏感なループで多くの呼び出しを行わない場合は、これが最も簡単な方法ですが、少し型が安全ではありません。

factの関数は何度でも呼び出せることに注意してください。Scriptは状態を保持し、何度でも実行できる状態になっています。

DynValue?のすべて

- すべてはDynValueであり、DynValueである

DynValue?の概念はMoonSharp?の根幹にあり、これまではあまり触れずに済んでいましたが、実際にはこの話題に触れずに先へ進むのは難しいでしょう。

タイトルにもあるように、MoonSharp?の(ほぼ)すべてがDynValue?オブジェクトのインスタンスです。

DynValue?は、スクリプト内の値を表し、その型に関わらず、テーブル、関数、数値、文字列などすべてを表現できます。

では、まず前回のチュートリアルの最後のステップから始めて、代わりにDynValue?(s)を使用するように変更してみましょう。

ステップ1: 前回のチュートリアルで行ったことをすべてやり直す

再び、前回の最後のステップから始めます。やったよね? リファレンスドキュメントはここで入手できます。

ステップ2: fact関数をDynValue?で取得する

最初の変更点として、fact変数を別の方法で取得します。

double MoonSharpFactorial()
{
  string scriptCode = @"    
    -- 階乗関数を定義
    function fact (n)
      if (n == 0) then
        return 1
      else
        return n*fact(n - 1)
      end
    end";

  Script script = new Script();

  script.DoString(scriptCode);

  DynValue luaFactFunction = script.Globals.Get("fact");

  DynValue res = script.Call(luaFactFunction, 4);

  return res.Number;
}

この行について少し説明しましょう。

DynValue luaFactFunction = script.Globals.Get("fact");

これは、スクリプトのグローバルからfact関数を取得します。

Getメソッドは、インデクサープロパティとは反対に、DynValue?を返します。

インデクサーは、代わりにSystem.Objectに変換しようとします。

この場合、DynValue?に関心があるので、Getメソッドを使用します。

ステップ3: 数値をDynValue?で取得する

さて、もう1つの変換を避けることにも関心があるなら、Callメソッドが親切に提供する暗黙の変換を使用する代わりに、直接数値を渡すこともできます。

ここでは必要ありませんが、他の場所では必要になるかもしれません。すべてがあなたのために魔法のような変換をしてくれるわけではありません!

double MoonSharpFactorial2()
{
  string scriptCode = @"    
    -- 階乗関数を定義
    function fact (n)
      if (n == 0) then
        return 1
      else
        return n*fact(n - 1)
      end
    end";

  Script script = new Script();

  script.DoString(scriptCode);

  DynValue luaFactFunction = script.Globals.Get("fact");

  DynValue res = script.Call(luaFactFunction, DynValue.NewNumber(4));

  return res.Number;
}

つまり、数値を以下のように置き換えました。

DynValue.NewNumber(4)

DynValue?には、"New"で始まる多くのファクトリメソッドがあります(NewString?NewNumber?NewBoolean?など)。

これらを使用して、最初から値を作成できます。

また、便利なFromObject?メソッドもあります。これはオブジェクトからDynValue?を作成するもので、Callが内部で使用していたものです。

ステップ4: DataType?の理解

DynValue?で最も重要なプロパティの1つはTypeです。

Typeプロパティは列挙型で、DynValue?にどのような種類のデータが含まれているかを示します。

DynValue?に何が含まれているかを知りたい場合は、いつでもTypeプロパティを照会できます。

// 新しい数値を作成
DynValue v1 = DynValue.NewNumber(1);
// 新しい文字列を作成
DynValue v2 = DynValue.NewString("ciao");
// 自動変換を使用して別の文字列を作成
DynValue v3 = DynValue.FromObject(new Script(), "hello");

// Number - String - String と表示される
Console.WriteLine("{0} - {1} - {2}", v1.Type, v2.Type, v3.Type);
// Number - String - 0になると期待すべきではないゴミの数値が表示される
Console.WriteLine("{0} - {1} - {2}", v1.Number, v2.String, v3.Number);

DynValue?の一部のプロパティは、値が特定の型の場合にのみ意味のある値を持つことに注意してください。

例えば、NumberプロパティはDataType?.Numberの場合にのみ意味のある値を持つことが保証されており、Stringプロパティも同様です。

DynValue?の中身が確実にわかっていない限り、そのプロパティを使用する前に必ずDynValue?のTypeを確認するようにしてください。

DynValue?が特定の型を含んでいると想定しているのに、別の型を含んでいると、間違いやエラーの一般的な原因になります。

ステップ5: タプル

ご存知のように(あるいは知っておくべきですが)、Luaでは関数から複数の値を返すことができます(その他の状況でも)。

これを処理するために、特別なDynValue?型(DataType?.Tuple)が使用されます。タプルにはTupleプロパティがあり、これはDynValue?オブジェクトの配列で、タプルのメンバーです。

これは聞こえるよりも簡単です。

DynValue ret = Script.RunString("return true, 'ciao', 2*3");

// "Tuple" と表示される
Console.WriteLine("{0}", ret.Type);

// 以下が表示される:
//   Boolean = true
//   String = "ciao"
//   Number = 6
for (int i = 0; i < ret.Tuple.Length; i++)
  Console.WriteLine("{0} = {1}", ret.Tuple[i].Type, ret.Tuple[i]); 

終わりに DynValues?について終わりです。

まだまだ学ぶべきことはたくさんありますが、このトピックを始めるにはこれで十分でしょう。

すべてがこれを中心に回っているので、これを肝に銘じておいてください。

C#をコールバックする

- ..そしてF#、VB.NET、C++/CLI、Booなども

スクリプトは、アプリケーション自体に実装された構成要素から始まるビジネスロジックをカスタマイズできるため、アプリケーションで役立ちます。

ビジネスアプリケーション、ビデオゲーム、またはある種のツールにスクリプトを組み込む場合、最初の関心事はスクリプトとアプリケーションの相互運用性と、オブジェクトの(ある意味での)共有です。

そして、手続き型プログラミングの基本的な構成要素は関数です。

演習1、ステップ1: 階乗に飽きるな

これまで使ってきた標準的な階乗スクリプトで基礎を押さえましょう。

private static double CallbackTest()
{
    string scriptCode = @"    
        -- 階乗関数を定義
        function fact (n)
            if (n == 0) then
                return 1
            else
                return n * fact(n - 1);
            end
        end";

    Script script = new Script(); 

    script.DoString(scriptCode);

    DynValue res = script.Call(script.Globals["fact"], 4);

    return res.Number;
}

演習1、ステップ2: 乗算関数をカスタマイズする

さて、乗算をホストアプリケーションのAPI関数で実装したいとしましょう。

この場合、そうする目的はありませんが、学ぶためにここにいるので、それが良いアイデアだと仮定しましょう。

private static int Mul(int a, int b)
{
    return a * b;
}

private static double CallbackTest()
{
    string scriptCode = @"    
        -- 階乗関数を定義
        function fact (n)
            if (n == 0) then
                return 1
            else
                return Mul(n, fact(n - 1));
            end
        end";

    Script script = new Script();

    script.Globals["Mul"] = (Func<int, int, int>)Mul;

    script.DoString(scriptCode);

    DynValue res = script.Call(script.Globals["fact"], 4);

    return res.Number;
}

それだけです!

script.Globals["Mul"] = (Func<int, int, int>)Mul;

これにより、グローバル環境のMul変数が、アプリケーションのMulデリゲートを指すように設定されます。

ここでは、C#コンパイラを満足させるために、デリゲートをFunc<int, int, int>型にキャストしています。

デリゲートをSystem.Objectにキャストするために使用する方法は何でも構いません。

また、メソッドをstaticに定義しましたが、この場合、それらがstaticである必要は全くありません。

インスタンスメソッドも問題ありません。

演習2: 数値のシーケンスを返す

問題: 整数のシーケンスを返すAPI関数を用意する。スクリプトは受け取った数値を合計して、その合計値を返す。

private static IEnumerable<int> GetNumbers()
{
    for (int i = 1; i <= 10; i++)
        yield return i;
}

private static double EnumerableTest()
{
    string scriptCode = @"    
        total = 0;
        
        for i in getNumbers() do
            total = total + i;
        end

        return total;
    ";

    Script script = new Script();

    script.Globals["getNumbers"] = (Func<IEnumerable<int>>)GetNumbers; 

    DynValue res = script.DoString(scriptCode);

    return res.Number;
}

ここでも、特に難しいことはありません。

IEnumerable(またはIEnumerator)が、スクリプトを実行するためにLuaイテレータに自動的に変換される様子がわかります。

スクリプトには直接実行可能なコードが含まれているため、Call .. callを必要とせずに、DoString?時にgetNumbersメソッドにアクセスしていることにも注目してください。

これを覚えておいてください。

DoString?DoFile?は、スクリプトに含まれるコードを即座に実行します!

注意すべき点が1つあります。MoonSharp?は、System.Collections.IEnumerableとSystem.Collections.IEnumeratorの型のイテレータを変換できます。

つまり、ジェネリックではないバリアントです。

何らかの理由でジェネリックイテレータを実装する際にジェネリック以外のイテレータを実装しない場合、そのイテレータは機能しません。

上記のように、すべての標準コレクション型とイテレータメソッドは、デフォルトでジェネリック以外のバリアントを実装しているので、あまり心配する必要はありません。

テーブルを返す

問題: 今度は整数のシーケンスをテーブルで返すAPI関数を用意する。

private static List<int> GetNumberList()
{
    List<int> lst = new List<int>();
    
    for (int i = 1; i <= 10; i++)
        lst.Add(i); 

    return lst;
}

private static double TableTest1()
{
    string scriptCode = @"    
        total = 0; 

        tbl = getNumbers()
        
        for _, i in ipairs(tbl) do
            total = total + i;
        end

        return total;
    ";

    Script script = new Script(); 

    script.Globals["getNumbers"] = (Func<List<int>>)GetNumberList;

    DynValue res = script.DoString(scriptCode);

    return res.Number;
}

ここでは、List<int>がLuaテーブルに自動的に変換される様子がわかります!

結果のテーブルは、Luaテーブルが通常そうであるように、1から始まるインデックスを持つことに注意してください。

ただし、もっと良い方法があります。

関数内で直接Luaテーブルを構築することができます。

private static Table GetNumberTable(Script script)
{
    Table tbl = new Table(script);

    for (int i = 1; i <= 10; i++)
        tbl[i] = i; 

    return tbl;
}

private static double TableTest2()
{
    string scriptCode = @"    
        total = 0;

        tbl = getNumbers()
        
        for _, i in ipairs(tbl) do
            total = total + i;
        end

        return total;
    ";

    Script script = new Script(); 

    script.Globals["getNumbers"] = (Func<Script, Table>)(GetNumberTable);

    DynValue res = script.DoString(scriptCode);

    return res.Number;
}

Tableオブジェクトを使用してテーブルを操作するのがいかに簡単かがわかります。

ただし、注意すべき点が2つあります。

新しいTableオブジェクトを作成するには、実行中のスクリプトへの参照が必要です。

CLR関数にScriptパラメータがあり、それがLuaスクリプトで利用可能な場合、MoonSharp?はそれを自動的に設定します。

これは、(このように使用される可能性は低いですが)ScriptExecutionContext?CallbackArguments?の型でも発生します。

これらが何をするのかわからなくても心配しないでください。

MoonSharp?の基本を理解するのに必要ありません!

良い習慣として、可能な限りScriptオブジェクトを保持するようにしてください。

(テーブルの作成など)Scriptオブジェクトを使用してのみ実行できることがいくつかあります。

テーブルを受け入れる

テーブルは自動的にList<T>に変換されます。

例えば、

public static double TableTestReverse()
{
	string scriptCode = @"    
		return dosum { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
	";

	Script script = new Script();

	script.Globals["dosum"] = (Func<List<int>, int>)(l => l.Sum());

	DynValue res = script.DoString(scriptCode);

	return res.Number;
} 

ただし、これによって一部のプラットフォーム(具体的にはiOS)で問題が発生する可能性があります。

この問題を回避する方法はたくさんありますが(他のチュートリアルで見ることができます)、次の方法では問題なく機能し、さらに高速であると言えます。

public static double TableTestReverseSafer()
{
	string scriptCode = @"     
		return dosum { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
	";

	Script script = new Script();

	script.Globals["dosum"] = (Func<List<object>, int>)(l => l.OfType<int>().Sum());

	DynValue res = script.DoString(scriptCode);

	return res.Number;
}

さらに高速な別の方法は、Tableオブジェクトを使用することです。

static double Sum(Table t)
{
    var nums = from v in t.Values
               where v.Type == DataType.Number
               select v.Number;

    return nums.Sum();
}


private static double TableTestReverseWithTable()
{
    string scriptCode = @"    
        return dosum { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
    ";

    Script script = new Script();

    script.Globals["dosum"] = (Func<Table, double>)Sum;

    DynValue res = script.DoString(scriptCode);

    return res.Number;
}

しかし、ここではDynValue?(s)を扱う必要があります。

これらすべてを理解するには、MoonSharp?がLuaの型をC#の型にどのようにマッピングするか、その逆はどうかについて、もう少し深く掘り下げる必要があります。

次の部分で説明します。

自動変換の説明

- ..今回はコードなし

MoonSharp?とCLRの統合についてさらに掘り下げる前に、型がどのように相互にマッピングされるかを明確にする必要があります。

残念ながら、逆方向は順方向とは大きく異なるので、2つを別々に分析します。

これは少し複雑すぎませんか? - あなたはそう尋ねるかもしれません。

確かにそうです。

自動的なものは良いですが、失敗すると、ひどく複雑な方法で失敗します。疑問がある場合や、物事が複雑すぎる場合は、物事を単純化する必要があります。

2つの方法があります:DynValue?をそのまま使用するか、カスタムコンバーターを使用するかです。

これらの解決策は、正気と単純さを得るだけでなく、自動変換よりもかなり高速です!

カスタムコンバーター

変換プロセスをカスタマイズすることは可能ですが、設定はグローバルで、すべてのスクリプトに影響します。

変換をカスタマイズするには、

Script.GlobalOptions.CustomConverters

の適切なコールバックを設定するだけです。

例えば、CLRからスクリプトへの変換が行われるときに、すべての

StringBuilder

オブジェクトを大文字の文字列に変換したい場合は、次のようにします。

Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion<StringBuilder>(
	v => DynValue.NewString(v.ToString().ToUpper()));

テーブルがIList<int>に一致する必要がある場合に、テーブルの変換方法をカスタマイズしたい場合は、次のように記述できます。

Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Table, typeof(IList<int>),
	v => new List<int>() { ... });

コンバーターがnullを返した場合、システムはカスタムコンバーターが存在しないかのように動作し、自動変換を試みます。

CLR型からMoonSharp?型への自動変換

この変換は、次の状況で適用されます。

この変換は実際にはかなり簡単です。次の表は、変換を説明しています。

CLR型C#のわかりやすい名前Lua型備考
-------------------------------------------------------------------------------------------------------------------------------------
void(no value)これはメソッドの戻り値にのみ適用できます。
nullnilnullはすべてnilに変換されます。
MoonSharp?.Interpreter.DynValue?*DynValue?はそのまま渡されます。
System.SBytesbytenumber
System.Bytebytenumber
System.Int16shortnumber
System.UInt16ushortnumber
System.Int32intnumber
System.UInt32uintnumber
System.Int64longnumber変換により、暗黙的に精度が失われる可能性があります。
System.UInt64ulongnumber変換により、暗黙的に精度が失われる可能性があります。
System.Singlefloatnumber
System.Decimaldecimalnumber変換により、暗黙的に精度が失われる可能性があります。
System.Doubledoublenumber
System.Booleanboolboolean
System.Stringstringstring
System.Text.StringBuilder?string
System.Charcharstring
MoonSharp?.Interpreter.Tabletable
MoonSharp?.Interpreter.CallbackFunction?function
System.Delegatefunction
System.Objectobjectuserdata型がuserdataに登録されている場合のみ。
System.Typeuserdata型がuserdataに登録されている場合のみ。静的メンバーアクセス。
MoonSharp?.Interpreter.Closurefunction
System.Reflection.MethodInfo?function
System.Collections.IListtable結果のテーブルは1からインデックスが付けられます。すべての値はこれらのルールを使用して変換されます。
System.Collections.IDictionarytableすべてのキーと値はこれらのルールを使用して変換されます。
System.Collections.IEnumerableiteratorすべての値はこれらのルールを使用して変換されます。
System.Collections.IEnumeratoriteratorすべての値はこれらのルールを使用して変換されます。

これには、コレクションのかなり良いカバレッジが含まれています。ほとんどのコレクションは、IList、IDictionary、IEnumerable、IEnumeratorのいずれかを実装しているためです。

独自のコレクションを書く場合は、これらの非ジェネリックインターフェイスの1つを実装することを忘れないでください。

このロジックを使用して変換できない値はすべて、ScriptRuntimeException?をスローします。

MoonSharp?型からCLR型への標準の自動変換

反対の変換はかなり複雑です。

実際、2つの異なる変換パスがあります。

「標準」の変換パスと、「制約付き」の変換パスです。

最初の変換パスは、実際に何を受け取りたいかを指定せずにDynValue?をオブジェクトに変換するように要求するたびに適用され、もう一方の変換パスは、一致させるSystem.Typeがある場合に適用されます。

これは以下の場合に使用されます。

ここでは、デフォルトの変換を見ていきます。実際には簡単です。

MoonSharp?CLR型備考
-----------------------------------------------------------------------------------------------------------------------------------------------
nilnullnilであるすべての値に適用されます。
booleanSystem.Boolean
numberSystem.Double
stringSystem.String
functionMoonSharp?.Interpreter.ClosureDataType?がFunctionの場合。
functionMoonSharp?.Interpreter.CallbackFunction?DataType?ClrFunction?の場合。
tableMoonSharp?.Interpreter.Table
tupleMoonSharp?.Interpreter.DynValue?[]
userdata(special)userdataに格納されているオブジェクトを返します。「静的」userdataの場合は、Typeが返されます。

このロジックを使用して変換できない値はすべて、ScriptRuntimeException?をスローします。

MoonSharp?型からCLR型への制約付き自動変換

しかし、制約付き自動変換ははるかに複雑です。この場合、MoonSharp?の値は、特定の型のCLR変数に格納する必要があり、これらの変換を行う方法はほぼ無限にあります。

これは以下の場合に使用されます。

MoonSharp?は値の変換に非常に努力しますが、特にテーブルが関与する場合は、変換には確実に限界があります。

この場合、変換は単純なマッピングのテーブルではなく、プロセスに近いので、ターゲットの型を1つずつ分析していきましょう。

特殊なケース

文字列

文字列は、System.String、System.Text.StringBuilder?、System.Charに自動的に変換できます。

ブール値

ブール値は、System.BooleanやSystem.Nullable<System.Boolean>に自動的に変換できます。また、System.String、System.Text.StringBuilder?、System.Charにも変換できます。

数値

数値は、System.SByte、System.Byte、System.Int16、System.UInt16、System.Int32、System.UInt32、System.Int64、System.UInt64、System.Single、System.Decimal、System.Doubleとそれらのnullableバージョンに自動的に変換できます。また、System.String、System.Text.StringBuilder?、System.Charにも変換できます。

関数

スクリプト関数は、MoonSharp?.Interpreter.ClosureまたはMoonSharp?.Interpreter.ScriptFunctionDelegate?に変換されます。コールバック関数は、MoonSharp?.Interpreter.ClrFunction?またはSystem.Func<ScriptExecutionContext?, CallbackArguments?, DynValue?>に変換されます。

ユーザーデータ

ユーザーデータは、「静的」でない場合にのみ変換されます(チュートリアルのuserdataセクションを参照)。変換対象のオブジェクトを割り当てることができる型の場合に変換されます。

また、オブジェクトのToString?()メソッドを呼び出すことで、System.String、System.Text.StringBuilder?、System.Charにも変換できます。

テーブル

テーブルは以下に変換できます。

ジェネリックや型付き配列への変換には、次の制限があることに注意してください。

これらの問題を補うために、開発の後半でカスタムコンバーターを追加することもできます! 一部の変換(List<int>への変換など)は、Unity/iOSなどのAOTプラットフォームで問題を引き起こす可能性があります。この種の問題を回避するには、カスタムコンバーターを登録してください。

iOSなどのAOTプラットフォームをターゲットにする場合は、値型のジェネリックには常に注意してください。参照型のジェネリックは、一般的に問題を引き起こしません。

オブジェクトの共有

注:このページに記載されている一部の機能は、masterブランチの現在の状態を反映しています(つまり、最新のリリースには存在しない機能かもしれません)。

MoonSharp?の便利な機能の1つは、.NETオブジェクトをスクリプトと共有できることです。

デフォルトでは、型はすべてのパブリックメソッド、パブリックプロパティ、パブリックイベント、パブリックフィールドをLuaスクリプトと共有します。

MoonSharpVisible?アトリビュートを使用して、このデフォルトの可視性をオーバーライドできます。

CLRコードとスクリプトコード間のインターフェイスとして専用のオブジェクトを使用することをお勧めします(アプリケーションの内部モデルをスクリプトに公開するのとは対照的です)。

多くのデザインパターン(アダプター、ファサード、プロキシなど)が、そのようなインターフェイス層の設計に役立ちます。

これは特に以下のために重要です。

これらの理由から、MoonSharp?はデフォルトで、スクリプトで使用可能な型の明示的な登録を必要とします。

スクリプトを信頼できるシナリオでは、`

UserData.RegistrationPolicy = InteropRegistrationPolicy.Automatic;

を使用して自動登録をグローバルに有効にできます。

危険なので、警告しておきます。

メニューにあるもの

では、メニューにあるものを見てみましょう。

たくさんありますね。では、始めましょう。

型記述子

まず、型記述子について説明しましょう 最初に、インターオペラビリティがどのように実装されているかについて少し理論を説明します。

すべてのCLR型は、CLR型をスクリプトに記述する役割を持つ「型記述子」にラップされます。

型をインターオペラビリティ用に登録するとは、型を記述子(MoonSharp?が自身で作成できる)に関連付けることを意味します。

この記述子は、メソッド、プロパティなどのディスパッチに使用されます。

次のセクション以降では、MoonSharp?が提供する「自動」記述子について説明しますが、速度、機能の追加、セキュリティなどのために独自の記述子を実装することもできます。

独自の記述子を実装したい場合

独自の記述子を実装したい場合(簡単ではなく、必要でない限り行うべきではありません)、次のような方法があります。

記述子の作成を助けるために、次のクラスが利用できます。

ユーザーデータとしての値型とのインターオペラビリティに関する注意事項。

値型をパラメーターとして渡す関数を呼び出す場合と同様に、スクリプトはユーザーデータのコピーを操作するため、たとえば、ユーザーデータのフィールドを変更しても、元の値には反映されません。

繰り返しになりますが、これは値型の標準的な動作と変わりませんが、人を驚かせるのに十分です。

さらに、値型は参照型ほどの最適化の範囲をサポートしていないため、値型での一部の操作は参照型よりも遅くなる可能性があります。

シンプルに保つ

では、最初の例を見てみましょう。

[MoonSharpUserData]
class MyClass
{
    public double calcHypotenuse(double a, double b)
    {
        return Math.Sqrt(a * a + b * b);
    }
} 

double CallMyClass1()
{
    string scriptCode = @"    
        return obj.calcHypotenuse(3, 4);
    "; 

    // MoonSharpUserData型をすべて自動的に登録
    UserData.RegisterAssembly(); 

    Script script = new Script(); 

    // MyClassのインスタンスをスクリプトのグローバルに渡す
    script.Globals["obj"] = new MyClass();  

    DynValue res = script.DoString(scriptCode); 

    return res.Number;
}

ここでは以下のことを行っています。

少し複雑

もう少し複雑な例を試してみましょう。

class MyClass
{
    public double CalcHypotenuse(double a, double b)
    {
        return Math.Sqrt(a * a + b * b);
    }
}

static double CallMyClass2()
{
    string scriptCode = @"    
        return obj.calcHypotenuse(3, 4);
    "; 

    // MyClassだけを明示的に登録
    UserData.RegisterType<MyClass>(); 

    Script script = new Script();

    // ユーザーデータを明示的に作成
    DynValue obj = UserData.Create(new MyClass());
    
    script.Globals.Set("obj", obj); 

    DynValue res = script.DoString(scriptCode); 

    return res.Number;
} 

大きな違い

大きな違いは以下の通りです。

また、C#コードではメソッドがCalcHypotenuse? と呼ばれていましたが、LuaスクリプトではcalcHypotenuse として呼ばれていることに注目してください。

他のバージョンが存在しない限り、MoonSharp?は異なる言語の構文規則間の一貫性を高めるために、いくつかの限定的な方法でケースを自動的に調整してメンバーを一致させます。たとえば、SomeMethodWithLongName?というメンバーは、LuaスクリプトからsomeMethodWithLongName?やsome_method_with_long_nameとしてアクセスすることもできます。

静的メンバーの呼び出し

calcHypotenuse メソッドが静的であるとしましょう。

[MoonSharpUserData]
class MyClassStatic
{
    public static double calcHypotenuse(double a, double b)
    {
        return Math.Sqrt(a * a + b * b);
    }
}

呼び出す方法

これを呼び出すには2つの方法があります。

方法1 - 静的メソッドはインスタンスから透過的に呼び出すことができます - 何もする必要はなく、すべて自動的に行われます

double MyClassStaticThroughInstance()
{
    string scriptCode = @"    
        return obj.calcHypotenuse(3, 4);
    ";

    // MoonSharpUserData型をすべて自動的に登録
    UserData.RegisterAssembly();

    Script script = new Script();

    script.Globals["obj"] = new MyClassStatic();

    DynValue res = script.DoString(scriptCode);

    return res.Number;
}

別の方法

- 型を直接渡す(またはUserData.CreateStaticメソッドを使用する)ことで、プレースホルダーのユーザーデータを作成できます。
double MyClassStaticThroughPlaceholder()
{
    string scriptCode = @"    
        return obj.calcHypotenuse(3, 4);
    ";

    // MoonSharpUserData型をすべて自動的に登録
    UserData.RegisterAssembly();

    Script script = new Script();

    script.Globals["obj"] = typeof(MyClassStatic);

    DynValue res = script.DoString(scriptCode);

    return res.Number;
}

':' と '.' のどちらを使うべきか 上記の例のコードを考えると、この構文を使うべきでしょうか

return obj.calcHypotenuse(3, 4);

それともこちらでしょうか?

return obj:calcHypotenuse(3, 4);

99.999%の場合、違いはありません。MoonSharp?は、ユーザーデータに対して呼び出しが行われていることを認識し、それに応じて動作します。

違いが出る可能性があるコーナーケース

違いが出る可能性があるコーナーケースがあります。たとえば、プロパティがデリゲートを返し、そのデリゲートをインスタンスとして元のオブジェクトですぐに呼び出す場合などです。これはまれなシナリオですが、そのような場合は手動で処理する必要があります。

オーバーロード

オーバーロードされたメソッドがサポートされています。オーバーロードされたメソッドのディスパッチは、いくぶん黒魔術的で、C#のオーバーロードディスパッチほど決定的ではありません。

これは、いくつかの曖昧さが存在するためです。たとえば、オブジェクトはこれらの2つのメソッドを宣言できます。

void DoSomething(int i) { ... }
void DoSomething(float f) { ... }

Luaのすべての数値がdoubleであることを考えると、MoonSharp?はどのメソッドをディスパッチすべきかをどのように知ることができるでしょうか?

この問題を解決するために、MoonSharp?は入力型に対するすべてのオーバーロードのヒューリスティック係数を計算し、最適なオーバーロードを選択します。MoonSharp?がオーバーロードを間違った方法で解決していると思われる場合は、フォーラムやdiscordに報告して、ヒューリスティックを調整してください。

MoonSharp?は、ヒューリスティックの重みを可能な限り安定させようとしており、メソッド間のスコアが同点の場合は、常に決定的に同じものを選択します(ビルドやプラットフォーム間で一貫した動作を提供するため)。

とは言え、MoonSharp?が考えているのとは異なるオーバーロードを選択することは十分にありえます。

そのため、オーバーロードが同等の処理を行うようにすることが非常に重要です。これは、間違ったオーバーロードを呼び出すことの影響を最小限に抑えるためです。これは、ベストプラクティスであるべきですが、ここでその概念を強化する価値があります。

参考

MoonSharp?によるUnityとLuaの連携

https://qiita.com/massu2357/items/425cc93b5965ceea29aa

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2024-05-03 (金) 02:30:54 (14d)