Unity 包体如何缩减优化(一)

769 阅读4分钟

      在游戏开发过程中,都免不了这么一道工序,那就是对包体的缩减优化。至于为什么要缩减包体,一是上线平台的要求,二是推广运营利于分发和买量,三是小包体下载的时间大大缩小,能较大幅度的提升用户体验,让用户“想下”游戏。下面以本人最近的项目为例,说说如何缩减包体。

       如何缩减包体,我们首选要了解Unity 包体内容来源构成的几个主要部分:

1、Resources 文件夹

2、StreamingAssets 文件夹

3、代码,各种引用库

       接下来我们来说说如何对这三个部分进行缩减优化。

一、Resources 文件夹缩减

       Resources文件夹的内容,无论如何都会打进包体,而且内容越多,越影响游戏启动时间。因此这个文件夹内容除了存放一些在游戏启动期间需要的配置文件、必须字体、资源文件之外,都不要存在放其它文件。坚持这一原则非常重要,因为这个文件夹内容一旦发布,内容便不可更改。随着游戏业务的进展,会不利于在线更新和拓展。

二、StreamingAssets 文件缩减

       StreamingAssets 文件夹主要存放的是Addressable 系统打包出来的资源包,Unity在打包的过程中会先从资源包地址中复制所有资源包到StreamingAssets文件夹,然后再将StreamingAssets内的所有文件全部打进包体。当然,StreamingAssets文件夹也可以按项目需求自定义存放自己所需的文件。

       StreamingAssets 的来源是Addressable,Addressable 的来源实质就是我们游戏内用到的大部分资源,包体占大头的就是这个。我们可以通过以下步骤来验证一下:

打开  Build setting 面板,Build  一次 项目工程,如下图:

 耐心等待一会,打包完后会自动打开APK所在文件夹,同样会产生一份打包日志。我们打开Console 窗口,按以下步骤打开打包日志,如图:

Editor log 日志如下图:

       我们看到Texutres 占了包体的81%,这个是大头了,只要优化好这部分,包体相信会大大缩小。第二部分是Shaders,  由于项目用了URP管线, shaders 占比稍大,如果用内置管线的应该只有几百K左右,这个看项目需求了,我们的重点是缩减Textures 这部分。下面说说如何缩减。

1、先删除没用到的资源

       可以网上搜一搜用插件扫描出来删除,或者自己写脚本删除,或者手动删除。通常执行完这一步能缩减5%-10%左右。

2、按发布平台分别设置纹理属性和格式

       我们不能依赖于unity的默认设置,通常这种设置并不是最优的。利用默认设置时,贴图不符合规定时,通常会转换成RGBA16 或 RGBA32,这样包体会变得很大。所以我们需要覆盖Unity的默认设置,如下图:

       影响包体大小最关键的一个属性,就是Format。就目前来说,Android 平台有透明通道的我们可以选择RGBA ETC2 8bits,没有透明通道的可以选择RGB ETC2 4bits。IOS 平台可以选择ASTC 6X6。其它格式为什么不能选,在这儿就不展开说明了,小伙胖们可以自行通过其它途径了解一下纹理在各个平台上的格式使用和优缺点。

      每一张纹理都这么选择各个平台设置一遍显然不是明智的,我们可以通过代码控制来自动做这一步。代码如下:

先做一个贴图管理器,方便后面扩展:

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.U2D;
using UnityEngine.U2D;
using UnityEngine;

namespace EditorAsset
{
    /// <summary>
    /// 贴图管理器
    /// </summary>
    public class AssetTextureMgr
    {
        //纹理最大尺寸
        private static int _maxTextureSize = 2048;   
        //各平台默认设置格式
        private static TextureImporterFormat _androidAlphaDefaultFormat = TextureImporterFormat.ETC2_RGBA8;
        private static TextureImporterFormat _androidNotAlphaDefaultFormat = TextureImporterFormat.ETC2_RGB4;
        private static TextureImporterFormat _iosDefaultFormat = TextureImporterFormat.ASTC_6x6;

        //设置公共属性,如是否开启mimap或可读写
        public static void SetPublicSettings(TextureImporter txImpt)
        {
            txImpt.textureType = TextureImporterType.Sprite;
            txImpt.maxTextureSize = _maxTextureSize;
            txImpt.mipmapEnabled = false;
            txImpt.isReadable = false;         
        } 

        /// <summary>
        /// 设置安卓平台设置属性
        /// </summary>
        /// <param name="txImpt"></param>
        public static void SetAndroidSettings(TextureImporter txImpt)
        {
            SetPublicSettings(txImpt);

            var androidSettings = new TextureImporterPlatformSettings();
            androidSettings.name = "Android";
            androidSettings.overridden = true;
            androidSettings.maxTextureSize = _maxTextureSize;
            if (txImpt.DoesSourceTextureHaveAlpha())//图片是否具有透明通道
                androidSettings.format = _androidAlphaDefaultFormat;
            else
                androidSettings.format = _androidNotAlphaDefaultFormat;
            txImpt.SetPlatformTextureSettings(androidSettings);
        }
        public static void SetAndroidSettings(string txFile)
        {
            var txImpt = AssetImporter.GetAtPath(txFile) as TextureImporter;
            SetAndroidSettings(txImpt);
        }
        /// <summary>
        /// 设置IOS平台设置属性
        /// </summary>
        /// <param name="txImpt"></param>        public static void SetIosSettings(TextureImporter txImpt)
        {
            SetPublicSettings(txImpt);

            var iosSetting = new TextureImporterPlatformSettings();
            iosSetting.name = "iPhone";
            iosSetting.overridden = true;
            iosSetting.maxTextureSize = _maxTextureSize;
            iosSetting.format = _iosDefaultFormat;
            txImpt.SetPlatformTextureSettings(iosSetting);
        }
       
        public static void SetIosSettings(string txFile)
        {
            var txImpt = AssetImporter.GetAtPath(txFile) as TextureImporter;
            SetIosSettings(txImpt);
        }       
    }

}

再写一个导入器,调用刚才的贴图管理器,如下:

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace EditorAsset
{
    public class AssetTextureImporter : AssetPostprocessor
    {     
        private void OnPreprocessTexture()
        {

            var txImpt = assetImporter as TextureImporter;

            if (!txImpt.assetPath.Contains("Assets/Res/Pictures"))
                return;

            Debug.Log($"-------Set texture platform settings {txImpt.assetPath} ");
         
            AssetTextureMgr.SetAndroidSettings(txImpt);
            AssetTextureMgr.SetIosSettings(txImpt);
          
        }

    }
}

这样我们只要工程的纹理贴图稍有变化,都会自动设置成各个平台的对应格式和其它属性,免去了手动设置的麻烦。为了保险,我们再加一个菜单功能,方便开发过程中随时随地可以批量的自动化设置纹理。代码如下:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

namespace EditorAsset
{
    public class AssetTextureAutomation
    {
        [MenuItem("Assets/刷新TextureSettings")]
        private static void RefreshTextureSettings()
        {
            var dir = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]);
            var files = AssetDir.GetAssetPath(dir, new string[] { ".png" }, null, null);
            foreach (var f in files)
            {
                AssetTextureMgr.SetAndroidSettings(f);
                AssetTextureMgr.SetIosSettings(f);
            }
            Debug.Log($"----------Fresh settings ok. count {files.Length}");
        }

        [MenuItem("Assets/刷新TextureSettings", true)]
        private static bool IsSelected1()
        {
            return Selection.assetGUIDs != null && Selection.assetGUIDs.Length > 0;
        }       
    }
}

       把上面代码放到Editor文件夹下面,这样只要右键点击某个文件夹,在弹出菜单中就可以设置该文件夹下所有的纹理格式和属性了。

       经过这一步操作,打包后的纹理大约可以缩减10%-15%左右。

       下一篇文章,我们会继续对纹理再进行一些缩减操作,如打图集,规定制作格式等。敬请期待。