本記事について
xLua使えるようにするまでに5hくらいかかったためその方法まとめる。
silicon用の手順だが、他の端末でも参考になるはず。
環境
mac OS 12.5 Monterey
チップ Apple M2
Unity 2021.3.20f1
xLua 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に書かれている。
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.
これダウンロードしてきてAssets以下をそのままProjectにコピーする。
具体的には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
プラグインのxlua.bundleをsilicon用に置き換える必要がある。
まずは既存のxlua.bundleの設定を確認する。
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が作られる。
そうしたら、既存のAssets/Plugins/xlua.bundle
を削除して、ビルドした新しいのをAssets/Plugins/arm64/xlua.bundle
に配置
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成功する
ステップ2 assembly definition
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ってつけている
ステップ3 Generate Codeで生成されるフォルダのパスを変更する。
フォルダ移動に関しては以下のように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定義してる場所
両方以下のファイルの中にある。
それを踏まえて以下のクラスを作成した。
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();
}
}
}
オリジナルのメソッドを呼ぶ前にcommon_pathを設定してるだけ。
Menuからすぐに動作確認できる
ステップ4 Pluginsを移動させる
XLuaフォルダ以下に全部入っててほしいから。
以下の記事が大変参考になった。ありがとうございます!
シンプルに移動。
PluginsフォルダはUnity2018のドキュメントでは特殊なフォルダー名とされているが、2021のドキュメントでは削除されている。
そのためわざわざPluginsから別の名前に変更とかはしなくていいかなと判断。
ステップ5 Generate Codeで生成されるファイルを減らす。
Assets/XLua/Runtime/Examples/ExampleGenConfig.cs
この設定によって生成されるファイルが多くなっている。で、その出力されたファイルがURPだとエラー出す。(URPじゃない場合は不明)
生成するものは最低で良いため、以下のように修正する。
ExampleGenConfigの中身を全部コメントアウトしたGenConfigを作成。
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してから再生成すると、生成されるクラスがとても減っていることがわかる。
必要なもの、必要そうなものをコメントアウトしていくのが良いのではないかと思う。
この設定と似たExampleConfigに関してはとくに問題ないため放置している。
サンプル luaにインスタンスを渡して、luaからインスタンスのメソッドを叩く
サンプルブランチでも確認可能
adv:ShowMessage('Hello World!')
これでログに「Hello World!」を出力できるっていうものを作る。
まず2つAssembly Defenitionを作る。
依存関係は以下のようにする
Sample -> Sample.AdvCommandInterfaces
XLua -> Sample.AdvCommandInterfaces
Sample -> XLua
そしてまずはインタフェースを作る
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Sample.AdvCommandInterfaces
{
public interface IAdvCommand
{
void ShowMessage(string message);
}
}
ここにはluaから呼び出したいメソッドを定義する。
そしてluaで呼び出すクラスを以下のように作る
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から送られたコマンドを処理する実装を作る。
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の情報は多くないため、いろいろノウハウ増えたらいいなぁと思います!