【unity】 Loom實現多執行緒

Sitar發表於2024-10-23


通常情況下,unity中在子執行緒中改變變數的值,但是子執行緒尚未結束時,主執行緒無法使用該變數。

因此使用Loom作為中介,子執行緒呼叫並傳值給Loom,Loom呼叫主執行緒的API。

實現步驟
建立Loom空物體,並掛載Loom指令碼

//Loom.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class Loom : MonoBehaviour
{
    static bool isInitialized;

    private static Loom instance;
    public static Loom Instance
    {
        get
        {
            Initialize();
            return instance;
        }
    }

    public static void Initialize()
    {
        if (!isInitialized)
        {
            if (!Application.isPlaying)
                return;
            isInitialized = true;
            var obj = new GameObject("Loom");
            instance = obj.AddComponent<Loom>();

            DontDestroyOnLoad(obj);
        }
    }

    private void Awake()
    {
        instance = this;
        isInitialized = true;
    }

    struct NoDelayQueueItem
    {
        public Action<int> action;
        public int param;
    }

    List<NoDelayQueueItem> listNoDelayActions = new List<NoDelayQueueItem>();

    struct DelayQueueItem
    {
        public Action<int> action;
        public int param;
        public float time;
    }

    List<DelayQueueItem> listDelayedActions = new List<DelayQueueItem>();

    public static void QueueOnMainThread(Action<int> taction, int param)
    {
        QueueOnMainThread(taction, param, 0f);
    }

    public static void QueueOnMainThread(Action<int> action, int param, float time)
    {
        if (time != 0)
        {
            lock (Instance.listDelayedActions)
            {
                Instance.listDelayedActions.Add(new DelayQueueItem { time = Time.time + time, action = action, param = param });
            }
        }
        else
        {
            lock (Instance.listNoDelayActions)
            {
                Instance.listNoDelayActions.Add(new NoDelayQueueItem { action = action, param = param });
            }
        }
    }

    List<NoDelayQueueItem> currentActions = new List<NoDelayQueueItem>();
    List<DelayQueueItem> currentDelayed = new List<DelayQueueItem>();

    private void Update()
    {
        //無延遲的執行佇列中存在任務
        if (listNoDelayActions.Count > 0)
        {
            lock (listNoDelayActions)
            {
                //把所有任務放入當前執行佇列
                currentActions.Clear();
                currentActions.AddRange(listNoDelayActions);
                listNoDelayActions.Clear();
            }
            //挨個執行完任務
            for (int i = 0; i < currentActions.Count; i++)
            {
                currentActions[i].action(currentActions[i].param);
            }
        }
        //有延遲的執行佇列中存在任務
        if (listDelayedActions.Count > 0)
        {
            lock (listDelayedActions)
            {
                //將此刻之前的所有未完成的任務放入當前執行佇列
                currentDelayed.Clear();
                currentDelayed.AddRange(listDelayedActions.Where(d => Time.time >= d.time));
                for (int i = 0; i < currentDelayed.Count; i++)
                {
                    listDelayedActions.Remove(currentDelayed[i]);
                }
            }

            for (int i = 0; i < currentDelayed.Count; i++)
            {
                currentDelayed[i].action(currentDelayed[i].param);
            }
        }
    }
    private void OnDisable()
    {
        if (instance == this)
        {
            instance = null;
        }
    }
}

子執行緒中呼叫Loom類,Loom中執行主執行緒API

private void Awake()
{
    button.GetComponent<UnityEngine.UI.Button>().onClick.AddListener(OnPointClick);
    onPositionChangedEvent += onPositionChanged;
    onPositionChanged();
}

public UnityEngine.UI.Button button;

public Action onPositionChangedEvent;

//子執行緒會改變此變數,此變數發生變化時,會執行委託(主執行緒API)
private int _position = 10;
public int Position
{
    get { return _position; }
    set
    {
        _position = value;
        if (onPositionChangedEvent != null)
        {
            onPositionChangedEvent();
        }
    }
}

Thread thread = null;
int direct = 1;
// button開啟子執行緒,此執行緒一直開啟
public void OnPointClick()
{
    if (thread == null)
    {
        thread = new Thread(() =>
        {
            while (true)
            {
                if (Position < -100)
                {
                    direct = 1;
                }
                else if (Position > 100)
                {
                    direct = -1;
                }
				//子執行緒更改的變數,此變數每次發生改變時需要在主執行緒中使用
                Position += 1 * direct;

                Thread.Sleep(100);
            }
        });
        thread.IsBackground = true;
        thread.Start();
    }

}

//Position發生變化時,就會呼叫該方法
private void onPositionChanged()
{
    //Loom會在子執行緒執行,但是可以呼叫主執行緒API
    Loom.QueueOnMainThread((Position) =>
    {
        transform.position = new Vector3(Position, 0, 0);
    }, Position);
}

相關文章