更有效地使用资产数据库的技巧

Asset Import Pipeline v2 是Unity中新的默认资源管道,它用重写的资源数据库取代 Asset Import Pipeline v1,以实现快速平台切换。它为具有强大依赖性跟踪的可扩展资产导入奠定了基础。继续阅读以了解资产数据库的工作原理并发现一些节省时间的技巧。
自Unity 2019.3 发布以来,新的 Asset Import Pipeline 已成为新项目的默认管道。结合许多其他改进,这为更可靠、更高性能和可扩展的资产导入管道奠定了基础。这次重写改变了库文件夹的工作方式,以支持新的工作流程。
让我们深入了解您可能会遇到的一些情况以及如何有效地管理它们。您将学习如何发现和解决一些浪费您的时间和项目绩效的瓶颈。
此处的提示适用于Unity 2019.3 及更高版本。请记住,如果您的项目超出了原型设计阶段,为了获得最大的稳定性,我们建议您使用Unity的最新长期支持 (LTS) 版本, Unity 2019 LTS。
首先,让我们讨论一下在创建新项目或打开尚不存在资产库的项目时发生的一些事情。
如果你查看 Editor.log 文件,你会注意到很多行如下内容:
- 开始导入…
- 完成导入...
这是资产导入管道留下其操作痕迹的方式,以便可以在以后的某个时间点进行检查。
您可以使用此信息来找出某种类型的瓶颈,即 资产导入时间。
当您查看每行的输出时,您可以提取以下数据:
- 资源路径
- 导入时长
- 文件扩展名
通过解析这些数据,我们就可以按扩展进行分类。一旦您知道哪个导入器导入了哪些资产,您就可以将这些数据汇总到饼图中,向您显示哪些类型的资产导入时间最长。例如:

这些数据可以让你更清楚地了解项目的瓶颈在哪里。
在这个特定的例子中,纹理、模型和预制件是最耗时的,为研究哪些资产可以优化提供了一个起点。
下载此 SimpleEditorLogParser 示例项目 并将其用作您自己的解析器的基础。
能够看到哪个资产类别需要最长时间才能导入可以帮助您规划优化工作的重点。如果纹理导入是耗时最长的类别,则检查导入时间最长的纹理,并考虑删除不应该出现在最终版本中的纹理。
这不仅会加快您的导入时间,而且如果您正在进行干净的夜间构建或类似操作,还可以提高持续集成管道的性能。
能够快速打开您的项目非常重要。一整天重新启动编辑器或打开各种项目所花费的时间加起来可能会浪费宝贵的时间。
随着项目变得越来越复杂并且使用更多功能,打开所需的时间也越来越长。这可能是由多种因素造成的,在Unity 2019.3 之前,无法在编辑器启动时启动并运行分析器。
在打开Unity时可以提供的几个 命令行参数 中,-profiler-enable 命令行参数允许您在启动期间对编辑器进行分析。
此命令告诉编辑器开始记录应用程序第一帧的分析数据,这是编辑器可见之前执行的所有代码。使用这个参数可以帮助您了解启动期间发生了什么以及哪些花费了时间。您可以查看它是Unity中的系统、Asset Store 包还是特定于您的项目的代码。这可以帮助您弄清楚下一步该做什么。

在此捕获中,您可以看到打开这个特定的项目需要大约 50 秒。
乍一看,加载 AssetDatabase 似乎需要 43 秒。经过进一步检查,可以清楚地发现,调用 OnPostProcessAllAssets耗时 14 秒 。进一步说,RegisterScriptsAndTryLoadingExistingUserAssemblies 期间执行的代码总共需要 10 秒,其中 5.7 秒 用于加载域,而调用具有 [InitializeOnLoad] 属性的脚本进一步减慢了加载速度。
这些启动数据可以帮助您追踪性能瓶颈,并查看代码是在您的项目中还是在Unity本身中。
现在,Library 文件夹可以包含同一资产的多个导入结果,因此使用新资产导入管道的项目不再具有 Library 文件夹中的“简单”GUID(全局唯一标识符)到文件夹映射。
库文件夹中的文件基于其内容的哈希值加上一些静态和动态依赖项。这使得Unity具有 快速平台切换、资产重复数据删除以及如果资产哈希值已存在于库文件夹中则跳过导入等功能。
所以这意味着在库文件夹中查找导入结果不再是件容易的事。下面是一个示例,向您展示了在哪里可以找到“Assets/Prefabs/MyPrefab.prefab”的导入结果:
public class LibraryPathsForAsset
{
[MenuItem("AssetDatabase/OutputLibraryPathsForAsset")]
public static void OutputLibraryPathsForAsset()
{
var assetPath = "Assets/Prefabs/MyPrefab.prefab";
StringBuilder assetPathInfo = new StringBuilder();
var guidString = AssetDatabase.AssetPathToGUID(assetPath);
//The ArtifactKey is needed here as there are plans to
//allow importing for different platforms without switching
//platform, thus ArtifactKeys will be parametrized in the future
var artifactKey = new ArtifactKey(new GUID(guidString));
var artifactID = AssetDatabaseExperimental.LookupArtifact(artifactKey);
//Its possible for an Asset to have multiple import results,
//if, for example, Sub-assets are present, so we need to iterate
//over all the artifacts paths
AssetDatabaseExperimental.GetArtifactPaths(artifactID, out var paths);
assetPathInfo.Append($"Files associated with {assetPath}");
assetPathInfo.AppendLine();
foreach (var curVirtualPath in paths)
{
//The virtual path redirects somewhere, so we get the
//actual path on disk (or on the in memory database, accordingly)
var curPath = Path.GetFullPath(curVirtualPath);
assetPathInfo.Append("\t" + curPath);
assetPathInfo.AppendLine();
}
Debug.Log("Path info for asset:\n"+assetPathInfo.ToString());
}
}
以下是Unity不同版本的两个要点:
这些示例有所不同,因为随着资产导入管道实现的日趋成熟,许多 API 已从实验命名空间移至 AssetDatabase 自己的命名空间。
通常,您可能希望在项目中找到特定资产并利用其结果采取一些措施。当您运行编辑器代码时,您甚至可能想要多次执行此操作。
调用 AssetDatabase.FindAssets 将遍历整个项目以匹配您提供给它的查询。随着项目变得越来越大,这可能会成为性能瓶颈,因为搜索不同规模项目所需的时间会线性增长。

正如预期的那样,项目拥有的资产越多,搜索它们所需的时间就越长。幸运的是,随着时间的推移,找到每项资产的时间保持相对稳定。

如您所见,如果您的项目拥有数十万个资产,那么搜索资产可能会导致明显的开发速度放缓。当资产数量达到 200,000 时,一个简单的搜索查询就已经有 200 毫秒的延迟。
使用蛮力攻击的方法会产生以下常见的使用模式:
string[] assets = AssetDatabase.FindAssets ("t:texture2D");
if(assets.Length > 0)
{
string path = AssetDatabase.GUIDToAssetPath (guids[0]);
if (!string.IsNullOrEmpty (path))
{
Texture tex = AssetDatabase.LoadAssetAtPath<Texture>(path);
//Do something with this texture
}
}本质上,此代码遍历整个项目以找到一个纹理,然后对其执行某些操作。
AssetDatabase 提供了一种通过 GUID 查找资产路径的方法。您可以将其视为按键在 字典 中查找某些内容,而不是通过迭代数组来查找匹配项。
与蛮力破解相比,使用此方法的好处是 AssetDatabase 不需要查看整个项目来查找资产。它可以使用 GUID 作为数据库索引并按照该路径将资产加载到内存中。
您可以在资产对应的 .meta 文件中找到 GUID,例如:
fileFormatVersion:2
指南:9fc0d4010bbf28b4594072e72b8655ab
默认导入器:
外部对象:{}
用户数据:
assetBundleName:
assetBundleVariant:
在这种情况下,此资产的 GUID 为 9fc0d4010bbf28b4594072e72b8655ab。
根据这些信息,您可以执行以下操作:
var path = AssetDatabase.GUIDToAssetPath("9fc0d4010bbf28b4594072e72b8655ab");
var asset = AssetDatabase.LoadAssetAtPath<Texture>(path);
现在您的资产已可供使用。
通常情况下,资产的 GUID 是恒定的。
然而,在某些情况下,资产及其 .meta 文件会重复,从而引起冲突,AssetDatabase 将通过以下方式解决该冲突:
如果你将项目内的文件夹复制到同一项目中的另一个位置
多次导入 Asset Store 包或多次将文件夹从其他项目复制到您的项目中
当 AssetDatabase.Refresh 执行时,许多系统需要协同工作才能使您的项目处于有效状态。在我的 Unite Copenhagen 演讲中,我详细介绍了调用 Refresh 时发生的各个步骤。
刷新期间执行的特定回调与代码交互。这会影响刷新操作完成所需的时间。
域重新加载期间执行的代码越多,编辑器体验就会越慢。为了在迭代代码时保持流程,请仔细考虑何时执行代码以及是否可以将其推迟到项目的后期。
在域重新加载期间,如果您的代码包含以下任何方法,则会执行它:
1.醒
2.OnEnable
3.OnValidate
这些方法中的代码最好应该非常快,或者应该推迟到其他时间运行(例如,不是在刷新期间)。这是因为这些回调应该帮助您恢复某些状态,但由于这些调用中可以执行的操作没有任何限制,因此任何无法扩展的代码(即遍历整个项目的任何代码)都会在编辑器打开时减慢脚本的迭代速度。
另一种方法是使用 EditorApplication.delayCall,在 AssetDatabase 有机会检测并导入磁盘上的所有更改后,您的代码将在下一个编辑器滴答时执行。
关注Unity论坛上的 此主题 ,即可了解该领域改进的最新资讯。
我希望你发现这些技巧很有用。让我们知道您还想了解有关资产导入管道的哪些信息或您的痛点是什么。我们正在积极致力于改进资产导入管道,我们希望让您的迭代尽可能接近即时,这样您在使用编辑器和更改资产和/或脚本时就可以更有效率。
