2018年01月25日

(メモ)Unityで直列に処理を書いてみた

やりたかったことは一番下のExecuteね。これ、クリック待ちも文章表示もメインスレッドにやらせて、クリック待ちの時はちゃんと関数の途中で待っている。コルーチンでは書きにくかったことを実現、昔のAPIなのでexperimentalな機能使わなくても出来る。
このクラスを継承して、Executeだけをオーバーライドして使うようなクラス作って、Executeからはまるでコンソールアプリケーションみたいな感覚でコードが書ける。アドベンチャーゲームのシナリオなんかには便利なのではないだろうか。
さらには、トランスパイラの類を書けば他の直列に記述するスクリプトからこの関数を作るのもそう難しいことではないだろう。昔のBASICみたいな環境を作ったり、NScripterやら吉里吉里っぽいスクリプトやらを動かせるようになるかも知れない。
C#の(C++でもCでもJavaでも、多分どんな言語でも可能だが)関数の実行を途中で中断して保存・復帰する方法についてはアイデアがある(ソースの自動生成が前提だけど)。また次の記事で。組み合わせれば高速で単純で柔軟なノベルゲームエンジン作れると思う。
なお、残念なことにこのコードはWebGLでは機能しない。UnityのWebGLではスレッドを作れないようなので。PCとAndroidで動くのは確認済み。他は持ってないので試せない……PS系とかSwitchとか気になるよね。
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System.Threading;

public class ThreadTest : MonoBehaviour
{
    delegate void InvokeDelegate();
    static Object lockObject=new Object();
    List invokeList=new List();
    private bool _b;
    public bool canInvoke
    {
        get
        {
            lock (lockObject)
            {
                return _b;
            }
        }
        set
        {
            lock (lockObject)
            {
                _b = value;
            }
        }
    }
    Thread thread;


    //描画用
    public Text text;

    // Use this for initialization
    void Start()
    {
        canInvoke = true;
        thread = new Thread(Execute);
        thread.Start();
    }

    // Update is called once per frame
    void Update()
    {
        if (canInvoke)
        {
            lock (lockObject)
            {
                foreach (var data in invokeList)
                {
                    data();
                }
                invokeList.Clear();
            }
        }
    }

    void InvokeWait()
    {
        while (true)
        {
            lock(lockObject)
            {
                if (!canInvoke)
                {
                    Debug.Log("canInvoke=falseの時にInvokeWaitを実行してはいけません。");
                }
                if (invokeList.Count == 0) break;
            }
            Thread.Sleep(8);
        }
    }

    void ClickWait()
    {
        bool isMouseDown = false;
        while (!isMouseDown)
        {
            InvokeMainThread(()=>
            {
                if (Input.GetMouseButtonDown(0))
                {
                    isMouseDown = true;
                }
            });
            InvokeWait();
        }
    }

    void InvokeMainThread(InvokeDelegate f)
    {
        lock (lockObject)
        {
            invokeList.Add(f);
        }
    }

    void PutText(string s)
    {
        InvokeMainThread(()=>
        {
            text.text = s;
        });
    }

    void Execute()
    {
        PutText("てきすと1");
        ClickWait();
        PutText("てきすと2");
        ClickWait();
        PutText("直列で実行できてる?");

        //ここでは使ってないけど
        /*
        canInvoke=false;
        Load1();
        Load2();
        Setting1();
        canInvoke=true;
        こう書けば、canInvoke=falseのあいだの命令実行中にInvokeしたものが実行されないことが保証され、正確に同じフレームで実行できる。
        */
    }

}

(追記:バグを修正しました。)
posted by NTak_Indies at 20:50| Comment(0) | TrackBack(0) | 日記