【Unity效率优化】资源管理系统Addressable Asset

1. Unity3D 效率优化:Addressable Asset 自动分配group

 

在之前的开发过程中一直在使用AB来加载资源,之前根据资源的目录来实时处理AB资源的tag,不用费什么心在tag的管理上。

在后来使用官方的Addressable Asset System的时候也希望它能够自动分配group来管理资源

下面我们来实现自动管理tag的功能

首先我们需要用到这个类---AssetPostprocessor

https://docs.unity3d.com/ScriptReference/AssetPostprocessor.htmldocs.unity3d.com

它可以在资源导入之前和之后对资源进行一些处理,其中有一个重点方法

public static void OnPostprocessAllAssets(string[] importedAsset, 
string[] deleteAsset, string[] movedAssets, string[] movedFromAssetPaths)

它提供了 导入,删除和移动的资源的路径

下面相关代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using System;
using System.Linq;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
using UnityEditor.Experimental.SceneManagement;
using System.Data;

namespace Qarth.Editor
{
    public class AddressableAssetAutoProcess : AssetPostprocessor
    {
        private static AddressableAssetSettings setting = AssetDatabase.LoadAssetAtPath<AddressableAssetSettings>("Assets/AddressableAssetsData/AddressableAssetSettings.asset");


        public static void OnPostprocessAllAssets(string[] importedAsset, string[] deleteAsset, string[] movedAssets, string[] movedFromAssetPaths)
        {
            ProcessImportedAssets(importedAsset);
            ProcessMovedAsset(movedAssets, movedFromAssetPaths);
        }

        private static void ProcessImportedAssets(string[] assetPath)
        {
            if (assetPath == null || assetPath.Length == 0)
            {
                return;
            }

            for (int i = 0; i < assetPath.Length; ++i)
            {
                if (CheckIsRes4Addresable(assetPath[i]))
                {
                    ProcessAssetGroup(assetPath[i]);
                }
            }
        }

        private static void ProcessMovedAsset(string[] movedAssets, string[] movedFromAssets)
        {

            if (movedAssets != null && movedAssets.Length > 0)
            {
                for (int i = 0; i < movedAssets.Length; ++i)
                {
                    ProcessAssetGroup(movedAssets[i], movedFromAssets[i]);
                }
            }
        }


        private static bool CheckIsRes4Addresable(string name)
        {
            if (name.StartsWith("Assets/") && name.Contains("/AddressableRes/"))
            {
                return true;
            }

            return false;
        }

        public static string AssetsPath2ABSPath(string assetsPath)
        {
            string assetRootPath = System.IO.Path.GetFullPath(Application.dataPath);
            assetRootPath = assetRootPath.Substring(0, assetRootPath.Length - 6) + assetsPath;
            return assetRootPath.Replace("\\", "/");
        }

        private static void ProcessAssetGroup(string assetPath)
        {
            AssetImporter ai = AssetImporter.GetAtPath(assetPath);
            if (ai == null)
            {
                Log.e("Not Find Asset:" + assetPath);
                return;
            }


            string fullPath = AssetsPath2ABSPath(assetPath);
            if (Directory.Exists(fullPath))
            {
                return;
            }

            string groupName = string.Empty;

            string dirName = Path.GetDirectoryName(assetPath);
            string assetBundleName = EditorUtils.AssetPath2ReltivePath(dirName).ToLower();
            assetBundleName = assetBundleName.Replace("addressableres/", "");

            if (assetPath.Contains("FolderMode"))
            {
                groupName = assetBundleName;
            }
            else
            {
                groupName = setting.DefaultGroup.name;
            }

            groupName = groupName.Replace("/", "-");
            var group = setting.FindGroup(groupName);
            if (group == null)
            {
                group = setting.CreateGroup(groupName, false, false, false, new List<AddressableAssetGroupSchema> { setting.DefaultGroup.Schemas[0] }, typeof(SchemaType));
            }

            if (group == null)
            {
                return;
            }

            var guid = AssetDatabase.AssetPathToGUID(assetPath);
            var entry = setting.CreateOrMoveEntry(guid, group);
            entry.SetAddress(PathHelper.FileNameWithoutSuffix(Path.GetFileName(assetPath)), true);
        }

        /// <summary>
        /// 处理移动
        /// </summary>
        /// <param name="assetPath"></param>
        /// <param name="moveFromPath"></param>
        private static void ProcessAssetGroup(string assetPath, string moveFromPath)
        {
            AssetImporter ai = AssetImporter.GetAtPath(assetPath);
            if (ai == null)
            {
                Log.e("Not Find Asset:" + assetPath);
                return;
            }

            string fullPath = EditorUtils.AssetsPath2ABSPath(assetPath);
            if (Directory.Exists(fullPath))
            {
                return;
            }

            if (CheckIsRes4Addresable(assetPath))//如果移动到了另一个资源文件夹
            {
                ProcessAssetGroup(assetPath);
            }
            else
            {
                var guid = AssetDatabase.AssetPathToGUID(assetPath);
                setting.RemoveAssetEntry(guid);
            }

            if (CheckIsRes4Addresable(moveFromPath))
            {
                //处理移动前的Group
                string removeFromGroupName = string.Empty;
                string dirName = Path.GetDirectoryName(moveFromPath);
                string assetBundleName = EditorUtils.AssetPath2ReltivePath(dirName).ToLower();
                assetBundleName = assetBundleName.Replace("addressableres/", "");

                if (moveFromPath.Contains("FolderMode"))
                {
                    removeFromGroupName = assetBundleName;
                }
                else
                {
                    removeFromGroupName = setting.DefaultGroup.name;
                }
                removeFromGroupName = removeFromGroupName.Replace("/", "-");
                var group = setting.FindGroup(removeFromGroupName);
                if (group != null)
                {
                    if (group.entries.Count == 0)
                    {
                        setting.RemoveGroup(group);
                    }

                }
            }
        }
    }
}

 

2. Unity3D 效率优化:资源导入预处理 AssetPostprocessor

 

运用资源导入可以规范项目中的资源设置,减少不需要的开销,为我们做一些通用的操作

Unity Now中讲解了一些优化方法,其中就谈到了几种资源的设置

Unite Now - (中文字幕)性能优化技巧(下)_哔哩哔哩 (゜-゜)つロ 干杯~-bilibiliwww.bilibili.com/video/BV1Bp4y1i7wK

首先通过这一个例子可以看到,通过减少Size和适当的压缩,可以减少资源体积,图片质量却几乎没有损失。以此可见资源的管理对性能的优化的重要性。

另外也给出了一些推荐设置

Texture设置

Mesh设置

Unity提供了AssetPostProcessor来对资源导入前后进行处理,下面我们来用他来处理我们的资源。

https://docs.unity3d.com/ScriptReference/AssetPostprocessor.htmldocs.unity3d.com

 

 

首先先说说我的大体思路,有关资源的导入设置用ScriptableObject来保存,这样不同的项目就可以运用不同的导入设置,而不用改代码来适应项目。

Texture

我们先看看Texture的相关设置

  • 一般情况下,如果不需要Read/Write的话 直接关闭他

  • Mipmap 会带来额外的存储 如果不需要的话也可以关闭

  • MaxSize需要降到最小的尺寸

  • 可以根据需要来选择质量

新建一个脚本ScriptableObject 来配置一些属性

[CreateAssetMenu(menuName="ScriptableObject/AssetPreprocessor/TexturePreprocessorConfig")]
public class TexturePreprocessorConfig : BasePreprocessorConfig<TexturePreprocessorConfig>
{
       [Header("Default Texture Settings")]
       [SerializeField] private int MaxTextureSize = 4096;
       [SerializeField] private bool EnableReadWrite = false;
       [SerializeField] private bool EnableMipmap = false;
       [SerializeField] private bool EnableMipMapStreaming = false;
       [SerializeField] private TextureImporterAlphaSource AlphaSource = TextureImporterAlphaSource.None;
       [SerializeField] private float NativeTextureSizeMultiplier = 1f;
       [Header("Filtering Settings")]
       [SerializeField] private bool ForceFilterMode;
       [SerializeField] private FilterMode FilterMode = FilterMode.Bilinear;
       [SerializeField] private int AnisoLevel = 1;

        public static int maxTextureSize => S.MaxTextureSize;
        public static bool enableReadWirte => S.EnableReadWrite;
        public static bool enableMipmap => S.EnableMipmap;
        public static bool enableMipmapStreaming => S.EnableMipMapStreaming;
        public static TextureImporterAlphaSource alphaSource => S.AlphaSource;
        public static float nativeTextureSizeMultiplier => S.NativeTextureSizeMultiplier;

        public static bool forceFilterMode => S.ForceFilterMode;
        public static FilterMode filterMode => S.FilterMode;
        public static int anisoLevel => S.AnisoLevel;

}

核心的处理脚本

public class TexturePostprocessor : AssetPostprocessor
{
        public void OnPreprocessTexture()
        {
            Debug.Log("OnPreProcessTexture=" + this.assetPath);
            TextureImporter importer = this.assetImporter as TextureImporter;
            if (importer == null) return;

            var nativeTextureSize = GetOriginalTextureSize(importer);
            var nativeSize = Mathf.NextPowerOfTwo(Mathf.Max(nativeTextureSize.width, nativeTextureSize.height));
            var maxTextureSize = TexturePreprocessorConfig.maxTextureSize;
            var multipliedNativeRes = Mathf.RoundToInt(nativeSize * TexturePreprocessorConfig.nativeTextureSizeMultiplier);
            var textureSize = Mathf.Min(multipliedNativeRes, maxTextureSize);

            importer.maxTextureSize=textureSize;

            importer.mipmapEnabled = TexturePreprocessorConfig.enableMipmap;
            importer.streamingMipmaps = TexturePreprocessorConfig.enableMipmapStreaming;

            if (TexturePreprocessorConfig.enableReadWirte && !importer.isReadable)
            {
                        Debug.Log("Enabling Read/Write." + importer.name);
                        importer.isReadable = true;
            }

            if (TexturePreprocessorConfig.forceFilterMode)
            {
                       importer.anisoLevel = TexturePreprocessorConfig.anisoLevel;
                       importer.filterMode = TexturePreprocessorConfig.filterMode;
            }

       }
}

NativeTextureSizeMultiplier可以在原先的大小的基础上进行统一的缩放,比如设置成0.9的话,就可以将原先530*530的图片从1024的maxSize降到512,而600*600的图片还是1024,这样就可以将一些合适的图片maxSize适当向下调。

另外还可以单独对各个平台进行设置

这样可以提前对移动端平台选择合适的压缩格式

另外还可以对文件名添加一些规则,比如UI用到的图片可以直接调成Sprite类型,这样就不用手动去调,以及其他的一些自定义规则,比如一些特定名字的图片不做预处理。

还可以设置滤波,默认双线好了

我的一些配置

Audio

下面在看看Audio的相关设置

再看一下Unity Now的提示

  • ForceToMono打勾 设置成单通道,减少内存

  • 为各个平台选择合适的质量和格式

  • 另外根据音频大小选择合适的加载方式

loadtype为我们可以暂时用音频长度来代替 ,选取合适的时间长度来区分就行了

另外为了得到音频的长度,需要使用 OnPostprocessAudio(AudioClip audioClip)而不是 OnPreprocessAudio()

那么现在的ScriptedObject就大概这样

 

Mesh

mesh相关的思路也大体差不多 ,也是列出一些基础属性放入到config中.

另外还可以设置多个config,为这些config设置不同的优先级,这样能够根据不同的规则以及优先级来管理资源.

以上就是我对资源预处理的一些简单用法,写的略有些仓促,如有不足,请多指正。