Unity编辑器扩展包体优化神器,图片压缩,批量生成图集/图集变体,动画压缩
功能介绍:
1. 压缩工具支持对图片原文件压缩(支持png/jpg),也支持使用Unity内置图片压缩批量对图片设置压缩参数。
2. 支持以文件夹或及其子文件夹为单位批量生成图集(SpriteAtlas), 支持同时生成图集变体(SpriteAtlas Variant),支持忽略像素宽高大于限定值的图片打进图集。
3. 批量给现有图集(SpriteAtlas)生成图集变体,生成图集变体后可以调整图集的缩放
4. 动画压缩,降低animation clip序列化文件的浮点型精度,保留较少的小数以降低文件大小。
工具预览:
工具完整代码参见开源框架GF_HybridCLR
图片无疑是游戏资源大户,无论数量还是文件大小占比都非常高。使用TinyPng等压缩工具压缩,近乎疯狂的压缩比,通常可以将图片文件大小降低70%左右。
对于Cocos2d-x时代的项目通常都会使用TinyPng进行图片压缩。
然而对于Unity来说,压缩图片虽然能大幅降低图片文件大小,但是最终打出的包(AssetBundle或Addressables)文件大小并不会明显降低,甚至会比压缩图片前还大。这是因为Unity针对不同平台都有对应的图片压缩模式,无论你再怎么压缩,Unity导入图片或打包时都会再次使用对应平台的压缩方式重新压缩图片,这就导致在图片分辨率不变的情况下,最终打包后的资源大小并不能有效降低。
但是。。。没错,还有但是,对于庞大的项目来说,合理压缩图片原文件可以有效降低工程大小,提高打开工程的加载速度等。然后通过工具的Unity内置图片压缩批量操作可以快速方便设置图片压缩参数。
AssetBundle提供了LZ4和LZMA两种压缩方式:
LZ4: 压缩/解压较快,压缩后的文件大。适用于在线压缩/解压、网络数据等需要频繁压缩/解压的情况。
LZMA: 压缩/解压较慢,压缩后的文件小。适用于对文件大小要求高,且不频繁压缩/解压的情况。
以上两种压缩再结合GF的额外压缩,又能进一步降低包体大小。当然,也需要根据需求平衡文件加载速度和文件资源大小的取舍。
模式一:图片原文件压缩模式
一,图片原文件压缩工具功能设计:
1. 压缩算法的选择:
tinypng在线压缩 pngquant和ImageSharp离线压缩:
tinypng压缩比极高,支持png/jpg/webp, 并且提供了包括.Net的多种编程语言API支持,适合做批处理。但是,tinypng需要上传图片到服务器,压缩完后还要下载压缩后的图片。图片较大较多时处理过程会巨慢。如果有离线压缩算法就完美了,离线压缩库使用的是pngquant和ImageSharp,都是开源压缩算法:
pngquant: 只支持png压缩,对png的压缩比接近tinypng;也可从官网可以下载命令行工具,支持windows和mac;
ImageSharp:C#实现,跨平台。对jpg的压缩比tinypng还要好。
Tinypng API : TinyPNG – API Reference
2. 添加需要压缩的文件/文件夹,并在列表中显示已经添加的文件/文件夹,支持添加/删除:
如上图,用户可以点击列表的" "号弹出Unity自带的资源选择界面(支持选择文件夹/图片文件),
但是Unity自带选择界面仅支持单选,所以还需要做个拖拽功能以支持批量添加。
3. 压缩设置项:
对于tinypng,需要注册序列号,每个序列号可以免费压缩500张。可以一次配置多个序列号,压缩时取首行序列号。
离线压缩:对于png格式,勾选离线压缩后使用pngquant本地压缩。
覆盖原图片:勾选后压缩后的图片直接覆盖原图。
压缩质量(仅对pngquant离线压缩有效):为区间数值(min, max),当压缩质量小于min时则不对该图片压缩,其实就是为了把图片控制在一定质量范围,不至于太糊。
快压等级:等级越高,压缩处理速度越快,但压缩比随之小幅降低。一般为了极致压缩比会把快压等级调到最低。
输出路径:压缩后的图片存放路径。
备份路径:点击备份会自动把当前选择的原图备份到指定目录,以便后续还原需求。
4. 功能:
功能按钮包含压缩、备份、还原、保存当前设置。
二,功能实现:
下载tinypng压缩库:可以在Visual Studio的NuGet中搜索下载tinypng库,然后把dll放入Unity工程。
下载pngquant命令行版(有Window,Mac版本),放入Unity工程。
1. tinypng在线压缩:
-
/// <summary>
-
/// 使用TinyPng在线压缩,支持png,jpg,webp
-
/// </summary>
-
private async Task<bool> CompressOnlineAsync(string imgFileName, string outputFileName)
-
{
-
if (string.IsNullOrWhiteSpace(TinifyAPI.Tinify.Key))
-
{
-
return false;
-
}
-
-
var srcImg = TinifyAPI.Tinify.FromFile(imgFileName);
-
await srcImg.ToFile(outputFileName);
-
return srcImg.IsCompletedSuccessfully;
-
}
2. pngquant和ImageSharp本地压缩:
-
/// <summary>
-
/// 使用ImageSharp压缩jpg图片
-
/// </summary>
-
/// <param name="imgFileName"></param>
-
/// <param name="outputFileName"></param>
-
/// <returns></returns>
-
private static bool CompressJpgOffline(string imgFileName, string outputFileName)
-
{
-
using (var img = SixLabors.ImageSharp.Image.Load(imgFileName))
-
{
-
var encoder = new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder()
-
{
-
Quality = (int)AppBuildSettings.Instance.CompressImgToolQualityLv
-
};
-
using (var outputStream = new FileStream(outputFileName, FileMode.Create))
-
{
-
img.Save(outputStream, encoder);
-
}
-
-
}
-
-
return true;
-
}
-
/// <summary>
-
/// 使用pngquant压缩png图片
-
/// </summary>
-
/// <param name="imgFileName"></param>
-
/// <param name="outputFileName"></param>
-
/// <returns></returns>
-
private static bool CompressPngOffline(string imgFileName, string outputFileName)
-
{
-
string pngquant = Path.Combine(Directory.GetParent(Application.dataPath).FullName, pngquantTool);
-
-
StringBuilder strBuilder = new StringBuilder();
-
strBuilder.AppendFormat(" --force --quality {0}-{1}", (int)AppBuildSettings.Instance.CompressImgToolQualityMinLv, (int)AppBuildSettings.Instance.CompressImgToolQualityLv);
-
strBuilder.AppendFormat(" --speed {0}", AppBuildSettings.Instance.CompressImgToolFastLv);
-
strBuilder.AppendFormat(" --output \"{0}\"", outputFileName);
-
strBuilder.AppendFormat(" -- \"{0}\"", imgFileName);
-
-
var proceInfo = new System.Diagnostics.ProcessStartInfo(pngquant, strBuilder.ToString());
-
proceInfo.CreateNoWindow = true;
-
proceInfo.UseShellExecute = false;
-
bool success;
-
using (var proce = System.Diagnostics.Process.Start(proceInfo))
-
{
-
proce.WaitForExit();
-
success = proce.ExitCode == 0;
-
if (!success)
-
{
-
Debug.LogWarningFormat("离线压缩图片:{0}失败,ExitCode:{1}", imgFileName, proce.ExitCode);
-
}
-
}
-
return success;
-
}
3. 弹出Unity编辑器内置资源选择窗口:
通过反射调用Unity编辑器内置选文件窗口,需要注意反射调用不支持重载函数,所以需要把参数填写完整才能成功调用:
-
public class EditorUtilityExtension
-
{
-
-
/// <summary>
-
/// 选择相对工程路径文件夹
-
/// </summary>
-
/// <param name="title">标题</param>
-
/// <param name="relativePath">默认打开的路径(相对路径)</param>
-
/// <returns></returns>
-
public static string OpenRelativeFolderPanel(string title, string relativePath)
-
{
-
var rootPath = Directory.GetParent(Application.dataPath).FullName;
-
var curFullPath = Path.Combine(rootPath, relativePath);
-
var selectPath = EditorUtility.OpenFolderPanel(title, curFullPath, curFullPath);
-
-
return string.IsNullOrWhiteSpace(selectPath) ? selectPath : Path.GetRelativePath(rootPath, selectPath);
-
}
-
-
/// <summary>
-
/// 打开UnityEditor内置文件选择界面
-
/// </summary>
-
/// <param name="assetTp"></param>
-
/// <param name="searchFilter"></param>
-
/// <param name="onObjectSelectorClosed"></param>
-
/// <param name="objectSelectorID"></param>
-
/// <returns></returns>
-
public static bool OpenAssetSelector(Type assetTp, string searchFilter = null, Action<UnityEngine.Object> onObjectSelectorClosed = null, int objectSelectorID = 0)
-
{
-
var objSelector = Utility.Assembly.GetType("UnityEditor.ObjectSelector");
-
var objSelectorInst = objSelector?.GetProperty("get", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)?.GetValue(objSelector);
-
if (objSelectorInst == null) return false;
-
-
var objSelectorInstTp = objSelectorInst.GetType();
-
var showFunc = objSelectorInstTp.GetMethod("Show", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new System.Type[] { typeof(UnityEngine.Object), typeof(Type), typeof(UnityEngine.Object), typeof(bool), typeof(List<int>), typeof(Action<UnityEngine.Object>), typeof(Action<UnityEngine.Object>) }, null);
-
if (showFunc == null) return false;
-
-
showFunc.Invoke(objSelectorInst, new object[] { null, assetTp, null, false, null, onObjectSelectorClosed, null });
-
if (!string.IsNullOrEmpty(searchFilter))
-
{
-
objSelectorInstTp.GetProperty("searchFilter", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(objSelectorInst, searchFilter);
-
}
-
-
objSelectorInstTp.GetField("objectSelectorID", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(objSelectorInst, objectSelectorID);
-
-
return true;
-
}
-
}
4. 拖拽批量添加功能:
-
private void DrawDropArea()
-
{
-
var dragRect = EditorGUILayout.BeginVertical("box");
-
{
-
GUILayout.FlexibleSpace();
-
EditorGUILayout.LabelField(dragAreaContent, centerLabelStyle);
-
if (dragRect.Contains(Event.current.mousePosition))
-
{
-
if (Event.current.type == EventType.DragUpdated)
-
{
-
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
-
}
-
else if (Event.current.type == EventType.DragExited)
-
{
-
if (DragAndDrop.objectReferences != null && DragAndDrop.objectReferences.Length > 0)
-
{
-
OnItemsDrop(DragAndDrop.objectReferences);
-
}
-
-
}
-
}
-
GUILayout.FlexibleSpace();
-
EditorGUILayout.EndVertical();
-
}
-
}
-
-
/// <summary>
-
/// 拖拽松手
-
/// </summary>
-
/// <param name="objectReferences"></param>
-
/// <exception cref="NotImplementedException"></exception>
-
private void OnItemsDrop(UnityEngine.Object[] objectReferences)
-
{
-
foreach (var item in objectReferences)
-
{
-
if (CheckItemType(item) == ItemType.NoSupport)
-
{
-
Debug.LogWarningFormat("添加失败! 不支持的文件格式:{0}", AssetDatabase.GetAssetPath(item));
-
continue;
-
}
-
AddItem(item);
-
}
-
}
模式二:Unity内置压缩批处理
1. 通过编辑器脚本批量修改图片TextureImporter属性:
通过下面代码可以获取上图红色区域的设置参数:
-
var texSetting = new TextureImporterSettings();
-
texImporter.ReadTextureSettings(texSetting);
通过下面代码可以获取针对各个平台的设置参数:
-
var texImporter = AssetImporter.GetAtPath(assetName) as TextureImporter;
-
var texPlatformSetting = texImporter.GetPlatformTextureSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
需要注意的是,不同平台支持的图片压缩方式(Format)不同,可以通过反射调用Unity内置API获取对应平台支持的所有Format类型以供下拉选择:
-
var getOptionsFunc = Utility.Assembly.GetType("UnityEditor.TextureImportValidFormats").GetMethod("GetPlatformTextureFormatValuesAndStrings", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
-
var paramsObjs = new object[] { TextureImporterType.Sprite, EditorUserBuildSettings.activeBuildTarget, null, null };
-
getOptionsFunc.Invoke(null, paramsObjs);
-
-
var formatValues = paramsObjs[2] as int[];
-
var formatDisplayOptions = paramsObjs[3] as string[];
-
-
-
...
-
//Format
-
EditorGUILayout.BeginHorizontal();
-
{
-
overrideFormat = EditorGUILayout.ToggleLeft("Format", overrideFormat, GUILayout.Width(150));
-
EditorGUI.BeginDisabledGroup(!overrideFormat);
-
{
-
compressPlatformSettings.format = (TextureImporterFormat)EditorGUILayout.IntPopup((int)compressPlatformSettings.format, formatDisplayOptions, formatValues);
-
EditorGUI.EndDisabledGroup();
-
}
-
EditorGUILayout.EndHorizontal();
-
}
2. 通过EditorUtility.FormatBytes(UnityEditor.TextureUtil.GetStorageMemorySizeLong(texture))方法可以获取到对应压缩格式的文件占用大小,这样就可以通过自动比对筛选出最合适的压缩格式。此方法不是公开方法,需要通过反射调用。
3. 编辑器代码判断贴图是否符合压缩格式要求
比如ETC2要求图片像素宽高必须是4的倍数,Crunch格式要求图片宽高必须为POT(即2的N次方),对于不支持的压缩的贴图Unity还给了贴心警告,压缩失败时压缩格式会回滚到默认的通用格式,会造成贴图大小不降反升。所以需要判断贴图是否压缩成功,如果失败了就设置一个相对通用的压缩格式。
遗憾的是在Unity开源代码中并没有找到直接获取是否压缩成功的方法,但是可以通过判断是否有警告字符以判断是否压缩成功:
-
/// <summary>
-
/// 检测贴图是否适用压缩格式
-
/// </summary>
-
/// <param name="texImporter"></param>
-
/// <param name="warning"></param>
-
/// <returns></returns>
-
bool CheckTexFormatValid(TextureImporter texImporter, out string warning)
-
{
-
var impWarningFunc = texImporter.GetType().GetMethod("GetImportWarnings", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
-
warning = impWarningFunc.Invoke(texImporter, null) as string;
-
return string.IsNullOrWhiteSpace(warning);
-
}
模式三:创建图集,图集变体
功能需求:
1. 根据用户选定的文件夹,支持以文件夹或文件夹及其子文件夹为单位批量创建图集(每个文件夹生成一个图集文件),并且支持忽略把像素宽/高大于限制大小的图片打进图集。
2. 创建AtlasVariant,AtlasVariant是用来按比例缩放SpriteAtlas的,用于资源大小优化。勾选AtlasVariant后,生成图集同时生成AtlasVariant。
3. 其他图集设置参数,同图集的Inspector设置面板。
需要注意的是SpriteAtlas目前有v1和v2两个版本,图集格式分别为spriteatlas和spriteatlasv2:
两个版本的图集创建方法不同, v1是SpriteAtlas,v2是SpriteAtlasAsset,可通过EditorSettings.spritePackerMode获取当前使用的图集版本。
使用编辑器代码创建图集(SpriteAtlas),支持v1和v2:
-
/// <summary>
-
/// 创建图集
-
/// </summary>
-
/// <param name="atlasFilePath"></param>
-
/// <param name="settings"></param>
-
/// <param name="objectsForPack"></param>
-
/// <param name="createAtlasVariant"></param>
-
/// <param name="atlasVariantScale"></param>
-
/// <returns></returns>
-
public static SpriteAtlas CreateAtlas(string atlasName, AtlasSettings settings, UnityEngine.Object[] objectsForPack, bool createAtlasVariant = false, float atlasVariantScale = 1f)
-
{
-
CreateEmptySpriteAtlas(atlasName);
-
SpriteAtlas result;
-
if (EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2)
-
{
-
var atlas = SpriteAtlasAsset.Load(atlasName);
-
atlas.SetIncludeInBuild(settings.includeInBuild ?? true);
-
atlas.Add(objectsForPack);
-
var packSettings = atlas.GetPackingSettings();
-
var texSettings = atlas.GetTextureSettings();
-
var platformSettings = atlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
atlas.SetPackingSettings(packSettings);
-
atlas.SetTextureSettings(texSettings);
-
atlas.SetPlatformSettings(platformSettings);
-
SpriteAtlasAsset.Save(atlas, atlasName);
-
-
result = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasName);
-
}
-
else
-
{
-
var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasName);
-
atlas.SetIncludeInBuild(settings.includeInBuild ?? true);
-
atlas.Add(objectsForPack);
-
var packSettings = atlas.GetPackingSettings();
-
var texSettings = atlas.GetTextureSettings();
-
var platformSettings = atlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
atlas.SetPackingSettings(packSettings);
-
atlas.SetTextureSettings(texSettings);
-
atlas.SetPlatformSettings(platformSettings);
-
result = atlas;
-
AssetDatabase.SaveAssets();
-
}
-
-
if (createAtlasVariant)
-
{
-
var atlasVarSets = new AtlasVariantSettings()
-
{
-
variantScale = atlasVariantScale,
-
readWrite = settings.readWrite,
-
mipMaps = settings.mipMaps,
-
sRGB = settings.sRGB,
-
filterMode = settings.filterMode,
-
texFormat = settings.texFormat,
-
compressQuality = settings.compressQuality
-
};
-
CreateAtlasVariant(result, atlasVarSets);
-
}
-
return result;
-
}
使用编辑器代码为指定图集创建图集变体:
-
/// <summary>
-
/// 根据图集对象生成图集变体
-
/// </summary>
-
/// <param name="atlas"></param>
-
/// <param name="settings"></param>
-
/// <returns></returns>
-
public static SpriteAtlas CreateAtlasVariant(SpriteAtlas atlasMaster, AtlasVariantSettings settings)
-
{
-
if (atlasMaster == null || atlasMaster.isVariant) return atlasMaster;
-
var atlasFileName = AssetDatabase.GetAssetPath(atlasMaster);
-
if (string.IsNullOrEmpty(atlasFileName))
-
{
-
Debug.LogError($"atlas '{atlasMaster.name}' is not a asset file.");
-
return null;
-
}
-
-
var atlasVariantName = UtilityBuiltin.ResPath.GetCombinePath(Path.GetDirectoryName(atlasFileName), $"{Path.GetFileNameWithoutExtension(atlasFileName)}_Variant{Path.GetExtension(atlasFileName)}");
-
-
SpriteAtlas varAtlas;
-
if (EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2)
-
{
-
var atlas = SpriteAtlasAsset.Load(atlasFileName);
-
atlas.SetIncludeInBuild(false);
-
var packSettings = atlas.GetPackingSettings();
-
var texSettings = atlas.GetTextureSettings();
-
var platformSettings = atlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
atlas.SetPackingSettings(packSettings);
-
atlas.SetTextureSettings(texSettings);
-
atlas.SetPlatformSettings(platformSettings);
-
SpriteAtlasAsset.Save(atlas, atlasFileName);
-
-
CreateEmptySpriteAtlas(atlasVariantName);
-
var tmpVarAtlas = SpriteAtlasAsset.Load(atlasVariantName);
-
tmpVarAtlas.SetIncludeInBuild(true);
-
tmpVarAtlas.SetIsVariant(true);
-
packSettings = tmpVarAtlas.GetPackingSettings();
-
texSettings = tmpVarAtlas.GetTextureSettings();
-
platformSettings = tmpVarAtlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
tmpVarAtlas.SetPackingSettings(packSettings);
-
tmpVarAtlas.SetTextureSettings(texSettings);
-
tmpVarAtlas.SetPlatformSettings(platformSettings);
-
tmpVarAtlas.SetMasterAtlas(atlasMaster);
-
tmpVarAtlas.SetVariantScale(settings.variantScale);
-
SpriteAtlasAsset.Save(tmpVarAtlas, atlasVariantName);
-
-
varAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasVariantName);
-
}
-
else
-
{
-
var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasFileName);
-
atlas.SetIncludeInBuild(false);
-
var packSettings = atlas.GetPackingSettings();
-
var texSettings = atlas.GetTextureSettings();
-
var platformSettings = atlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
atlas.SetPackingSettings(packSettings);
-
atlas.SetTextureSettings(texSettings);
-
atlas.SetPlatformSettings(platformSettings);
-
-
CreateEmptySpriteAtlas(atlasVariantName);
-
var tmpVarAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasVariantName);
-
tmpVarAtlas.SetIncludeInBuild(true);
-
tmpVarAtlas.SetIsVariant(true);
-
packSettings = tmpVarAtlas.GetPackingSettings();
-
texSettings = tmpVarAtlas.GetTextureSettings();
-
platformSettings = tmpVarAtlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
tmpVarAtlas.SetPackingSettings(packSettings);
-
tmpVarAtlas.SetTextureSettings(texSettings);
-
tmpVarAtlas.SetPlatformSettings(platformSettings);
-
tmpVarAtlas.SetMasterAtlas(atlasMaster);
-
tmpVarAtlas.SetVariantScale(settings.variantScale);
-
AssetDatabase.SaveAssets();
-
-
varAtlas = tmpVarAtlas;
-
}
-
-
return varAtlas;
-
}
模式四:Animation Clip动画文件大小优化
原理非常简单,动画文件的位置、旋转、缩放等数据以浮点型保存在动画文件,默认精度太高,保留了一大串小数点后的数字,实际上不需要精度过大,保留3位小数即可。降低浮点型精度可以降低动画文件大小以减少打包后包体大小。
我这里直接偷懒使用正则匹配动画文件里的小数并降低小数的精度(注意,此方式只适用于Asset Serialization位Force Text模式,不支持Force Binary,Unity工程默认是Force Text模式):
-
public static void OptimizeAnimationClips(List<string> list, int precision)
-
{
-
string pattern = $"(\\d \\.[\\d]{{{precision},}})";
-
-
int totalCount = list.Count;
-
int finishCount = 0;
-
foreach (var itmName in list)
-
{
-
if (File.GetAttributes(itmName) != FileAttributes.ReadOnly)
-
{
-
if (Path.GetExtension(itmName).ToLower().CompareTo(".anim") == 0)
-
{
-
finishCount ;
-
if (EditorUtility.DisplayCancelableProgressBar(string.Format("压缩浮点精度({0}/{1})", finishCount, totalCount), itmName, finishCount / (float)totalCount))
-
{
-
break;
-
}
-
var allTxt = File.ReadAllText(itmName);
-
// 将匹配到的浮点型数字替换为精确到3位小数的浮点型数字
-
string outputString = Regex.Replace(allTxt, pattern, match =>
-
float.Parse(match.Value).ToString($"F{precision}"));
-
File.WriteAllText(itmName, outputString);
-
Debug.LogFormat("----->压缩动画浮点精度:{0}", itmName);
-
}
-
}
-
}
-
EditorUtility.ClearProgressBar();
-
AssetDatabase.Refresh();
-
}
工具的功能代码:
-
using UnityEditor.U2D;
-
using UnityEditor;
-
using UnityEngine;
-
using UnityEngine.U2D;
-
using System.IO;
-
using System.Text;
-
using System.Threading.Tasks;
-
using TinifyAPI;
-
using SixLabors.ImageSharp.Processing;
-
using SixLabors.ImageSharp;
-
using GameFramework;
-
using System.Collections.Generic;
-
using System.Text.RegularExpressions;
-
-
namespace UGF.EditorTools
-
{
-
public class AtlasSettings : IReference
-
{
-
public bool? includeInBuild = null;
-
public bool? allowRotation = null;
-
public bool? tightPacking = null;
-
public bool? alphaDilation = null;
-
public int? padding = null;
-
public bool? readWrite = null;
-
public bool? mipMaps = null;
-
public bool? sRGB = null;
-
public FilterMode? filterMode = null;
-
public int? maxTexSize = null;
-
public TextureImporterFormat? texFormat = null;
-
public int? compressQuality = null;
-
public virtual void Clear()
-
{
-
includeInBuild = null;
-
allowRotation = null;
-
tightPacking = null;
-
alphaDilation = null;
-
padding = null;
-
readWrite = null;
-
mipMaps = null;
-
sRGB = null;
-
filterMode = null;
-
maxTexSize = null;
-
texFormat = null;
-
compressQuality = null;
-
}
-
}
-
public class AtlasVariantSettings : AtlasSettings
-
{
-
public float variantScale = 0.5f;
-
public override void Clear()
-
{
-
base.Clear();
-
variantScale = 0.5f;
-
}
-
public static AtlasVariantSettings CreateFrom(AtlasSettings atlasSettings, float scale = 1f)
-
{
-
var settings = ReferencePool.Acquire<AtlasVariantSettings>();
-
settings.includeInBuild = atlasSettings.includeInBuild;
-
settings.allowRotation = atlasSettings.allowRotation;
-
settings.tightPacking = atlasSettings.tightPacking;
-
settings.alphaDilation = atlasSettings.alphaDilation;
-
settings.padding = atlasSettings.padding;
-
settings.readWrite = atlasSettings.readWrite;
-
settings.mipMaps = atlasSettings.mipMaps;
-
settings.sRGB = atlasSettings.sRGB;
-
settings.filterMode = atlasSettings.filterMode;
-
settings.maxTexSize = atlasSettings.maxTexSize;
-
settings.texFormat = atlasSettings.texFormat;
-
settings.compressQuality = atlasSettings.compressQuality;
-
settings.variantScale = scale;
-
return settings;
-
}
-
}
-
public class CompressTool
-
{
-
-
const string pngquantTool = "Tools/CompressImageTools/pngquant_win/pngquant.exe";
-
-
const string pngquantTool = "Tools/CompressImageTools/pngquant_mac/pngquant";
-
-
/// <summary>
-
/// 使用TinyPng在线压缩,支持png,jpg,webp
-
/// </summary>
-
public static async Task<bool> CompressOnlineAsync(string imgFileName, string outputFileName, string tinypngKey)
-
{
-
if (string.IsNullOrWhiteSpace(tinypngKey))
-
{
-
return false;
-
}
-
Tinify.Key = tinypngKey;
-
var srcImg = TinifyAPI.Tinify.FromFile(imgFileName);
-
await srcImg.ToFile(outputFileName);
-
return srcImg.IsCompletedSuccessfully;
-
}
-
-
/// <summary>
-
/// 使用pngquant离线压缩,只支持png
-
/// </summary>
-
public static bool CompressImageOffline(string imgFileName, string outputFileName)
-
{
-
var fileExt = Path.GetExtension(imgFileName).ToLower();
-
switch (fileExt)
-
{
-
case ".png":
-
return CompressPngOffline(imgFileName, outputFileName);
-
case ".jpg":
-
return CompressJpgOffline(imgFileName, outputFileName);
-
}
-
return false;
-
}
-
/// <summary>
-
/// 按比例缩放图片尺寸
-
/// </summary>
-
/// <param name="imgFileName"></param>
-
/// <param name="outputFileName"></param>
-
/// <param name="scale"></param>
-
/// <returns></returns>
-
public static bool ResizeImage(string imgFileName, string outputFileName, float scale)
-
{
-
using (var img = SixLabors.ImageSharp.Image.Load(imgFileName))
-
{
-
int scaleWidth = (int)(img.Width * scale);
-
int scaleHeight = (int)(img.Height * scale);
-
img.Mutate(x => x.Resize(scaleWidth, scaleHeight));
-
img.Save(outputFileName);
-
}
-
return true;
-
}
-
/// <summary>
-
/// 设置图片尺寸
-
/// </summary>
-
/// <param name="imgFileName"></param>
-
/// <param name="outputFileName"></param>
-
/// <param name="width"></param>
-
/// <param name="height"></param>
-
/// <returns></returns>
-
public static bool ResizeImage(string imgFileName, string outputFileName, int width, int height)
-
{
-
using (var img = SixLabors.ImageSharp.Image.Load(imgFileName))
-
{
-
img.Mutate(x => x.Resize(width, height));
-
img.Save(outputFileName);
-
}
-
return true;
-
}
-
/// <summary>
-
/// 使用ImageSharp压缩jpg图片
-
/// </summary>
-
/// <param name="imgFileName"></param>
-
/// <param name="outputFileName"></param>
-
/// <returns></returns>
-
private static bool CompressJpgOffline(string imgFileName, string outputFileName)
-
{
-
using (var img = SixLabors.ImageSharp.Image.Load(imgFileName))
-
{
-
var encoder = new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder()
-
{
-
Quality = (int)EditorToolSettings.Instance.CompressImgToolQualityLv
-
};
-
using (var outputStream = new FileStream(outputFileName, FileMode.Create))
-
{
-
img.Save(outputStream, encoder);
-
}
-
-
}
-
-
return true;
-
}
-
/// <summary>
-
/// 使用pngquant压缩png图片
-
/// </summary>
-
/// <param name="imgFileName"></param>
-
/// <param name="outputFileName"></param>
-
/// <returns></returns>
-
private static bool CompressPngOffline(string imgFileName, string outputFileName)
-
{
-
string pngquant = Path.Combine(Directory.GetParent(Application.dataPath).FullName, pngquantTool);
-
-
StringBuilder strBuilder = new StringBuilder();
-
strBuilder.AppendFormat(" --force --quality {0}-{1}", (int)EditorToolSettings.Instance.CompressImgToolQualityMinLv, (int)EditorToolSettings.Instance.CompressImgToolQualityLv);
-
strBuilder.AppendFormat(" --speed {0}", EditorToolSettings.Instance.CompressImgToolFastLv);
-
strBuilder.AppendFormat(" --output \"{0}\"", outputFileName);
-
strBuilder.AppendFormat(" -- \"{0}\"", imgFileName);
-
-
var proceInfo = new System.Diagnostics.ProcessStartInfo(pngquant, strBuilder.ToString());
-
proceInfo.CreateNoWindow = true;
-
proceInfo.UseShellExecute = false;
-
bool success;
-
using (var proce = System.Diagnostics.Process.Start(proceInfo))
-
{
-
proce.WaitForExit();
-
success = proce.ExitCode == 0;
-
if (!success)
-
{
-
Debug.LogWarningFormat("离线压缩图片:{0}失败,ExitCode:{1}", imgFileName, proce.ExitCode);
-
}
-
}
-
return success;
-
}
-
/// <summary>
-
/// 创建图集
-
/// </summary>
-
/// <param name="atlasFilePath"></param>
-
/// <param name="settings"></param>
-
/// <param name="objectsForPack"></param>
-
/// <param name="createAtlasVariant"></param>
-
/// <param name="atlasVariantScale"></param>
-
/// <returns></returns>
-
public static SpriteAtlas CreateAtlas(string atlasName, AtlasSettings settings, UnityEngine.Object[] objectsForPack, bool createAtlasVariant = false, float atlasVariantScale = 1f)
-
{
-
CreateEmptySpriteAtlas(atlasName);
-
SpriteAtlas result;
-
if (EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2)
-
{
-
var atlas = SpriteAtlasAsset.Load(atlasName);
-
atlas.SetIncludeInBuild(settings.includeInBuild ?? true);
-
atlas.Add(objectsForPack);
-
var packSettings = atlas.GetPackingSettings();
-
var texSettings = atlas.GetTextureSettings();
-
var platformSettings = atlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
atlas.SetPackingSettings(packSettings);
-
atlas.SetTextureSettings(texSettings);
-
atlas.SetPlatformSettings(platformSettings);
-
SpriteAtlasAsset.Save(atlas, atlasName);
-
-
result = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasName);
-
}
-
else
-
{
-
var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasName);
-
atlas.SetIncludeInBuild(settings.includeInBuild ?? true);
-
atlas.Add(objectsForPack);
-
var packSettings = atlas.GetPackingSettings();
-
var texSettings = atlas.GetTextureSettings();
-
var platformSettings = atlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
atlas.SetPackingSettings(packSettings);
-
atlas.SetTextureSettings(texSettings);
-
atlas.SetPlatformSettings(platformSettings);
-
result = atlas;
-
AssetDatabase.SaveAssets();
-
}
-
-
if (createAtlasVariant)
-
{
-
var atlasVarSets = new AtlasVariantSettings()
-
{
-
variantScale = atlasVariantScale,
-
readWrite = settings.readWrite,
-
mipMaps = settings.mipMaps,
-
sRGB = settings.sRGB,
-
filterMode = settings.filterMode,
-
texFormat = settings.texFormat,
-
compressQuality = settings.compressQuality
-
};
-
CreateAtlasVariant(result, atlasVarSets);
-
}
-
return result;
-
}
-
private static void ModifySpriteAtlasSettings(AtlasSettings input, ref SpriteAtlasPackingSettings packSets, ref SpriteAtlasTextureSettings texSets, ref TextureImporterPlatformSettings platSets)
-
{
-
packSets.enableRotation = input.allowRotation ?? packSets.enableRotation;
-
packSets.enableTightPacking = input.tightPacking ?? packSets.enableTightPacking;
-
packSets.enableAlphaDilation = input.alphaDilation ?? packSets.enableAlphaDilation;
-
packSets.padding = input.padding ?? packSets.padding;
-
texSets.readable = input.readWrite ?? texSets.readable;
-
texSets.generateMipMaps = input.mipMaps ?? texSets.generateMipMaps;
-
texSets.sRGB = input.sRGB ?? texSets.sRGB;
-
texSets.filterMode = input.filterMode ?? texSets.filterMode;
-
platSets.overridden = null != input.maxTexSize || null != input.texFormat || null != input.compressQuality;
-
platSets.maxTextureSize = input.maxTexSize ?? platSets.maxTextureSize;
-
platSets.format = input.texFormat ?? platSets.format;
-
platSets.compressionQuality = input.compressQuality ?? platSets.compressionQuality;
-
}
-
/// <summary>
-
/// 根据文件夹名字返回一个图集名
-
/// </summary>
-
/// <param name="folder"></param>
-
/// <returns></returns>
-
public static string GetAtlasExtensionV1V2()
-
{
-
return EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2 ? ".spriteatlasv2" : ".spriteatlas";
-
}
-
public static void CreateEmptySpriteAtlas(string atlasAssetName)
-
{
-
if (EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2)
-
{
-
SpriteAtlasAsset.Save(new SpriteAtlasAsset(), atlasAssetName);
-
}
-
else
-
{
-
AssetDatabase.CreateAsset(new SpriteAtlas(), atlasAssetName);
-
}
-
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
-
}
-
/// <summary>
-
/// 根据图集对象生成图集变体
-
/// </summary>
-
/// <param name="atlas"></param>
-
/// <param name="settings"></param>
-
/// <returns></returns>
-
public static SpriteAtlas CreateAtlasVariant(SpriteAtlas atlasMaster, AtlasVariantSettings settings)
-
{
-
if (atlasMaster == null || atlasMaster.isVariant) return atlasMaster;
-
var atlasFileName = AssetDatabase.GetAssetPath(atlasMaster);
-
if (string.IsNullOrEmpty(atlasFileName))
-
{
-
Debug.LogError($"atlas '{atlasMaster.name}' is not a asset file.");
-
return null;
-
}
-
-
var atlasVariantName = UtilityBuiltin.ResPath.GetCombinePath(Path.GetDirectoryName(atlasFileName), $"{Path.GetFileNameWithoutExtension(atlasFileName)}_Variant{Path.GetExtension(atlasFileName)}");
-
-
SpriteAtlas varAtlas;
-
if (EditorSettings.spritePackerMode == SpritePackerMode.SpriteAtlasV2)
-
{
-
var atlas = SpriteAtlasAsset.Load(atlasFileName);
-
atlas.SetIncludeInBuild(false);
-
var packSettings = atlas.GetPackingSettings();
-
var texSettings = atlas.GetTextureSettings();
-
var platformSettings = atlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
atlas.SetPackingSettings(packSettings);
-
atlas.SetTextureSettings(texSettings);
-
atlas.SetPlatformSettings(platformSettings);
-
SpriteAtlasAsset.Save(atlas, atlasFileName);
-
-
CreateEmptySpriteAtlas(atlasVariantName);
-
var tmpVarAtlas = SpriteAtlasAsset.Load(atlasVariantName);
-
tmpVarAtlas.SetIncludeInBuild(true);
-
tmpVarAtlas.SetIsVariant(true);
-
packSettings = tmpVarAtlas.GetPackingSettings();
-
texSettings = tmpVarAtlas.GetTextureSettings();
-
platformSettings = tmpVarAtlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
tmpVarAtlas.SetPackingSettings(packSettings);
-
tmpVarAtlas.SetTextureSettings(texSettings);
-
tmpVarAtlas.SetPlatformSettings(platformSettings);
-
tmpVarAtlas.SetMasterAtlas(atlasMaster);
-
tmpVarAtlas.SetVariantScale(settings.variantScale);
-
SpriteAtlasAsset.Save(tmpVarAtlas, atlasVariantName);
-
-
varAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasVariantName);
-
}
-
else
-
{
-
var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasFileName);
-
atlas.SetIncludeInBuild(false);
-
var packSettings = atlas.GetPackingSettings();
-
var texSettings = atlas.GetTextureSettings();
-
var platformSettings = atlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
atlas.SetPackingSettings(packSettings);
-
atlas.SetTextureSettings(texSettings);
-
atlas.SetPlatformSettings(platformSettings);
-
-
CreateEmptySpriteAtlas(atlasVariantName);
-
var tmpVarAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasVariantName);
-
tmpVarAtlas.SetIncludeInBuild(true);
-
tmpVarAtlas.SetIsVariant(true);
-
packSettings = tmpVarAtlas.GetPackingSettings();
-
texSettings = tmpVarAtlas.GetTextureSettings();
-
platformSettings = tmpVarAtlas.GetPlatformSettings(EditorUserBuildSettings.activeBuildTarget.ToString());
-
ModifySpriteAtlasSettings(settings, ref packSettings, ref texSettings, ref platformSettings);
-
tmpVarAtlas.SetPackingSettings(packSettings);
-
tmpVarAtlas.SetTextureSettings(texSettings);
-
tmpVarAtlas.SetPlatformSettings(platformSettings);
-
tmpVarAtlas.SetMasterAtlas(atlasMaster);
-
tmpVarAtlas.SetVariantScale(settings.variantScale);
-
AssetDatabase.SaveAssets();
-
-
varAtlas = tmpVarAtlas;
-
}
-
-
return varAtlas;
-
}
-
/// <summary>
-
/// 根据Atlas文件名为Atlas生成Atlas变体(Atlas Variant)
-
/// </summary>
-
/// <param name="atlasFile"></param>
-
/// <param name="settings"></param>
-
/// <returns></returns>
-
public static SpriteAtlas CreateAtlasVariant(string atlasFile, AtlasVariantSettings settings)
-
{
-
var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasFile);
-
-
return CreateAtlasVariant(atlas, settings);
-
}
-
-
/// <summary>
-
/// 批量重新打包图集
-
/// </summary>
-
/// <param name="spriteAtlas"></param>
-
public static void PackAtlases(SpriteAtlas[] spriteAtlas)
-
{
-
SpriteAtlasUtility.PackAtlases(spriteAtlas, EditorUserBuildSettings.activeBuildTarget);
-
}
-
-
public static void OptimizeAnimationClips(List<string> list, int precision)
-
{
-
string pattern = $"(\\d \\.[\\d]{{{precision},}})";
-
-
int totalCount = list.Count;
-
int finishCount = 0;
-
foreach (var itmName in list)
-
{
-
if (File.GetAttributes(itmName) != FileAttributes.ReadOnly)
-
{
-
if (Path.GetExtension(itmName).ToLower().CompareTo(".anim") == 0)
-
{
-
finishCount ;
-
if (EditorUtility.DisplayCancelableProgressBar(string.Format("压缩浮点精度({0}/{1})", finishCount, totalCount), itmName, finishCount / (float)totalCount))
-
{
-
break;
-
}
-
var allTxt = File.ReadAllText(itmName);
-
// 将匹配到的浮点型数字替换为精确到3位小数的浮点型数字
-
string outputString = Regex.Replace(allTxt, pattern, match =>
-
float.Parse(match.Value).ToString($"F{precision}"));
-
File.WriteAllText(itmName, outputString);
-
Debug.LogFormat("----->压缩动画浮点精度:{0}", itmName);
-
}
-
}
-
}
-
EditorUtility.ClearProgressBar();
-
AssetDatabase.Refresh();
-
}
-
}
-
}
-
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhfhbgig
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
excel下划线不显示怎么办
PHP中文网 06-23 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
photoshop蒙版画笔没反应怎么办
PHP中文网 06-24