xLuaとUnityでコルーチンを連携する方法についてまとめました。
Unity2020.2.6
xLua 2.1.15
はじめに
xLuaはUnityでLuaを利用するためのライブラリです。
基本的な使い方は以下の記事にまとめていますので、必要に応じて参照してください。
本記事でxLuaでLuaを記述する際にUnityのコルーチンを使う方法についてまとめます。
コルーチンを使う
コルーチンの開始や終了はMonoBehaviourの機能なので、これをLuaで使うにはまずStartCoroutine()
やStopCoroutine()
といったメソッドをLuaに渡す必要があります。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using XLua; public class Example : MonoBehaviour { private LuaEnv _luaEnv; private void Start() { _luaEnv = new LuaEnv(); // LuaにStartCoroutineとStopCoroutineを渡す // 本当はコルーチンを走らせるためのDontDestroyOnLoadなMonoBehaviourを別で用意してそのメソッドを渡すべき Coroutine InvokeStartCoroutine(IEnumerator routine) => StartCoroutine(routine); void InvokeStopCoroutine(Coroutine coroutine) => StopCoroutine(coroutine); _luaEnv.Global.Set("csStartCoroutine", (Func<IEnumerator, Coroutine>)InvokeStartCoroutine); _luaEnv.Global.Set("csStopCoroutine", (Action<Coroutine>)InvokeStopCoroutine); _luaEnv.DoString("require 'coroutine_example'"); } private void Update() { if (_luaEnv != null) { _luaEnv.Tick(); } } private void OnDestroy() { _luaEnv.Dispose(); } } // LuaからWaitForSecondsを使うのでLuaCallCsharpに登録しておく public static class CoroutineConfig { [LuaCallCSharp] public static List<Type> LuaCallCSharp => new List<Type> { typeof(WaitForSeconds) }; }
次にこうして渡したメソッドをLuaで使います。
coroutine_exampleに以下のように記述します。
-- xlua.utilをrequireする local util = require 'xlua.util' -- コルーチンを開始する関数 function startCoroutine(routine) return csStartCoroutine(util.cs_generator(routine)) end -- コルーチンを停止する関数 function stopCoroutine(coroutine) csStopCoroutine(coroutine) end -- ルーチンを定義 function routine() print(CS.UnityEngine.Time.frameCount) -- C#側のルーチンを呼びだす coroutine.yield(CS.UnityEngine.WaitForSeconds(1)) print(CS.UnityEngine.Time.frameCount) end -- コルーチン開始 local coroutine = startCoroutine(routine) -- コルーチンを止める --stopCoroutine(coroutine)
Luaのルーチンをutil.cs_generatorに与えることでC#のルーチンとして取り扱えます。
コルーチンを入れ子にする
Lua側でコルーチンを入れ子にするにはLuaを以下のように書きます。
local util = require 'xlua.util' function startCoroutine(routine) return csStartCoroutine(util.cs_generator(routine)) end function stopCoroutine(coroutine) csStopCoroutine(coroutine) end function subRoutine() print('start subRoutine') coroutine.yield(CS.UnityEngine.WaitForSeconds(3)) print('end subRoutine') end function routine() print('start routine') -- subRoutineを呼び出して待つ coroutine.yield(util.cs_generator(subRoutine())) print('end routine') end local coroutine = startCoroutine(routine)
コルーチンを待ち合わせる
次にコルーチンの待ち合わせをしてみます。
準備としてExampleクラスを以下のように書き換えます。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using XLua; public class Example : MonoBehaviour { private LuaEnv _luaEnv; private void Start() { _luaEnv = new LuaEnv(); Coroutine InvokeStartCoroutine(IEnumerator routine) => StartCoroutine(routine); void InvokeStopCoroutine(Coroutine coroutine) => StopCoroutine(coroutine); _luaEnv.Global.Set("csStartCoroutine", (Func<IEnumerator, Coroutine>)InvokeStartCoroutine); _luaEnv.Global.Set("csStopCoroutine", (Action<Coroutine>)InvokeStopCoroutine); // 自身をLuaに渡しておく _luaEnv.Global.Set("example", this); _luaEnv.DoString("require 'coroutine_example'"); } private void Update() { if (_luaEnv != null) { _luaEnv.Tick(); } } private void OnDestroy() { _luaEnv.Dispose(); } // コルーチンを追加 public IEnumerator TestRoutine() { Debug.Log("1"); yield return new WaitForSeconds(1.0f); Debug.Log("2"); yield return new WaitForSeconds(1.0f); Debug.Log("3"); } } public static class CoroutineConfig { [LuaCallCSharp] public static List<Type> LuaCallCSharp => new List<Type> { typeof(WaitForSeconds) }; }
次にLuaのルーチンを以下のように書き換えます。
local util = require 'xlua.util' function startCoroutine(routine) return csStartCoroutine(util.cs_generator(routine)) end function stopCoroutine(coroutine) csStopCoroutine(coroutine) end function routine() -- 1つ目のコルーチンを開始だけする(待たない) local innerCoroutine1 = csStartCoroutine(example:TestRoutine()) -- 2つ目のコルーチンを開始だけする(待たない) local innerCoroutine2 = csStartCoroutine(example:TestRoutine()) -- 両方が終わるまで待つ coroutine.yield(innerCoroutine1) coroutine.yield(innerCoroutine2) print('end') end local coroutine = startCoroutine(routine)
これで二つのコルーチンを待ち合わせることができました。
Luaの関数をIEnumerator型としてC#から取得する
さてここまではLuaでC#のコルーチンを扱う方法を見てきました。
次に、Luaに書いたコルーチンをC#から取り扱う方法についてまとめます。
例として前節のLuaで定義したroutine()関数をC#からIEnumeratorとして取得することを考えます。
-- これをC#から取得する function routine() -- 中身は省略 end
これをC#から取得するには、まずLuaに以下のようにgetCsRoutine関数を定義します。
function getCsRoutine() return util.cs_generator(routine); end
これをC#から以下のようにして取得します。
var routine = _luaEnv.Global.Get<Func<IEnumerator>>("getCsRoutine").Invoke();
得られた型はIEnumeratorになるので、これをStartCoroutineなどでそのままコルーチンとして走らせることができます。