Unityの型定義ファイルをTypeScriptToLuaで使う

# Unityの型定義ファイル

UnityでTypeScriptを使うソリューションとして以前の記事でPuertsをリンクしましたが、PuertsにはC#クラスから型定義ファイルを生成する機能があるらしく、
これをTypeScriptToLuaに応用できるんじゃないかと思ったのでやってみた。

https://github.com/Tencent/puerts

https://qiita.com/katai5plate/items/8ef3e8a29565af928f00#unity-%E5%81%B4%E3%81%AE%E8%A8%AD%E5%AE%9A

# ダウンロードと修正

index.d.tsを自動生成をすると言ってもそのためだけにPuertsインストールするのはどうかと思うので生成済みのファイルはないかと探してみたらありました。

https://github.com/chexiongsheng/puerts_unity_demo/blob/master/projects/1_Start_Template/Assets/Gen/Typing/csharp/index.d.ts

とはいえ残念ながらそのままでは使えないので修正する必要があります。

```typescript:index.d.ts(修正前)

declare module 'csharp' {
    namespace CSharp {
        interface $Ref<T> {
            value: T
        }
        namespace System {
            interface Array$1<T> extends System.Array {
                get_Item(index: number):T;
                set_Item(index: number, value: T):void;
            }
        }
        interface $Task<T> {}
        namespace UnityEngine {
            /** Class containing methods to ease debugging while developing a game. */
            class Debug extends System.Object
            {
            /** Get default debug logger. */
                public static get unityLogger(): UnityEngine.ILogger;
                /** Reports whether the development console is visible. The development console cannot be made to appear using: */
                public static get developerConsoleVisible(): boolean;
                public static set developerConsoleVisible(value: boolean);
//---------------中略-------------------//
        }
    }
    export = CSharp;
}
```

```typescript:index.d.ts(修正後)

//declare module 'csharp' {
    //namespace CSharp {
    declare namespace CS {
        interface $Ref<T> {
            value: T
        }
        namespace System {
            interface Array$1<T> extends System.Array {
                get_Item(index: number):T;
                set_Item(index: number, value: T):void;
            }
        }
        interface $Task<T> {}
        namespace UnityEngine {
            /** Class containing methods to ease debugging while developing a game. */
            class Debug extends System.Object
            {
            /** Get default debug logger. */
                /** @noSelf */public static get unityLogger(): UnityEngine.ILogger;
                /** Reports whether the development console is visible. The development console cannot be made to appear using: */
                /** @noSelf */public static get developerConsoleVisible(): boolean;
                /** @noSelf */public static set developerConsoleVisible(value: boolean);
//---------------中略-------------------//
        }
    }
//    export = CSharp;
//}
```

具体的な修正内容は
- 先頭2行と末尾2行をコメントアウト
- 先頭または3行目に```declare namespace CS {```を追加
- エディタやIDEの置換機能で```public static```から```/** @noSelf */public static```に置換(前に```/** @noSelf */```を追加する)

何をしているかというと、csharpモジュール定義を外し、namespaceをCSharpからCSに変更してdeclareしています。
csharpモジュール定義を外したのはモジュールとして定義するとLua変換の際にモジュールを取り込むLuaコードが生成されてしまうのでそれを防ぐためです。
namespaceをCSに変えたのはxLuaには```CS.Debug.Log("test")```のようにCSネームスペースを通じてUnityクラスにアクセスできる機能があるのでそれを活かすためです(MoonSharpにはありません)。
TypeScriptToLuaはstaticメソッドだろうが何だろうが第一引数にthis(self)を渡すように変換(コロン呼び出し)して動かないので、staticメソッドにnoSelfアノテーションをつけることでそれを抑止します。

# 実際に使ってみる

修正した型定義ファイルをUnityプロジェクトフォルダのどこかに置けば動くはずです。TypeScriptコードと同じフォルダに置いておけばいいんじゃないでしょうか。

## MoonSharp

MoonSharpの場合はポリシーの関係でクラス毎にC#側で明示的に登録しないとLuaからC#オブジェクトを扱えない仕様になっているので、C#側で```UserData.RegistrationPolicy = InteropRegistrationPolicy.Automatic;```で暗黙的に自動登録するように設定します。
(Fungusは独自にクラスを列挙したjsonを用意してそれを元にクラスを一括登録している)
またクラスを登録していてもstaticメソッドは使えませんので、予めC#側からタイプオブジェクトをLuaに渡す必要があります。

```csharp
using UnityEngine;
using MoonSharp.Interpreter;
using MoonSharp.Interpreter.Interop;
using MoonSharp.Interpreter.Loaders;

public class MoonTest : MonoBehaviour
{
    private Script _script;

    // Start is called before the first frame update
    void Start()
    {
        //
        UserData.RegistrationPolicy = InteropRegistrationPolicy.Automatic;
        _script = new Script();

        //パスの設定
        UnityAssetsScriptLoader uasl = new UnityAssetsScriptLoader();
        uasl.ModulePaths = new string[1];
        uasl.ModulePaths[0] = UnityAssetsScriptLoader.DEFAULT_PATH + "/?";
        _script.Options.ScriptLoader = uasl;

        //staticメソッドにアクセスするためのタイプオブジェクトの設定
        //UnityEngine.Debugをグローバル変数Debugに、UnityEngine.GameObjectをグローバル変数GameObjectに設定。
        _script.Globals["Debug"] = UserData.CreateStatic(typeof(UnityEngine.Debug));
        _script.Globals["GameObject"] = UserData.CreateStatic(typeof(UnityEngine.GameObject));

        _script.DoString("require('test')");
    }
}
```

```typescript
//C#側で設定したタイプオブジェクトをインテリセンスを有効にしたまま使うためにはこうする
declare class Debug extends CS.UnityEngine.Debug{};
declare class GameObject extends CS.UnityEngine.GameObject{};

let gobj: GameObject = GameObject.Find("Text (Legacy)");
if (gobj !== null)
{
    let txtobj: CS.UnityEngine.UI.Text = (gobj.GetComponent("Text") as CS.UnityEngine.UI.Text);
    if(txtobj !== null)
    {
        txtobj.text = "日本語のテスト";
    }
    else
    {
        Debug.Log("Load Component failed...");
    }
}
else
{
    Debug.Log("Load GameObject failed...");
}
```

## xLua

MoonSharpと違い、xluaの場合は特に何も考える必要もなくそのまま使えます。

```csharp
using System;
using UnityEngine;
using XLua;

public class xLuaTest : MonoBehaviour
{
    private LuaEnv _luaenv;
    
    void Start()
    {
        _luaenv = new LuaEnv();
        _luaenv.DoString("require('test')");
    }

    private void Update()
    {
        _luaenv.Tick();
    }

    private void OnDestroy()
    {
        _luaenv.Dispose();
    }
}
```

```typescript
let gobj: CS.UnityEngine.GameObject = CS.UnityEngine.GameObject.Find("Text (Legacy)");
if (gobj !== null)
{
    let txtobj: CS.UnityEngine.UI.Text = (gobj.GetComponent("Text") as CS.UnityEngine.UI.Text);
    if(txtobj !== null)
    {
        txtobj.text = "日本語のテスト";
    }
    else
    {
        CS.UnityEngine.Debug.Log("Load Component failed...");
    }
}
else
{
    CS.UnityEngine.Debug.Log("Load GameObject failed...");
}
```

クラス名が長過ぎて困る場合は別の短い名前の変数に代入することができます。

```typescript
//インテリセンスを有効にしたまま使うためにはこうする
//すべてトップレベルステートメントに書く必要があります。
declare class Debug extends CS.UnityEngine.Debug{};
declare class GameObject extends CS.UnityEngine.GameObject{};

let Debug = CS.UnityEngine.Debug
let GameObject = CS.UnityEngine.GameObject
```

## 共通化したい

多分いないと思いますが、MoonSharpとxLuaで共通にしたい場合はこうすればいいんじゃないでしょうか。
MoonSharpの方が制約が多いのでMoonSharpの仕様にxLuaが合わせる感じになります。

```typescript
//MoonSharpはC#側でタイプオブジェクトを設定する必要があります。
declare class Debug extends CS.UnityEngine.Debug{};
declare class GameObject extends CS.UnityEngine.GameObject{};
declare const _MOONSHARP: any;

if (typeof(_MOONSHARP) === "undefined")
{
    //xLua
    (_G as any)["Debug"] = CS.UnityEngine.Debug;
    (_G as any)["GameObject"] = CS.UnityEngine.GameObject;
}
```

TypeScript側でやるのがアレな場合はC#側でもできます。変数名さえ合わせればTypeScript側はスッキリします。
というかxLuaでもC#側で以下のようにすればTypeScript側はMoonSharp版と同じコードで動きます。

```csharp
        //_luaenv.Global.Setでは上手くいかない
        _luaenv.DoString(@"
            Debug = CS.UnityEngine.Debug
            GameObject = CS.UnityEngine.GameObject
        ");
```


# 感想

FungusみたいにすでにMoonSharpが組み込まれているという事情でもなければxLuaを使いましょう。