美文网首页Unity 学习路径
Unity实践—Unity 内置资源独立打包

Unity实践—Unity 内置资源独立打包

作者: Warl_G | 来源:发表于2021-07-05 00:46 被阅读0次

    针对内置资源重复打包冗余的问题,编写 Addressables Build 脚本将内置资源独立打包
    本人原博:Warl-G's Blog - Unity实践—Unity 内置资源独立打包

    什么是内置资源

    Unity 提供了一些内置资源,可在编辑器中找到内置资源包unity_builtin_extra

    • Windows: ~/Editor/Data/Resources/unity_builtin_extra
    • MacOS:~/Unity.app/Contents/Resources/unity_builtin_extra

    unity_builtin_extra 中包含了一系列默认 Shader 和贴图等资源,可在编辑器中直接选择

    image image

    由上图可见内置贴图资源路径为 Resources/unity_builtin_extra,在代码中可使用AssetDatabase.GetAssetPath 得到同样的路径

    但无法通过该路径读取资源,编辑器下可用接口AssetDatabase.GetBuiltinExtraResource加载内置资源,以下为内置贴图路径

    "UI/Skin/UISprite.psd"
    "UI/Skin/Background.psd"
    "UI/Skin/InputFieldBackground.psd"
    "UI/Skin/Knob.psd"
    "UI/Skin/Checkmark.psd"
    "UI/Skin/DropdownArrow.psd"
    "UI/Skin/UIMask.psd"
    

    另外还有Runtime还有接口Resources.GetBuiltinResource,但目前没有明确用法

    为什么要将内置资源打包

    若制作多个使用了同样内置资源的 Prefab 且被分到了不同的 Bundle 中,AddressablesDefault Build Script是不会统计这些引用而单独分包的,会导致内置资源被重复打进不同的 Bundle 中

    可通过创建使用KnobUISprite的 Image Prefab 各两个,并分别打成四个 Bundle

    image image

    通过对四个 Bundle 解包可看到使用相同资源的 Bundle 都有类似如下的内容(KnobUISprite),data 部分就是资源实际的数据内容,被重复打进了两个包

    ID: 5424255917358561739 (ClassID: 213) Sprite
        m_Name "Knob" (string)
        m_Rect  (Rectf)
            x 12 (float)
            y 12 (float)
            width 40 (float)
            height 40 (float)
        m_Offset (0 0) (Vector2f)
        m_Border (0 0 0 0) (Vector4f)
        m_PixelsToUnits 200 (float)
        m_Pivot (0.5 0.5) (Vector2f)
        m_Extrude 1 (unsigned int)
        m_IsPolygon 0 (bool)
        m_RenderDataKey  (pair)
            first 0000000000000000f000000000000000 (GUID)
            second 10913 (SInt64)
        m_AtlasTags  (vector)
            size 0 (int)
    ...............................
    ...............................
                size 184 (int)
                data (UInt8) #0: 205 204 204 61 205 204 76 61 0 0 0 0 92 143 194 61 205 204 204 189 0 0 0 0 205
                data (UInt8) #25: 204 204 61 123 20 174 189 0 0 0 0 123 20 174 61 205 204 204 61 0 0 0 0 205 204
                data (UInt8) #50: 76 189 205 204 204 61 0 0 0 0 10 215 163 189 205 204 204 189 0 0 0 0 92 143 194
                data (UInt8) #75: 189 41 92 143 61 0 0 0 0 205 204 204 189 143 194 245 60 0 0 0 0 205 204 204 189
                data (UInt8) #100: 174 71 97 189 0 0 0 0 0 0 0 0 62 62 62 143 62 62 62 143 62 62 62 143 62
                data (UInt8) #125: 62 62 143 62 62 62 143 73 73 73 137 116 116 116 135 146 146 146 94 255 255 255 0 255 255
                data (UInt8) #150: 255 0 146 146 146 50 117 117 117 134 55 55 55 143 62 62 62 143 62 62 62 143 62 62 62
                data (UInt8) #175: 143 62 62 62 143 62 62 62 143
            m_Bindpose  (vector)
                size 0 (int)
    
    ...............................
    ...............................
    

    此时一个 Bundle 的大小约为 8 KB

    image

    若内置资源使用范围比较广泛且分包较多,也是有可能造成一定的空间浪费,因此可重写Addressables打包脚本,将使用的内质资源独立打包

    编写 Addressables 打包脚本

    默认打包脚本

    首先可以查看Addressables的默认打包流程,在Packages/Addressables/Editor/Build/DataBuilders下可找到Addressables提供的几种预设打包模式脚本,其中BuildScriptPackedMode.cs即为Default Build Script

    static IList<IBuildTask> RuntimeDataBuildTasks(string builtinShaderBundleName)
    {
        var buildTasks = new List<IBuildTask>();
    
        // Setup
        buildTasks.Add(new SwitchToBuildPlatform());
        buildTasks.Add(new RebuildSpriteAtlasCache());
    
        // Player Scripts
        if (!s_SkipCompilePlayerScripts)
            buildTasks.Add(new BuildPlayerScripts());
        buildTasks.Add(new PostScriptsCallback());
    
        // Dependency
        buildTasks.Add(new CalculateSceneDependencyData());
        buildTasks.Add(new CalculateAssetDependencyData());
        buildTasks.Add(new AddHashToBundleNameTask());
        buildTasks.Add(new StripUnusedSpriteSources());
        buildTasks.Add(new CreateBuiltInShadersBundle(builtinShaderBundleName));
        buildTasks.Add(new PostDependencyCallback());
    
        // Packing
        buildTasks.Add(new GenerateBundlePacking());
        buildTasks.Add(new UpdateBundleObjectLayout());
        buildTasks.Add(new GenerateBundleCommands());
        buildTasks.Add(new GenerateSubAssetPathMaps());
        buildTasks.Add(new GenerateBundleMaps());
        buildTasks.Add(new PostPackingCallback());
    
        // Writing
        buildTasks.Add(new WriteSerializedFiles());
        buildTasks.Add(new ArchiveAndCompressBundles());
        buildTasks.Add(new GenerateLocationListsTask());
        buildTasks.Add(new PostWritingCallback());
    
        return buildTasks;
    }
    
    protected virtual TResult DoBuild<TResult>(AddressablesDataBuilderInput builderInput, AddressableAssetsBuildContext aaContext) where TResult : IDataBuilderResult
    {
      //////////////////////
      //////////////////////
      var builtinShaderBundleName = Hash128.Compute(GetProjectName()) + "_unitybuiltinshaders.bundle";
      var buildTasks = RuntimeDataBuildTasks(builtinShaderBundleName);
      buildTasks.Add(extractData);
      
      IBundleBuildResults results;
        using (m_Log.ScopedStep(LogLevel.Info, "ContentPipeline.BuildAssetBundles"))
        using (new SBPSettingsOverwriterScope(ProjectConfigData.generateBuildLayout)) // build layout generation requires full SBP write results
        {
            var exitCode = ContentPipeline.BuildAssetBundles(buildParams, new BundleBuildContent(m_AllBundleInputDefs), out results, buildTasks, aaContext, m_Log);
    
            if (exitCode < ReturnCode.Success)
                return AddressableAssetBuildResult.CreateResult<TResult>(null, 0, "SBP Error" + exitCode);
       }
      //////////////////////
      //////////////////////
    }
    

    抛弃代码中对资源的预分析和配置过程,如上代码为开始构建的核心部分,在DoBuild方法中创建构建任务队列,使用ContentPipeline.BuildAssetBundles开始构建打包

    RuntimeDataBuildTasks任务队列中有一个任务CreateBuiltInShadersBundle的功能是找到打包资源中使用到的内置 Shader 并独立打包,分析其中核心方法

    public ReturnCode Run()
    {
      //获取所有依赖资源中的内置资源,内置资源的GUID都统一为 0000000000000000f000000000000000
        HashSet<ObjectIdentifier> buildInObjects = new HashSet<ObjectIdentifier>();
        foreach (AssetLoadInfo dependencyInfo in m_DependencyData.AssetInfo.Values)
            buildInObjects.UnionWith(dependencyInfo.referencedObjects.Where(x => x.guid == k_BuiltInGuid));
    
        foreach (SceneDependencyInfo dependencyInfo in m_DependencyData.SceneInfo.Values)
            buildInObjects.UnionWith(dependencyInfo.referencedObjects.Where(x => x.guid == k_BuiltInGuid));
    
        ObjectIdentifier[] usedSet = buildInObjects.ToArray();
        Type[] usedTypes = BuildCacheUtility.GetTypeForObjects(usedSet);
    
        if (m_Layout == null)
            m_Layout = new BundleExplictObjectLayout();
    
      //从依赖的内置资源中找到所有的 Shader 资源,并记录在指定的 Bundle 名下
        Type shader = typeof(Shader);
        for (int i = 0; i < usedTypes.Length; i++)
        {
            if (usedTypes[i] != shader)
                continue;
    
            m_Layout.ExplicitObjectLocation.Add(usedSet[i], ShaderBundleName);
        }
    
        if (m_Layout.ExplicitObjectLocation.Count == 0)
            m_Layout = null;
    
        return ReturnCode.Success;
    }
    

    脚本改写

    由上述代码可见,默认的打包脚本已经帮助我们筛选出了所有的内置资源,只是额外添加了 Shader 单一类型的筛选,因此直接改造CreateBuiltInShadersBundle即可

    1. 创建一个新的实现IBUildTask的类 CreateBuiltInBundle,主要代码内容与CreateBuiltInShadersBundle保持一致,构造方法记录两个 Bundle 名ShaderBundleName 和 BundleName ,一个用于打包内置 Shader,一个用于打包其他内置资源,并对做出如下修改
    public ReturnCode Run()
    {
        HashSet<ObjectIdentifier> buildInObjects = new HashSet<ObjectIdentifier>();
        foreach (AssetLoadInfo dependencyInfo in m_DependencyData.AssetInfo.Values)
            buildInObjects.UnionWith(dependencyInfo.referencedObjects.Where(x => x.guid == k_BuiltInGuid));
    
        foreach (SceneDependencyInfo dependencyInfo in m_DependencyData.SceneInfo.Values)
            buildInObjects.UnionWith(dependencyInfo.referencedObjects.Where(x => x.guid == k_BuiltInGuid));
    
        ObjectIdentifier[] usedSet = buildInObjects.ToArray();
        Type[] usedTypes = ContentBuildInterface.GetTypeForObjects(usedSet);
    
        if (m_Layout == null)
            m_Layout = new BundleExplictObjectLayout();
        
      // 将 Shader 和非 Shader 资源分别记录到两个不同的 Bundle 中
        Type shader = typeof(Shader);
        for (int i = 0; i < usedTypes.Length; i++)
        {
            m_Layout.ExplicitObjectLocation.Add(usedSet[i], usedTypes[i] == shader ? ShaderBundleName : BundleName);
        }
    
        if (m_Layout.ExplicitObjectLocation.Count == 0)
            m_Layout = null;
    
        return ReturnCode.Success;
    }
    
    1. 创建一个新的 Build Script 继承自BuildScriptBase,所有代码和BuildScriptPackedMode.cs保持一致,菜单名称配置可自定义

      RuntimeDataBuildTasksbuildTasks.Add(new CreateBuiltInShadersBundle(builtinShaderBundleName));替换为改造后的CreateBuiltInBundle,并在DoBuild方法中配置 Bundle 名称

    static IList<IBuildTask> RuntimeDataBuildTasks(string builtinShaderBundleName, string builtinBundleName)
    {
        var buildTasks = new List<IBuildTask>();
    
        // Setup
        buildTasks.Add(new SwitchToBuildPlatform());
        buildTasks.Add(new RebuildSpriteAtlasCache());
    
        // Player Scripts
        if (!s_SkipCompilePlayerScripts)
            buildTasks.Add(new BuildPlayerScripts());
        buildTasks.Add(new PostScriptsCallback());
    
        // Dependency
        buildTasks.Add(new CalculateSceneDependencyData());
        buildTasks.Add(new CalculateAssetDependencyData());
        buildTasks.Add(new AddHashToBundleNameTask());
        buildTasks.Add(new StripUnusedSpriteSources());
        buildTasks.Add(new CreateBuiltInBundle(builtinShaderBundleName, builtinBundleName));
        buildTasks.Add(new PostDependencyCallback());
    
        // Packing
        buildTasks.Add(new GenerateBundlePacking());
        buildTasks.Add(new UpdateBundleObjectLayout());
        buildTasks.Add(new GenerateBundleCommands());
        buildTasks.Add(new GenerateSubAssetPathMaps());
        buildTasks.Add(new GenerateBundleMaps());
        buildTasks.Add(new PostPackingCallback());
    
        // Writing
        buildTasks.Add(new WriteSerializedFiles());
        buildTasks.Add(new ArchiveAndCompressBundles());
        buildTasks.Add(new GenerateLocationListsTask());
        buildTasks.Add(new PostWritingCallback());
    
        return buildTasks;
    }
    
    protected virtual TResult DoBuild<TResult>(AddressablesDataBuilderInput builderInput, AddressableAssetsBuildContext aaContext) where TResult : IDataBuilderResult
    {
      //////////////////////
      //////////////////////
      var builtinBundleName = Hash128.Compute(GetProjectName()) + "_unitybuiltin.bundle";
      var builtinShadersBundleName = Hash128.Compute(GetProjectName()) + "_unitybuiltinshaders.bundle";
      var buildTasks = RuntimeDataBuildTasks(builtinShadersBundleName, builtinBundleName);
      buildTasks.Add(extractData);
      //////////////////////
      //////////////////////
    }
    

    修改效果

    构建 Bundle 后,多出一个大小为 7 KB 的defaultlocalgroup_unitybuiltin.bundle,通过解包可见其中只有之前重复打包的 Knob 和 UISprite 两个内置资源,而之前的四个 Bundle 已不再包含具体的资源数据,仅包含一段简单的引用数据,同时单个包体的大小由之前的 8 KB 减小为 4 KB

    image
    Builtin 打包前 Builtin 打包后
    Bundle 数量 4 5
    总 Bundle 大小 32 KB 22 KB
    单个包体大小 image image

    源码链接:GRTools.Addressables · Warl-G

    参考

    Unity内置资源如何打包避免冗余 - 知乎 (zhihu.com)

    相关文章

      网友评论

        本文标题:Unity实践—Unity 内置资源独立打包

        本文链接:https://www.haomeiwen.com/subject/azqbultx.html