0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

MoonSharpのカスタムスクリプトローダーを作成する

Last updated at Posted at 2023-10-05

経緯と概要

以前、LuaファイルをTextAssetに対応するという記事を書きました。
今回はカスタムスクリプトローダーを作成し、Luaスクリプトの読み込みを便利にしようと思います。
UnityAssetsScriptLoaderを公式が提供してはいますが、現在非推奨のResourcesを要求しているので、自前で実装する必要があります。

調査

まずは、公式チュートリアルと、公式のGithubで公開されているUnityAssetsScriptLoaderを確認します。
その上で分かった最低限必要なことは以下の2つ。

  • ScriptLoaderBase (またはIScriptLoader)を継承する
  • 直接アセットを読みに行くのではなく、Luaのファイル名とその内容を連想配列で保持する

実装

今回の目的は以下の通りです。

  • DoFileでLuaスクリプトを実行する
  • Luaのrequireを使えるようにする

ScriptLoaderBaseを継承

まず、ScriptLoaderBaseを継承したクラスを定義し、override必須の関数を実装します。
UnityAssetsScriptLoaderをベースに実装しますが、Luaスクリプトをアセットから取得して、連想配列に格納する処理は別で実装します。

CustomScriptLoader.cs
using System;
using System.Collections.Generic;
using MoonSharp.Interpreter.Loaders;

public class CustomScriptLoader : ScriptLoaderBase
{
    private Dictionary<string, string> _resources;

    public CustomScriptLoader(Dictionary<string, string> resources)
    {
        _resources = resources;
    }

    // [必須] 実際に読み込むファイル情報を返す
    public override object LoadFile(string file, Table globalContext)
    {
        if (_resources.ContainsKey(file))
        {
            return _resources[file];
        }
        else
        {
            throw new Exception($"Cannot lead script '{file}'.");
        }
	}

    // [必須] 読み込みたいファイルが存在しているかを返す
    public override bool ScriptFileExists(string name)
    {
        return _resources.ContainsKey(name);
    }
}

これで基礎的な部分は終了です。
適当なコードで実験してみましょう。

挙動確認

連想配列でluaスクリプト群を格納する処理は今回は省きます。

Test.cs
using System.Collections.Generic;
using UnityEngine;
using MoonSharp.Interpreter;

public class Test : MonoBehaviour
{
    Dictionary<string, string> _luaAssets;

    void Start()
    {
        Script script = new Script();
        script.Options.ScriptLoader = new CustomScriptLoader( _luaAssets );

        var value = script.DoFile("sample");
        Debug.Log( value.String );
    }
}
sample.lua
return 'Hello'

手段は問いませんが、sample.luaのファイル名と中身を _luaAssetsの中に格納して実行します。

Console
[00:00:00] Hello
UnityEngine.Debug:Log (object)

以上の結果が得られれば大丈夫です。
以前はDoStringで実行していましたが、これでDoFileで実行できるようになりました。

requireへ対応する

最初の目的である、DoFileでのLuaスクリプトの実行は成功しましたが、requireは動作しません。
これには、IScriptLoaderで定義されている関数をoverrideする必要があります。
(ScriptLoaderBaseを継承しているために警告として表示されていません)

CustomScriptLoader.cs
using System;
using System.Collections.Generic;
using MoonSharp.Interpreter.Loaders;

public class CustomScriptLoader : ScriptLoaderBase
{
    /* 省略 */

    public override string ResolveFileName(string fileName, Table globalContext) => fileName;

	public override string ResolveModuleName(string modName, Table globalContext) => _resources.ContainsKey(modName) ? modName : null;
}

ResolveFileNameResolveModuleNameという2つの関数を実装しました。
ScriptLoaderBaseを継承しているので、必須なのはResolveModuleNameだけですが、両方実装したのは気分です。
ザックリと説明するならば、requireで要求したモジュールが本当に存在しているのかを調べ、存在しているならばそのファイル名を返します。今回はファイル名とモジュール名を完全一致させる予定なので、そのままmodNameを返しています。

挙動確認

では今回は、sample_module.luaというファイルに関数を定義し、sample.luaで使ってみましょう。

Test.cs
public class Test : MonoBehaviour
{
    Dictionary<string, string> _luaAssets;

    void Start()
    {
        Script script = new Script();
        script.Options.ScriptLoader = new CustomScriptLoader( _luaAssets );

        var value = script.DoFile("sample");
        Debug.Log( value.Number );
    }
}
sample.lua
require('some_module')

return pow( 5 )
sample_module.lua
function pow (n)
    return n * n
end
Console
[00:00:00] 25
UnityEngine.Debug:Log (object)

残る課題

ひとまず当初の目的は達成できました。
Luaスクリプトをロードする処理自体は下記のような形で、Addressableで適当なグループに突っ込んだものを一括で読み込む処理を実装すればひとまずは解決でしょう。

AddressableLuaAssetLoader
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEditor.AddressableAssets;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class AddressableLuaAssetLoader
{
    public static async Task<Dictionary<string, string>> LoadFromGroupAsync(string groupName)
	{
		var group = AddressableAssetSettingsDefaultObject.Settings.FindGroup(groupName);
		if (group != null)
		{
			var resources = new Dictionary<string, string>();
			foreach (var item in group.entries)
			{
				var asyncOperation = Addressables.LoadAssetAsync<TextAsset>(item.guid);
				await asyncOperation.Task;
				if (asyncOperation.Status == AsyncOperationStatus.Succeeded)
				{
					var loadedAsset = asyncOperation.Result;
					resources.Add(loadedAsset.name, loadedAsset.text);
				}
				else
				{
					Debug.LogError("Failed to load assets guid: " + item.guid);
				}
			}
			return resources;
		}
		return null;
	}
}

ただ、現在のままではLuaスクリプトが更新された時にすぐに反映はされないという問題が残っています。
そこまで対応して初めてLuaの良さを活かし切れると言っても過言ではないでしょう。

幸い、Unityには AssetPostprocessor というものが提供されているので、実装は苦ではないはずです。
ですが、今回の目的からは外れてしまうのでここまでとします。

参考

オススメの記事

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?