项目中遇到需要加载某个文件夹下的内容,但是数量很多,需要在场景开始前就加载完成。这个时候我们可以加一个加载页面,利用协程和异步方法、后台线程去执行这个操作,当读取和创建操作完成后再将加载页面关闭。(这个方法不会阻塞主线程,但是加载页面会比较卡顿)
首先,讲一下思路:
1.先获取文件夹下的所有文件的路径,存起来;
2.使用协程和for循环去执行创建图片和创建预制体的方法;
3.协程内循环执行异步方法,注意异步方法是调用线程,并非主线程,所以一些关于UI操作的任务我们需要用一个单例类,用于在主线程中调度。
接下来,请看代码:
UnityMainThreadDispatcher 是一个单例类,用于在主线程中调度操作:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// UnityMainThreadDispatcher 是一个单例类,用于在主线程中调度操作。
public class UnityMainThreadDispatcher : MonoBehaviour
{
private static readonly Queue<System.Action> _executionQueue = new Queue<System.Action>(); // 创建一个队列用于存储需要在主线程执行的操作
public void Update()
{
lock (_executionQueue) // 锁定队列,确保线程安全
{
while (_executionQueue.Count > 0) // 当队列中有操作时
{
_executionQueue.Dequeue().Invoke(); // 执行队列中的操作
}
}
}
private static UnityMainThreadDispatcher _instance = null; // 单例实例
public static bool Exists()
{
return _instance != null; // 检查实例是否存在
}
public static UnityMainThreadDispatcher Instance()
{
//if (!Exists())
//{
// throw new System.Exception("UnityMainThreadDispatcher is required to run."); // 如果实例不存在,抛出异常
//}
return _instance; // 返回实例
}
public void Awake()
{
if (_instance == null) // 如果实例不存在
{
_instance = this; // 设置实例为当前对象
//DontDestroyOnLoad(gameObject); // 确保该对象在场景切换时不被销毁
}
else if (_instance != this) // 如果实例已经存在且不是当前对象
{
Destroy(gameObject); // 销毁当前对象
}
}
public void Enqueue(System.Action action)
{
lock (_executionQueue) // 锁定队列,确保线程安全
{
_executionQueue.Enqueue(action); // 将操作加入队列
}
}
}
-----------------------------------------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
public class NewBehaviourScript : MonoBehaviour
{
//所有文件的路径数组
public string[] allTexture;
//文件夹路径
public string downloadPath;
// 存储加载的图片预制体
public List<Button> loadedImages = new List<Button>();
// 用于加载图片数据的Texture2D对象
private Texture2D mytex;
//预制体和预制体的父级
private Button btn_Item;
private Transform parent;
// Start is called before the first frame update
void Start()
{
//加载所有的图片
downloadPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\" + "测试";
Debug.Log(DateTime.Now + "开始读取");
// 调用方法开始加载图片
LoadImagesFromFolder(downloadPath);
}
void LoadImagesFromFolder(string folderPath)
{
// 获取文件夹下所有图片文件路径,并倒序排列
allTexture = Directory.GetFiles(folderPath, "*.*", SearchOption.TopDirectoryOnly)
.Where(s => s.EndsWith(".jpg") || s.EndsWith(".png") || s.EndsWith(".jpeg")) // 过滤图片格式
.OrderByDescending(f => f) // 倒序排列
.ToArray(); // 转换为数组
StartCoroutine(LoadLoadImages());
}
private IEnumerator LoadLoadImages()
{
for (int i = 0; i < allTexture.Length; i++)
{
yield return CreatePrefab(i);
}
}
async Task CreatePrefab(int i)
{
await Task.Run(() => {
Debug.Log("生成一个");
// 异步读取图片文件数据
byte[] fileData = File.ReadAllBytes(allTexture[i]);
// 在主线程中生成图片预制体
UnityMainThreadDispatcher.Instance().Enqueue(() =>
{
// 创建 Texture2D 对象并加载图片数据
Texture2D tex = new Texture2D(1, 1);
tex.LoadImage(fileData);
Button newImage = Instantiate(btn_Item, parent); // 实例化图片预制体
newImage.GetComponent<Image>().sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f)); // 设置图片精灵
loadedImages.Add(newImage); // 添加到已加载图片集合中
});
});
// 等待下一帧,防止主线程阻塞
await Task.Yield();
}
}
------------------------------------------------------------------------------------------------------------------------