我爱萨其马虞co 发表于 2020-12-24 09:04

Unity Loom 插件使用

最近在做资源更新时,需要显示现在的进度调。我在Unity中开启了一个线程来做下载任务,然后实时刷新下载进度。然后Unity报了一个错误。get_isActiveAndEnabled can only be called from the main thread.
大体意思是Unity中的组件只能运行在Unity的主线程中,无法在我新开的线程中调用Unity的组件。百度了一下,找到了Loom这个插件。感觉真的很好用,错误不见了。
这里做下记录。
Loom插件就一个脚本导入到Unity中就行了。具体脚本内容如下
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;

    public class Loom :MonoBehaviour
    {
      public static int maxThreads = 8;
      static int numThreads;

      private static Loom _current;
      //private int _count;
      public static Loom Current
      {
            get
            {
                Initialize();
                return _current;
            }
      }

      void Awake()
      {
            _current = this;
            initialized = true;
      }

      static bool initialized;

      public static void Initialize()
      {
            if (!initialized)
            {

                if (!Application.isPlaying)
                  return;
                initialized = true;
                var g = new GameObject("Loom");
                _current = g.AddComponent<Loom>();
#if !ARTIST_BUILD
                UnityEngine.Object.DontDestroyOnLoad(g);
#endif
            }

      }
      public struct NoDelayedQueueItem
      {
            public Action<object> action;
            public object param;
      }

      private List<NoDelayedQueueItem> _actions = new List<NoDelayedQueueItem>();
      public struct DelayedQueueItem
      {
            public float time;
            public Action<object> action;
            public object param;
      }
      private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();

      List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();

      public static void QueueOnMainThread(Action<object> taction, object tparam)
      {
            QueueOnMainThread(taction, tparam, 0f);
      }
      public static void QueueOnMainThread(Action<object> taction, object tparam, float time)
      {
            if (time != 0)
            {
                lock (Current._delayed)
                {
                  Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = taction, param = tparam });
                }
            }
            else
            {
                lock (Current._actions)
                {
                  Current._actions.Add(new NoDelayedQueueItem { action = taction, param = tparam });
                }
            }
      }

      public static Thread RunAsync(Action a)
      {
            Initialize();
            while (numThreads >= maxThreads)
            {
                Thread.Sleep(100);
            }
            Interlocked.Increment(ref numThreads);
            ThreadPool.QueueUserWorkItem(RunAction, a);
            return null;
      }

      private static void RunAction(object action)
      {
            try
            {
                ((Action)action)();
            }
            catch
            {
            }
            finally
            {
                Interlocked.Decrement(ref numThreads);
            }

      }


      void OnDisable()
      {
            if (_current == this)
            {

                _current = null;
            }
      }



      // Use this for initialization
      void Start()
      {

      }

      List<NoDelayedQueueItem> _currentActions = new List<NoDelayedQueueItem>();

      // Update is called once per frame
      void Update()
      {
            if (_actions.Count > 0)
            {
                lock (_actions)
                {
                  _currentActions.Clear();
                  _currentActions.AddRange(_actions);
                  _actions.Clear();
                }
                for (int i = 0; i < _currentActions.Count; i++)
                {
                  _currentActions.action(_currentActions.param);
                }
            }

            if (_delayed.Count > 0)
            {
                lock (_delayed)
                {
                  _currentDelayed.Clear();
                  _currentDelayed.AddRange(_delayed.Where(d => d.time <= Time.time));
                  for (int i = 0; i < _currentDelayed.Count; i++)
                  {
                        _delayed.Remove(_currentDelayed);
                  }
                }

                for (int i = 0; i < _currentDelayed.Count; i++)
                {
                  _currentDelayed.action(_currentDelayed.param);
                }
            }
      }
    }
代码也就100多行,主要是两个比较主要的方法
RunAsync(Action a)和QueueOnMainThread(Action<object> taction, object tparam)

开启一个线程然后在Loom.RunAsyn()中调用需要回到Unity主线程更新界面时调用QueueOnMainThread()即可。简单好用。
下面贴一下 不用Loom报错的代码和用Loom后的代码
    public Text mText;
        void Start ()
    {
      Thread thread = new Thread(RefreshText);
      thread.Start();
        }
       
        void Update ()
    {
       
        }
    private void RefreshText()
    {
      mText.text = "Hello Loom!";
    }运行这段代码Unity会报上面的错误,意思是我在我自己开的线程中调用了Unity组件。
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Threading;
public class testLoom : MonoBehaviour
{

    public Text mText;
        void Start ()
    {
      
      // 用Loom的方法调用一个线程
      Loom.RunAsync(
            () =>
            {
               Thread thread = new Thread(RefreshText);
                thread.Start();
            }
            );
        }
       
        void Update ()
    {
       
        }
    private void RefreshText()
    {
      // 用Loom的方法在Unity主线程中调用Text组件
      Loom.QueueOnMainThread((param) =>
            {
                mText.text = "Hello Loom!";
            },null);
    }
}
这样就是不会报错。
短短的100多行代码就搞定了这个问题,不得不佩服大神们的思路,有时间可以好好研究下Loom的实现的方法。
页: [1]
查看完整版本: Unity Loom 插件使用