【发布时间】:2018-06-13 15:55:01
【问题描述】:
我有一个在游戏开始时发生的反序列化任务。我基本上需要从持久路径中提取一些图像并从中创建一堆资产。图像可以很大(10-50MB)并且可以有很多,所以基本上这可以让我的框架永远冻结在单个任务上。我尝试使用Coroutines,但我可能会误解如何正确使用它们。
由于协程确实是单线程的,它们不会让我在 UI 运行时完成创建这些资产。我也不能只创建一个新线程来完成这项工作,并在完成回调后跳回主线程,因为 Unity 不允许我访问他们的 API(我正在创建 Texture2D、Button()、父对象等.)。
我该怎么办?我真的需要创建一个庞大的IEnumerable 函数并每隔一行代码放置一堆yield return null 吗?这似乎有点过分了。有没有办法调用需要访问 Unity 中的主线程的耗时方法,并让 Unity 根据需要将其分散到尽可能多的帧中,这样它就不会阻塞 UI?
这是Deserialize 方法的示例:
public IEnumerator Deserialize()
{
// (Konrad) Deserialize Images
var dataPath = Path.Combine(Application.persistentDataPath, "Images");
if (File.Exists(Path.Combine(dataPath, "images.json")))
{
try
{
var images = JsonConvert.DeserializeObject<Dictionary<string, Item>>(File.ReadAllText(Path.Combine(dataPath, "images.json")));
if (images != null)
{
foreach (var i in images)
{
if (!File.Exists(Path.Combine(dataPath, i.Value.Name))) continue;
var bytes = File.ReadAllBytes(Path.Combine(dataPath, i.Value.Name));
var texture = new Texture2D(2, 2);
if (bytes.Length <= 0) continue;
if (!texture.LoadImage(bytes)) continue;
i.Value.Texture = texture;
}
}
Images = images;
}
catch (Exception e)
{
Debug.Log("Failed to deserialize Images: " + e.Message);
}
}
// (Konrad) Deserialize Projects.
if (Projects == null) Projects = new List<Project>();
if (File.Exists(Path.Combine(dataPath, "projects.json")))
{
try
{
var projects = JsonConvert.DeserializeObject<List<Project>>(File.ReadAllText(Path.Combine(dataPath, "projects.json")));
if (projects != null)
{
foreach (var p in projects)
{
AddProject(p);
foreach (var f in p.Folders)
{
AddFolder(f, true);
foreach (var i in f.Items)
{
var image = Images != null && Images.ContainsKey(i.ParentImageId)
? Images[i.ParentImageId]
: null;
if (image == null) continue;
i.ThumbnailTexture = image.Texture;
// (Konrad) Call methods that would normally be called by the event system
// as content is getting downloaded.
AddItemThumbnail(i, true); // creates new button
UpdateImageDescription(i, image); // sets button description
AddItemContent(i, image); // sets item Material
}
}
}
}
}
catch (Exception e)
{
Debug.Log("Failed to deserialize Projects: " + e.Message);
}
}
if (Images == null) Images = new Dictionary<string, Item>();
yield return true;
}
所以这需要大约 10 秒才能完成。它需要反序列化驱动器中的图像,创建按钮资产,设置一堆育儿关系等。我会很感激任何想法。
附言。我还没有更新到实验性的 .NET 4.6,所以我还在使用 .NET 3.5。
好的,阅读下面的 cmets,我想我可以尝试一下。我将 IO 操作放入不同的线程中。他们不需要 Unity API,所以我可以完成这些并存储 byte[] 并在完成后将字节加载到纹理中。试试看:
public IEnumerator Deserialize()
{
var dataPath = Path.Combine(Application.persistentDataPath, "Images");
var bytes = new Dictionary<Item, byte[]>();
var done = false;
new Thread(() => {
if (File.Exists(Path.Combine(dataPath, "images.json")))
{
var items = JsonConvert.DeserializeObject<Dictionary<string, Item>>(File.ReadAllText(Path.Combine(dataPath, "images.json"))).Values;
foreach (var i in items)
{
if (!File.Exists(Path.Combine(dataPath, i.Name))) continue;
var b = File.ReadAllBytes(Path.Combine(dataPath, i.Name));
if (b.Length <= 0) continue;
bytes.Add(i, b);
}
}
done = true;
}).Start();
while (!done)
{
yield return null;
}
var result = new Dictionary<string, Item>();
foreach (var b in bytes)
{
var texture = new Texture2D(2, 2);
if (!texture.LoadImage(b.Value)) continue;
b.Key.Texture = texture;
result.Add(b.Key.Id, b.Key);
}
Debug.Log("Finished loading images!");
Images = result;
// (Konrad) Deserialize Projects.
if (Projects == null) Projects = new List<Project>();
if (File.Exists(Path.Combine(dataPath, "projects.json")))
{
var projects = JsonConvert.DeserializeObject<List<Project>>(File.ReadAllText(Path.Combine(dataPath, "projects.json")));
if (projects != null)
{
foreach (var p in projects)
{
AddProject(p);
foreach (var f in p.Folders)
{
AddFolder(f, true);
foreach (var i in f.Items)
{
var image = Images != null && Images.ContainsKey(i.ParentImageId)
? Images[i.ParentImageId]
: null;
if (image == null) continue;
i.ThumbnailTexture = image.Texture;
// (Konrad) Call methods that would normally be called by the event system
// as content is getting downloaded.
AddItemThumbnail(i, true); // creates new button
UpdateImageDescription(i, image); // sets button description
AddItemContent(i, image); // sets item Material
}
}
}
}
}
if (Images == null) Images = new Dictionary<string, Item>();
yield return true;
}
我不得不承认它有一点帮助,但仍然不是很好。看着探查器,我刚走出大门就得到了相当大的摊位:
这是我的反序列化例程导致它:
有什么办法可以解决这个问题?
【问题讨论】:
-
就像你说的,你可以使用线程来进行这种长期的努力,但是你不需要任何特殊的方式来移动结果。您可以从工作线程中设置一个简单的标志,
Update()可以不时监控。你不能使用协同程序,因为它们是原子的,如果你不中断文件加载,你很容易阻止你的游戏 -
但是文件加载不是问题。在
File.ReadAllBytes上花费的实际时间并不多。实际的瓶颈在于对 Unity API 的调用,在AddProject我创建按钮、分配新父级、设置文本等。在这些调用中,我必须使用慢速方法,如GetComponentsInChildrenGetComponent等。我认为这些调用导致一个摊位。 -
不,导致停顿的原因是使用
File.ReadAllBytes加载文件并序列化 json。由于您没有使用 Unity 的 API,因此将这两个放在一个线程中,将结果存储在一个数组中。等待它完成然后使用LoadImage将每个加载到Texture2D。在你这样做之前,责怪 Unity 的 API 还为时过早。 -
哦,我明白你的意思了。在运行时创建动态内容并不是制作高效游戏的方法。 设计它。创造它。烤吧
-
就像我说的,对那些非原子操作使用不同的线程。您的反序列化代码只会导致 FPS 大幅下降。与流行的看法相反,协程从来都不是一个好主意,它为糟糕的编码实践开创了先例。 amazon.com/Engine-Architecture-Second-Jason-Gregory/dp/…
标签: c# unity3d .net-3.5 coroutine