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设置不同的优先级,这样能够根据不同的规则以及优先级来管理资源.
以上就是我对资源预处理的一些简单用法,写的略有些仓促,如有不足,请多指正。