【Unity】xLua導入手順

## 本記事について
xLua使えるようにするまでに5hくらいかかったためその方法まとめる。
silicon用の手順だが、他の端末でも参考になるはず。

## 環境
mac OS 12.5 Monterey
チップ Apple M2
Unity 2021.3.20f1
[xLua v2.1.16_with_silicon_support](https://github.com/Tencent/xLua/releases/tag/v2.1.16_with_silicon_support)
Xcode 13.4.1
URP対応


## サクッとやる場合
以下の手順を行ったものをサンプル以外以下に反映したのでこちら使っていただけると良いかなと！
https://github.com/teach310/xLua/releases/tag/v2.1.16_silicon_reposition

## ステップ1 インストールしてHello Worldする

install方法は[README](https://github.com/Tencent/xLua/blob/v2.1.16_with_silicon_support/README_EN.md)に書かれている。

> Unpack the zip package and you will see an Assets directory, which corresponds to the Unity project's Assets directory. Keep the directory structure in your Unity project.

![スクリーンショット_2023-03-05_14_37_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/81872572-7ede-941e-0c6f-e446a335cde6.png)

これダウンロードしてきてAssets以下をそのままProjectにコピーする。

![スクリーンショット 2023-03-05 14.38.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/37015917-7224-36a3-f99b-9e1b62fdc931.png)

具体的にはPluginsとXLuaフォルダ。

で、`Assets/XLua/Examples/01_Helloworld/Helloworld.scene`開いて実行すると以下のエラーが出る。

```
DllNotFoundException: xlua assembly:<unknown assembly> type:<unknown type> member:(null)
XLua.LuaEnv..ctor () (at Assets/XLua/Src/LuaEnv.cs:66)
XLuaTest.Helloworld.Start () (at Assets/XLua/Examples/01_Helloworld/Helloworld.cs:19)
```

本エラーの対処法は以下のissueに記載されている。

[unity M1 version DllNotFoundException: xlua assembly #986](https://github.com/Tencent/xLua/issues/986)

プラグインのxlua.bundleをsilicon用に置き換える必要がある。
まずは既存のxlua.bundleの設定を確認する。

![スクリーンショット_2023-03-05_14_44_57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/c3dcc754-2ce8-b47a-78c0-def61bdaf47d.png)

EditorとStandaloneでしか使わないように設定されている。
確認したら、xlua.bundleを生成する。

> compile m1 version plugin using this: https://github.com/Tencent/xLua/blob/master/build/make_osx_lua53.sh

このシェルは内部でcmakeを使う。macにcmakeが入ってなかったらインストールする必要がある。brewなら以下

```
$ brew install cmake
```

で上記コメントには`make_osx_lua53.sh`と書かれていたが、`make_osx_lua54.sh`という新しいバージョンの方があったのでそっちを実行する。

buildフォルダの中で実行すること。(そうしないとエラーでる)

```
build((v2.1.16_with_silicon_support))$ ./make_osx_lua54.sh
```

以下のようにxlua.bundleが作られる。

![スクリーンショット 2023-03-05 14.53.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/c4c9b735-f3dd-50be-0229-1a9dcdceb082.png)

そうしたら、既存の`Assets/Plugins/xlua.bundle`を削除して、ビルドした新しいのを`Assets/Plugins/arm64/xlua.bundle`に配置

![スクリーンショット 2023-03-05 14.57.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/75276bed-aaf3-53d4-a45e-de3bebf74e37.png)
defaultだと上記のように全てのPlatformに含まれてしまうため、Editor, Standaloneのみになるように設定する

これをやらずにiosのビルドに含まれると以下のエラーが出る。

> ** EXPORT FAILED **
Error Domain=IDEDistributionPipelineErrorDomain Code=0 "Code signing "xlua.bundle" failed." UserInfo={NSLocalizedDescription=Code signing "xlua.bundle" failed., NSLocalizedRecoverySuggestion=View distribution logs for more information.}

これで再度シーンを再度再生すると以下のようにログが出力されてHello World成功する

![スクリーンショット 2023-03-05 15.02.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/24424cb5-bbae-d01c-67c7-6bacf0bfbfaf.png)

## ステップ2 assembly definition
![スクリーンショット 2023-03-05 15.10.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/48d43efe-73e0-ccc6-18b3-df69db9d5e0b.png)

xLuaはアセンブリ分けられておらず、Assmbly-CSharpに入ってしまっている。
毎回コンパイル走らせる必要ないため、分ける。

UnityのPackageの一般的な構成である、Runtime, Editorに分離する。
https://docs.unity3d.com/Manual/cus-layout.html

現状

```
- XLua
  - Editor
  - Examples
      - 13_BuildFromCLI　// Editor
      - それ以外 // Runtime
  - Src
      - Editor
      - 他
  - Tutorial // Runtimeのみ
```

こうする

```
- XLua
  - Editor
      - XLua.Editor.asmdef
  - Runtime
      - XLua.Runtime.asmdef
```

補足) XLua.Runtime.asmdefに関してはXLua.asmdefのほうが一般的だが、この名前つけるのが他のファイルとの名前重複の都合でNGだったためRuntimeってつけている

![スクリーンショット 2023-03-05 15.31.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/811ac6ca-b4bc-88c6-e3c2-6537cd7df2a3.png)

## ステップ3 Generate Codeで生成されるフォルダのパスを変更する。
https://github.com/Tencent/xLua/blob/v2.1.16_with_silicon_support/Assets/XLua/Doc/Faq_EN.md

フォルダ移動に関しては以下のようにFAQに記載されている。

> Can xLua be placed in another directory?
> Yes, but the generated code directory needs to be configured (by default, it is in the Assets\XLua\Gen directory). For details, see the GenPath configuration in XLua Configuration.doc.

フォルダ変えるのはOK. Generate Codeの生成場所はdefaultで`Assets/XLua/Gen`になってる。
 
> The important thing to note about changing directories is that the generated code and xLua core code must be in the same assembly. If you want to use the hotfix function, the xLua core code must be in the Assembly-CSharp assembly.

生成したコードとxLuaのcore codeは同じassemblyにある必要がある。
hotfix機能はAssembly-CSharpじゃないと使えない。

であるならば、hotfix機能は捨てよう！(hotfix機能が何をやるかは知らないけども)

ということで、Generate Codeの生成場所を
`Assets/XLua/Gen`から`Assets/XLua/Runtime/Gen`に変更する

必要になる知識は以下2つ。

- パスの設定場所
- MenuItem定義してる場所

両方以下のファイルの中にある。

https://github.com/Tencent/xLua/blob/master/Assets/XLua/Src/Editor/Generator.cs#L26-L30

それを踏まえて以下のクラスを作成した。

```Assets/XLua/Editor/Custom/CustomGenerator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using CSObjectWrapEditor;
using UnityEditor;

namespace XLuaCustomEditor
{
    // This class changes Generate Code Output Path From XLua/Gen to XLua/Runtime/Gen
    public class CustomGenerator
    {
#if XLUA_GENERAL
        public static string genPath = "./Gen/";
#else
        public static string genPath = Application.dataPath + "/XLua/Runtime/Gen/";
#endif

        [MenuItem("XLua/Generate Code", validate = true)]
        public static bool DisableOriginalGenAll()
        {
            return false;
        }

        [MenuItem("XLua/Clear Generated Code", validate = true)]
        public static bool DisableOriginalClearGen()
        {
            return false;
        }

        [MenuItem("XLua/Custom/Generate Code", false, 1)]
        public static void GenAll()
        {
            GeneratorConfig.common_path = genPath;
            Generator.GenAll();
        }

        [MenuItem("XLua/Custom/Clear Generated Code", false, 2)]
        public static void ClearGen()
        {
            GeneratorConfig.common_path = genPath;
            Generator.ClearAll();
        }
    }
}

```

![スクリーンショット 2023-03-05 15.50.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/e57ec3a7-bc8c-0132-ae8b-5c05c6a2352b.png)

オリジナルのメソッドを呼ぶ前にcommon_pathを設定してるだけ。

![スクリーンショット 2023-03-05 15.54.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/caafe966-b385-8da0-ce37-50f73d9bd610.png)

Menuからすぐに動作確認できる

## ステップ4 Pluginsを移動させる

XLuaフォルダ以下に全部入っててほしいから。

以下の記事が大変参考になった。ありがとうございます！

https://qiita.com/su10/items/5e78ec8f6ef0bcafc759

![スクリーンショット 2023-03-05 16.01.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/f37615a9-a1c7-80a5-4a9b-057623605925.png)

シンプルに移動。

Pluginsフォルダは[Unity2018のドキュメント](https://docs.unity3d.com/ja/2018.3/Manual/SpecialFolders.html)では特殊なフォルダー名とされているが、[2021のドキュメント](https://docs.unity3d.com/ja/2021.3/Manual/SpecialFolders.html)では削除されている。
そのためわざわざPluginsから別の名前に変更とかはしなくていいかなと判断。

## ステップ5 Generate Codeで生成されるファイルを減らす。

Assets/XLua/Runtime/Examples/ExampleGenConfig.cs

この設定によって生成されるファイルが多くなっている。で、その出力されたファイルがURPだとエラー出す。(URPじゃない場合は不明)

生成するものは最低で良いため、以下のように修正する。

ExampleGenConfigの中身を全部コメントアウトしたGenConfigを作成。

```Assets/XLua/Runtime/Custom/GenConfig.cs
using System.Collections.Generic;
using System;
using UnityEngine;
using XLua;

namespace XLua.Custom
{
    public static class GenConfig
    {
        // //lua中要使用到C#库的配置，比如C#标准库，或者Unity API，第三方库等。
        // [LuaCallCSharp]
        // public static List<Type> LuaCallCSharp = new List<Type>() {
        //             typeof(System.Object),
        //             typeof(UnityEngine.Object),
        //             typeof(Vector2),
        //             typeof(Vector3),
        //             typeof(Vector4),
        //             typeof(Quaternion),
        //             typeof(Color),
        //             typeof(Ray),
        //             typeof(Bounds),
        //             typeof(Ray2D),
        //             typeof(Time),
        //             typeof(GameObject),
        //             typeof(Component),
        //             typeof(Behaviour),
        //             typeof(Transform),
        //             typeof(Resources),
        //             typeof(TextAsset),
        //             typeof(Keyframe),
        //             typeof(AnimationCurve),
        //             typeof(AnimationClip),
        //             typeof(MonoBehaviour),
        //             typeof(ParticleSystem),
        //             typeof(SkinnedMeshRenderer),
        //             typeof(Renderer),
        //             typeof(WWW),
        //             typeof(Light),
        //             typeof(Mathf),
        //             typeof(System.Collections.Generic.List<int>),
        //             typeof(Action<string>),
        //             typeof(UnityEngine.Debug)
        //         };

        // //C#静态调用Lua的配置（包括事件的原型），仅可以配delegate，interface
        // [CSharpCallLua]
        // public static List<Type> CSharpCallLua = new List<Type>() {
        //             typeof(Action),
        //             typeof(Func<double, double, double>),
        //             typeof(Action<string>),
        //             typeof(Action<double>),
        //             typeof(UnityEngine.Events.UnityAction),
        //             typeof(System.Collections.IEnumerator)
        //         };

        // //黑名单
        // [BlackList]
        // public static List<List<string>> BlackList = new List<List<string>>()  {
        //             new List<string>(){"System.Xml.XmlNodeList", "ItemOf"},
        //             new List<string>(){"UnityEngine.WWW", "movie"},
        // #if UNITY_WEBGL
        //             new List<string>(){"UnityEngine.WWW", "threadPriority"},
        // #endif
        //             new List<string>(){"UnityEngine.Texture2D", "alphaIsTransparency"},
        //             new List<string>(){"UnityEngine.Security", "GetChainOfTrustValue"},
        //             new List<string>(){"UnityEngine.CanvasRenderer", "onRequestRebuild"},
        //             new List<string>(){"UnityEngine.Light", "areaSize"},
        //             new List<string>(){"UnityEngine.Light", "lightmapBakeType"},
        // #if UNITY_ANDROID
        //             new List<string>(){"UnityEngine.Light", "SetLightDirty"},
        //             new List<string>(){"UnityEngine.Light", "shadowRadius"},
        //             new List<string>(){"UnityEngine.Light", "shadowAngle"},
        // #endif
        //             new List<string>(){"UnityEngine.WWW", "MovieTexture"},
        //             new List<string>(){"UnityEngine.WWW", "GetMovieTexture"},
        //             new List<string>(){"UnityEngine.AnimatorOverrideController", "PerformOverrideClipListCleanup"},
        // #if !UNITY_WEBPLAYER
        //             new List<string>(){"UnityEngine.Application", "ExternalEval"},
        // #endif
        //             new List<string>(){"UnityEngine.GameObject", "networkView"}, //4.6.2 not support
        //             new List<string>(){"UnityEngine.Component", "networkView"},  //4.6.2 not support
        //             new List<string>(){"System.IO.FileInfo", "GetAccessControl", "System.Security.AccessControl.AccessControlSections"},
        //             new List<string>(){"System.IO.FileInfo", "SetAccessControl", "System.Security.AccessControl.FileSecurity"},
        //             new List<string>(){"System.IO.DirectoryInfo", "GetAccessControl", "System.Security.AccessControl.AccessControlSections"},
        //             new List<string>(){"System.IO.DirectoryInfo", "SetAccessControl", "System.Security.AccessControl.DirectorySecurity"},
        //             new List<string>(){"System.IO.DirectoryInfo", "CreateSubdirectory", "System.String", "System.Security.AccessControl.DirectorySecurity"},
        //             new List<string>(){"System.IO.DirectoryInfo", "Create", "System.Security.AccessControl.DirectorySecurity"},
        //             new List<string>(){"UnityEngine.MonoBehaviour", "runInEditMode"},
        //         };
    }
}

```

ExampleGenConfigは消す。

一度Clearしてから再生成すると、生成されるクラスがとても減っていることがわかる。

![スクリーンショット 2023-03-05 16.17.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/f14fabba-cefc-39cb-301a-4bc673ea3392.png)

必要なもの、必要そうなものをコメントアウトしていくのが良いのではないかと思う。

この設定と似たExampleConfigに関してはとくに問題ないため放置している。

## サンプル　luaにインスタンスを渡して、luaからインスタンスのメソッドを叩く

[サンプル](https://github.com/teach310/xLua/tree/teach_sample/Assets)ブランチでも確認可能

```.lua
adv:ShowMessage('Hello World!')
```

これでログに「Hello World!」を出力できるっていうものを作る。

![スクリーンショット_2023-03-05_18_28_43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/3ba2e24b-b0a1-1ecb-2ce4-bd61e04c5176.png)

まず2つAssembly Defenitionを作る。

依存関係は以下のようにする

```
Sample -> Sample.AdvCommandInterfaces
XLua -> Sample.AdvCommandInterfaces
Sample -> XLua
```

そしてまずはインタフェースを作る

```Assets/Sample/Runtime/AdvCommandInterfaces/IAdvCommand.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Sample.AdvCommandInterfaces
{
    public interface IAdvCommand
    {
        void ShowMessage(string message);
    }
}

```

ここにはluaから呼び出したいメソッドを定義する。

そしてluaで呼び出すクラスを以下のように作る

```Assets/XLua/Runtime/Custom/Sample/LuaAdvCommandAdapter.cs
using System.Collections;
using System.Collections.Generic;
using Sample.AdvCommandInterfaces;
using UnityEngine;

namespace XLua.Custom
{
    [LuaCallCSharp]
    public class LuaAdvCommandAdapter
    {
        public IAdvCommand advCommand;

        public LuaAdvCommandAdapter(IAdvCommand advCommand)
        {
            this.advCommand = advCommand;
        }

        public void ShowMessage(string message)
        {
            advCommand.ShowMessage(message);
        }
    }
}


```

ポイントは
- luaから呼び出すコードはXLua以下に配置すること。 (Genしたコードの依存関係が相互依存になるのを防ぐため)
- 呼び出すクラスに`LuaCallCSharp`をつけること。(ないとReflectionでアクセスするらしい)

そしてluaから送られたコマンドを処理する実装を作る。

```Assets/Sample/Runtime/SampleScene.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using Sample.AdvCommandInterfaces;

public class SampleScene : MonoBehaviour
{
    public class AdvCommand : IAdvCommand
    {
        public void ShowMessage(string message)
        {
            Debug.Log(message);
        }

        public XLua.Custom.LuaAdvCommandAdapter GetAdapter()
        {
            return new XLua.Custom.LuaAdvCommandAdapter(this);
        }
    }

    void Start()
    {
        var luaEnv = new LuaEnv();
        var advCommand = new AdvCommand();
        luaEnv.Global.Set("adv", advCommand.GetAdapter());
        luaEnv.DoString(@"
            adv:ShowMessage('Hello World!')
        ");
        luaEnv.Dispose();
    }
}

```

そして実行するまえにXLuaの Generate Codeを実行することで、`Assets/XLua/Runtime/Gen/XLua_Custom_LuaAdvCommandAdapterWrap.cs`等のファイルが作られる。

これでSample.sceneにSampleScene配置したらHello Worldできる。

インタフェースを返すことで相互依存を防ぐとともに、luaへの依存も少なくできるのがいいんじゃないかと思っている。

## おわりに
導入だけでとても大変だった..!!
luaの情報は多くないため、いろいろノウハウ増えたらいいなぁと思います！