在Unity中,子线程是无法调用Unity主线程的API的,因为unity不允许这么干。
但是我们可以通过别的途径,实现这一功能。
大致思路:
将子线程中需要调用的函数,通过委托传递给Loom中的委托列表,在Loom中去调用该委托。因为Loom是继承MonoBehavior的,挂载在空物体上面,所以由他去执行委托,自热是没问题的!
详细思路:
- 一开始在Unity中创建一个新物体obj,挂上Loom脚本。
- Loom中有List<Action> listActions;
- 将子线程中的函数,以委托的形式,传递给listActions,Loom在Updata里面,轮询去调用委托。
- 巧妙的将子线程无法调用主线程的API 转化为 子线程传递给空物体上面的Loom,在Loom里面去调用委托来调用主线程API。
Loom.cs:
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;
public class Loom : MonoBehaviour
{
//是否已经初始化
static bool isInitialized;
private static Loom _ins;
public static Loom ins { get { Initialize(); return _ins; } }
void Awake()
{
_ins = this;
isInitialized = true;
}
//初始化
public static void Initialize()
{
if (!isInitialized)
{
if (!Application.isPlaying)
return;
isInitialized = true;
var obj = new GameObject("Loom");
_ins = obj.AddComponent<Loom>();
DontDestroyOnLoad(obj);
}
}
//单个执行单元(无延迟)
struct NoDelayedQueueItem
{
public Action<object> action;
public object param;
}
//全部执行列表(无延迟)
List<NoDelayedQueueItem> listNoDelayActions = new List<NoDelayedQueueItem>();
//单个执行单元(有延迟)
struct DelayedQueueItem
{
public Action<object> action;
public object param;
public float time;
}
//全部执行列表(有延迟)
List<DelayedQueueItem> listDelayedActions = new List<DelayedQueueItem>();
//加入到主线程执行队列(无延迟)
public static void QueueOnMainThread(Action<object> taction, object param)
{
QueueOnMainThread(taction, param, 0f);
}
//加入到主线程执行队列(有延迟)
public static void QueueOnMainThread(Action<object> action, object param, float time)
{
if (time != 0)
{
lock (ins.listDelayedActions)
{
ins.listDelayedActions.Add(new DelayedQueueItem { time = Time.time + time, action = action, param = param });
}
}
else
{
lock (ins.listNoDelayActions)
{
ins.listNoDelayActions.Add(new NoDelayedQueueItem { action = action, param = param });
}
}
}
//当前执行的无延时函数链
List<NoDelayedQueueItem> currentActions = new List<NoDelayedQueueItem>();
//当前执行的有延时函数链
List<DelayedQueueItem> currentDelayed = new List<DelayedQueueItem>();
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);
}
}
}
void OnDisable()
{
if (_ins == this)
{
_ins = null;
}
}
}
测试
Text text;
void Start()
{
Thread recvThread = new Thread( ThreadFun ) { IsBackground = true };
recvThread.Start();
}
void ThreadFun()
{
// 用Loom的方法在Unity主线程中调用Text组件
Loom.QueueOnMainThread((param) =>
{
text.text = "测试";
}, null);
}
需要注意的是,应当提前把Loom实例化出来。
其实在上述的测试代码中,在ThreadFun()中调用Loom函数,Loom已经执行了初始化了。
假如这是第一次调用Loom,那么会触发Loom的初始化函数Initialize(),会执行 new GameObject("Loom"),相当于还是在子线程中调用主线程代码,还是行不通 会报错的。所以我们要提前给Loom实例化。
方法有2:
1.手动在场景中创建一个空物体,挂上Loom脚本
2.在游戏启动的时候,主动调用Loom的初始化函数。假设GameManager.cs 是游戏的管理启动脚本
GameManager.cs :
void Start()
{
Loom.Initialize();
}