特效处理
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
|
||||
/// <summary>
|
||||
/// 图片 maxTextureSize 批量设置工具(按原图尺寸智能降分辨率)。
|
||||
///
|
||||
/// 【用途】
|
||||
/// UI 图片常超采样(2048 的图实际显示只需 1200 左右),批量把 TextureImporter 的
|
||||
/// maxTextureSize 降到合适档位,减小包体与内存。规则:
|
||||
/// 目标缩放比例控制在原图的 60%~80%:
|
||||
/// - 在 [60%, 80%] 区间内取 2 的幂档位(32/64/.../4096),取区间内满足 ≥60% 的最小档位(最省内存);
|
||||
/// - 区间内没有档位时,取 ≥60% 的最小档位(硬保清晰度下限,允许略超 80%);
|
||||
/// - 档位 ≥ 原图边长(缩放后无变化)→ 不修改。
|
||||
///
|
||||
/// 【使用】
|
||||
/// 1. Project 选中一个/多个文件夹或图片(文件夹会递归)
|
||||
/// 2. 菜单 Tools → 图片尺寸工具 → [扫描选中对象]
|
||||
/// 3. 检查列表(新 maxSize、实际缩放比、预计内存节省),可逐行勾选
|
||||
/// 4. [应用勾选项] 批量写入 TextureImporter 并重导入
|
||||
///
|
||||
/// 【注意】
|
||||
/// - 已被 SpriteAtlas 收录的图会标注并默认跳过(图集的平台设置优先,单图 maxSize 无效)
|
||||
/// - 只处理 TextureImporter(Sprite / Default / GUI 类型),NormalMap 等自动跳过
|
||||
/// </summary>
|
||||
public class TextureMaxSizeWindow : EditorWindow
|
||||
{
|
||||
[MenuItem("Tools/图片尺寸工具")]
|
||||
public static void Open()
|
||||
{
|
||||
var w = GetWindow<TextureMaxSizeWindow>("图片尺寸工具");
|
||||
w.minSize = new Vector2(640f, 560f);
|
||||
}
|
||||
|
||||
private class ItemInfo
|
||||
{
|
||||
public string path;
|
||||
public int w, h;
|
||||
public int curMax;
|
||||
public int newMax;
|
||||
public float scale; // 实际缩放比例(1=不缩)
|
||||
public bool inAtlas; // 已被 SpriteAtlas 收录
|
||||
public bool selected = true;
|
||||
public long savedBytes; // 预计内存节省(RGBA32 估算)
|
||||
}
|
||||
|
||||
private readonly List<ItemInfo> _items = new List<ItemInfo>();
|
||||
private HashSet<string> _atlasPaths; // 所有图集收录的资源路径
|
||||
private Vector2 _scroll;
|
||||
private bool _skipSmall = true; // 跳过边长≤128 的小图
|
||||
private bool _skipInAtlas = true; // 跳过已被图集收录的
|
||||
private bool _onlyShrunk = true; // 只显示会实际缩小的
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"按原图尺寸智能设置 maxTextureSize:缩放控制在原图的 60%~80%,\n" +
|
||||
"取区间内 ≥60% 的最小 2 的幂档位(区间无档位时取 ≥60% 最小档位,保清晰度优先)。\n" +
|
||||
"选中 Project 里的文件夹或图片后点扫描。", MessageType.Info);
|
||||
|
||||
if (GUILayout.Button("扫描选中对象(文件夹/图片)", GUILayout.Height(30)))
|
||||
Scan();
|
||||
|
||||
_skipSmall = EditorGUILayout.ToggleLeft("跳过小图(最长边 ≤ 128)", _skipSmall);
|
||||
_skipInAtlas = EditorGUILayout.ToggleLeft("跳过已被 SpriteAtlas 收录的图(图集设置优先)", _skipInAtlas);
|
||||
_onlyShrunk = EditorGUILayout.ToggleLeft("只显示会实际缩小的图", _onlyShrunk);
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
var visible = VisibleItems().ToList();
|
||||
if (visible.Count > 0)
|
||||
{
|
||||
long totalSaved = visible.Where(i => i.selected).Sum(i => i.savedBytes);
|
||||
EditorGUILayout.LabelField(
|
||||
$"共 {visible.Count} 项 | 勾选 {visible.Count(i => i.selected)} 项 | 预计节省内存 ~{totalSaved / 1048576f:F1} MB(RGBA32 估算)",
|
||||
EditorStyles.boldLabel);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("全选")) visible.ForEach(i => i.selected = true);
|
||||
if (GUILayout.Button("全不选")) visible.ForEach(i => i.selected = false);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
_scroll = EditorGUILayout.BeginScrollView(_scroll, GUILayout.MinHeight(220));
|
||||
foreach (var it in visible)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
it.selected = EditorGUILayout.Toggle(it.selected, GUILayout.Width(20));
|
||||
string tag = it.inAtlas ? "[图集内]" : "";
|
||||
string change = it.newMax != it.curMax ? $"{it.curMax} → {it.newMax}" : $"{it.curMax}(已是目标)";
|
||||
GUILayout.Label($"{it.w}×{it.h} max: {change} 缩至 {it.scale:P0} {tag}", GUILayout.Width(330));
|
||||
GUILayout.Label(it.path, EditorStyles.miniLabel);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
using (new EditorGUI.DisabledScope(!visible.Any(i => i.selected)))
|
||||
{
|
||||
if (GUILayout.Button("应用勾选项", GUILayout.Height(34)))
|
||||
Apply(visible.Where(i => i.selected).ToList());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("尚未扫描,或按当前过滤条件没有需要处理的图片", MessageType.None);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<ItemInfo> VisibleItems()
|
||||
{
|
||||
foreach (var it in _items)
|
||||
{
|
||||
if (_skipSmall && Mathf.Max(it.w, it.h) <= 128) continue;
|
||||
if (_skipInAtlas && it.inAtlas) continue;
|
||||
if (_onlyShrunk && it.newMax >= Mathf.Max(it.w, it.h)) continue;
|
||||
yield return it;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>扫描选中对象(文件夹递归 / 单张图片),收集贴图并计算目标 maxSize</summary>
|
||||
private void Scan()
|
||||
{
|
||||
_items.Clear();
|
||||
_atlasPaths = CollectAtlasPaths();
|
||||
var paths = new HashSet<string>();
|
||||
|
||||
foreach (var obj in Selection.objects)
|
||||
{
|
||||
string p = AssetDatabase.GetAssetPath(obj);
|
||||
if (string.IsNullOrEmpty(p)) continue;
|
||||
if (Directory.Exists(p))
|
||||
{
|
||||
foreach (var guid in AssetDatabase.FindAssets("t:Texture2D", new[] { p }))
|
||||
paths.Add(AssetDatabase.GUIDToAssetPath(guid));
|
||||
}
|
||||
else if (p.EndsWith(".png") || p.EndsWith(".jpg") || p.EndsWith(".jpeg") || p.EndsWith(".tga"))
|
||||
{
|
||||
paths.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var path in paths.OrderBy(p => p))
|
||||
{
|
||||
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
|
||||
if (importer == null) continue;
|
||||
// 只处理常规贴图类型(NormalMap 等跳过)
|
||||
if (importer.textureType == TextureImporterType.NormalMap) continue;
|
||||
|
||||
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
|
||||
if (tex == null) continue;
|
||||
|
||||
int side = Mathf.Max(tex.width, tex.height);
|
||||
int newMax = ComputeMaxSize(side);
|
||||
|
||||
// maxSize 只在小于原边长时才实际缩放
|
||||
float scale = newMax < side ? (float)newMax / side : 1f;
|
||||
long cur = (long)tex.width * tex.height * 4;
|
||||
long after = (long)(tex.width * scale) * (long)(tex.height * scale) * 4;
|
||||
|
||||
_items.Add(new ItemInfo
|
||||
{
|
||||
path = path,
|
||||
w = tex.width,
|
||||
h = tex.height,
|
||||
curMax = importer.maxTextureSize,
|
||||
newMax = newMax,
|
||||
scale = scale,
|
||||
inAtlas = _atlasPaths.Contains(path),
|
||||
savedBytes = cur - after
|
||||
});
|
||||
}
|
||||
|
||||
ShowNotification(new GUIContent($"扫描到 {_items.Count} 张贴图"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算目标 maxTextureSize:
|
||||
/// 取 ≥ 原边长 60% 的最小 2 的幂档位(32~4096)。
|
||||
/// 若该档位落在 [60%,80%] 区间内则同时满足上限约束;
|
||||
/// 区间内无档位时允许略超 80%(清晰度 60% 是硬下限)。
|
||||
/// </summary>
|
||||
private static int ComputeMaxSize(int side)
|
||||
{
|
||||
int lower = Mathf.CeilToInt(side * 0.6f);
|
||||
int pow = 32;
|
||||
while (pow < lower && pow < 4096) pow <<= 1;
|
||||
return Mathf.Clamp(pow, 32, 4096);
|
||||
}
|
||||
|
||||
/// <summary>收集所有 SpriteAtlas(含其 packable 文件夹展开)覆盖的资源路径。
|
||||
/// 该版本 SpriteAtlas 的 packables 无公开读取 API,用 SerializedObject 读 m_EditorData.packables。</summary>
|
||||
private static HashSet<string> CollectAtlasPaths()
|
||||
{
|
||||
var set = new HashSet<string>();
|
||||
foreach (var guid in AssetDatabase.FindAssets("t:SpriteAtlas"))
|
||||
{
|
||||
var atlasPath = AssetDatabase.GUIDToAssetPath(guid);
|
||||
var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasPath);
|
||||
if (atlas == null) continue;
|
||||
|
||||
var so = new SerializedObject(atlas);
|
||||
var packables = so.FindProperty("m_EditorData.packables");
|
||||
if (packables == null || !packables.isArray) continue;
|
||||
|
||||
for (int i = 0; i < packables.arraySize; i++)
|
||||
{
|
||||
var packable = packables.GetArrayElementAtIndex(i).objectReferenceValue;
|
||||
if (packable == null) continue;
|
||||
string p = AssetDatabase.GetAssetPath(packable);
|
||||
if (string.IsNullOrEmpty(p)) continue;
|
||||
if (Directory.Exists(p))
|
||||
{
|
||||
foreach (var g in AssetDatabase.FindAssets("t:Texture2D", new[] { p }))
|
||||
set.Add(AssetDatabase.GUIDToAssetPath(g));
|
||||
}
|
||||
else
|
||||
set.Add(p);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/// <summary>批量写入 maxTextureSize 并重导入</summary>
|
||||
private void Apply(List<ItemInfo> targets)
|
||||
{
|
||||
try
|
||||
{
|
||||
int done = 0;
|
||||
foreach (var it in targets)
|
||||
{
|
||||
EditorUtility.DisplayProgressBar("应用 maxSize", it.path, (float)done / targets.Count);
|
||||
var importer = AssetImporter.GetAtPath(it.path) as TextureImporter;
|
||||
if (importer != null && importer.maxTextureSize != it.newMax)
|
||||
{
|
||||
importer.maxTextureSize = it.newMax;
|
||||
importer.SaveAndReimport();
|
||||
}
|
||||
done++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
Debug.Log($"[图片尺寸工具] 已应用 {targets.Count} 项 maxTextureSize");
|
||||
// 重新扫描刷新列表(当前值已变化)
|
||||
Scan();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user