Unity Scriptable Build Pipeline源码学习笔记(1)-总体架构,依赖分析接口

Addressable底层打包是由Scriptable Build Pipeline(简称SBP)实现的,为了更好的使用Addressable,所以有必要学习下SBP。

0x0 总体流程篇#

Pipeline设计模式#

总体来看,SBP使用了pipeline模式,将打包分过程成若干个IBuildTask步骤,然后依次执行,因此如果需要增加额外功能,可以实现自己的IBuildTask。

断点后可以看到默认流程使用了21个步骤(有点复杂),最终完成了打包工作。

0x1 新的打包接口ContentBuildInterface#

ContentBuildInterface

具体来看SBP中IBuildTask通过使用ContentBuildInterface类来实现具体功能,大致包括:

接口名 功能
GetPlayerObjectIdentifiersInAsset 传入guid,返回Asset资源内所有的ObjectIdentify信息
GetPlayerDependenciesForObject 查询Asset内部的Object所依赖的其他对象列表
WriteSerializedFile 保存AssetBundle文件

一句话文档,Unity的传统艺能#

查看文档发现,只有一句话,基本不知道是干嘛用的,所以还是需要配合断点研究

0x2 依赖分析步骤研究#

SBP中CalculateCustomDependencyData实现了对Asset进行依赖分析的功能。这个步骤主要使用了ContentBuildInterface提供的2个接口,
1.GetPlayerObjectIdentifiersInAsset
2.GetPlayerDependenciesForObject

为了更好的断点这2个接口,我们尝试脱离SBP做测试,直接调用2个接口打印具体的信息是什么。

GetPlayerObjectIdentifiersInAsset返回YAML结构内的序列化对象ObjectIdentifier#

先写结论,GetPlayerObjectIdentifiersInAsset返回Asset文件YAML级别的序列化对象,该对象用ObjectIdentifier类表示

具体实验步骤如下:
1.新建一个Cube,一个Mateial,使用一张Texture,保存为Prefab作为测试用的Asset。

2.实现一个Prefab信息查看窗口

Copy
using System.Collections.Generic; using UnityEditor; using UnityEditor.Build.Content; using UnityEngine; namespace Yaojz { public class AssetTestWindow:EditorWindow { private Object _asset; private List<ObjectIdentifier> _objectIdentifiers = new List<ObjectIdentifier>(); [MenuItem("yaojz/testAsset")] public static void Open() { var win = CreateInstance<AssetTestWindow>(); win.Show(); } private void OnGUI() { _asset = EditorGUILayout.ObjectField(_asset, typeof(Object)); if (GUILayout.Button("show")) { GetAssetInfo(); } foreach (var objId in _objectIdentifiers) { var localId = objId.localIdentifierInFile.ToString(); EditorGUILayout.LabelField("guid", objId.guid.ToString()); EditorGUILayout.LabelField("localId", localId); EditorGUILayout.LabelField("filepath", objId.filePath); EditorGUILayout.LabelField("FileType", objId.fileType.ToString()); } } private void GetAssetInfo() { //获得prefab的guid AssetDatabase.TryGetGUIDAndLocalFileIdentifier(_asset, out var guid, out long localId); //传入prefab的guid var ids = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(new GUID(guid), BuildTarget.Android); _objectIdentifiers.Clear(); foreach (var id in ids) { _objectIdentifiers.Add(id); } } } }

Asset中的对象信息ObjectIdentifier#

通过上面的测试结果对比Prefab的文件内容,可以知道ObjectIdentifier的含义是Asset内的可序列化对象(Object)的信息,这里打印出来的Ojbect对象是Component对象和GameObject对象。
查看prefab序列化文件我们可以看到,2514506536740829174这个id对应的类型是Transform,而ObjectIdentifier中的fileType显示结果是MetaAssetType,暂时不知道是什么含义,我希望获取Transform类型,那么应当如何获得ObjectIdentifier对应的Type呢?

通过ContentBuildInterface.GetTypeForObject获得ObjectIdentifier的Type信息#

调用返回数组或者者单个结果的接口中的任意一个,都可以获得ObjectIdentifier的Type

Copy
//数组版本 var types = ContentBuildInterface.GetTypeForObjects(ids); //单个结果版本 var type = ContentBuildInterface.GetTypeForObject(ids[0]);

通过ContentBuildInterface.GetPlayerDependenciesForObject获取依赖对象列表#

下面实现一个打印结果的log函数,这个函数需要3个参数,另外2个参数是BuildTarget和TypeDB
BuildTarget我们可以选一个,比如Android,那么TypeDB从哪里来呢

Copy
private void LogDependency(ObjectIdentifier id) { var depIds = ContentBuildInterface.GetPlayerDependenciesForObject(id,_buildTarget,_typeDB); foreach (var depId in depIds) { Debug.Log(depId); } }

TypeDB 类型数据库#

在BuildPlayerScripts类中,通过调用PlayerBuildInterface.CompilePlayerScripts我们可以获得TypeDB对象
CompilePlayerScripts的作用是编译所有脚本代码。

根据ChatGPT的回答,TypeDB(类型数据库)是一个用于存储所有类型信息的数据库。TypeDB包含了Unity中所有的类、结构体、枚举、委托等类型的信息,它是Unity的一个核心组件,可以用来进行反射和序列化等操作。
TypeDB中存储的信息包括类型的名称、命名空间、父类、成员变量、方法等信息。这些信息可以通过C#代码或Unity编辑器中的各种工具来访问和修改。

而官方文档描述只有一句话:用于保存有关脚本类型和属性数据的信息的容器。

因此,在调用GetPlayerDependenciesForObject之前,我们调用CompilePlayerScripts来获得TypeDB对象

Copy
private void BuildScripts() { var rlt = PlayerBuildInterface.CompilePlayerScripts(_settings, ContentPipeline.kScriptBuildPath); _typeDB = rlt.typeDB; }

依赖关系接口ContentBuildInterface.GetPlayerDependenciesForObject与AssetDatabase.GetDependencies的性能比较#

在SBP问世之前,我们通过AssetDatabase.GetDependencies来查询资源的依赖关系,ContentBuildInterface提供的接口性能更高,100000次循环2个接口大致差10倍,我的笔记本测试为4s和63s

Copy
private int _testCount = 100000; private void Test(ObjectIdentifier id) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i <_testCount ; i++) { var depIds = ContentBuildInterface.GetPlayerDependenciesForObject(id,_buildTarget,_typeDB); } stopwatch.Stop(); Debug.Log($"sec:{stopwatch.Elapsed.TotalSeconds}"); } private void Test2() { var path = AssetDatabase.GetAssetPath(_asset); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < _testCount; i++) { var rlt = AssetDatabase.GetDependencies(path); } stopwatch.Stop(); Debug.Log($"sec:{stopwatch.Elapsed.TotalSeconds}"); }
posted @   jeoyao  阅读(1899)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示
目录