【Unity】【xLua】LuaとUnityのコルーチンの連携方法まとめ

xLuaとUnityでコルーチンを連携する方法についてまとめました。

Unity2020.2.6
xLua 2.1.15

はじめに

xLuaはUnityでLuaを利用するためのライブラリです。
基本的な使い方は以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

本記事で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#から取得する

さてここまではLuaC#のコルーチンを扱う方法を見てきました。

次に、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などでそのままコルーチンとして走らせることができます。

関連

light11.hatenadiary.com