特效处理

This commit is contained in:
2026-10-20 18:30:03 +08:00
parent 40bd235e67
commit bee2d42356
131 changed files with 837 additions and 456 deletions
@@ -0,0 +1,296 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.U2D;
using UnityEngine;
using UnityEngine.U2D;
/// <summary>
/// 预制体图集打包工具。
///
/// 【用途】
/// 选中若干 UI 预制体(如大厅散包的各子面板)→ 扫描它们引用的全部 Sprite →
/// 一键创建 SpriteAtlas 并把这些图合进去(自动按总像素估算合适的图集尺寸),
/// 避免散包后每张小图单独打包/加载,也避免多个包重复携带同一批图。
///
/// 【使用】
/// 1. Project 里选中一个或多个预制体(子面板 prefab)
/// 2. 菜单 Tools → 图集工具 → [扫描选中预制体]
/// 3. 检查图片列表(会自动排除 Unity 内置资源;可勾选排除大背景图)
/// 4. 设置输出目录/图集名/尺寸(默认自动计算)/ Padding
/// 5. 选择要加入的 Addressables 组(强烈建议:与使用这些图的 prefab 同组,
/// 否则图集不进组会被引用它的每个 bundle 各复制一份!)
/// 6. [创建图集] → 重建 Addressables 内容后生效
///
/// 【注意】
/// - 打进图集的图片无需改 TextureImporter(图集优先于单图导入设置)
/// - 单图超过图集最大尺寸时 SpriteAtlas 会自动分页(multi-atlas)
/// - 创建后记得 Build → New Build 重建内容
/// </summary>
public class SpriteAtlasBuilderWindow : EditorWindow
{
[MenuItem("Tools/图集工具")]
public static void Open()
{
var w = GetWindow<SpriteAtlasBuilderWindow>("图集工具");
w.minSize = new Vector2(520f, 560f);
}
private class SpriteInfo
{
public Sprite sprite;
public string path;
public int w, h;
public bool big; // 边长 ≥ 512 的大图(默认排除)
}
private readonly List<SpriteInfo> _sprites = new List<SpriteInfo>();
private Vector2 _scroll;
private string _outputDir = "Assets/MyGame/aa/UI/Atlases";
private string _atlasName = "UIAtlas";
private int _maxSizeIndex = 0;
private readonly string[] _maxSizeOptions = { "自动(按总像素)", "1024", "2048", "4096" };
private int _padding = 4;
private bool _excludeBig = true;
private string[] _groupNames = { "不加入(稍后手动)" };
private int _groupIndex = 0;
private void OnEnable() => RefreshGroups();
private void RefreshGroups()
{
var list = new List<string> { "不加入(稍后手动)" };
try
{
var s = AddressableAssetSettingsDefaultObject.Settings;
if (s != null)
list.AddRange(s.groups.Where(g => !g.ReadOnly).Select(g => g.Name));
}
catch { }
_groupNames = list.ToArray();
}
private void OnGUI()
{
EditorGUILayout.HelpBox(
"1. Project 里选中一个或多个 UI 预制体\n" +
"2. 点 [扫描选中预制体] 收集引用的 Sprite\n" +
"3. 创建图集时选择与这些预制体相同的 Addressables 组\n" +
"(图集不进组会被引用它的每个 bundle 复制一份)", MessageType.Info);
if (GUILayout.Button("扫描选中预制体", GUILayout.Height(32)))
Scan();
EditorGUILayout.Space(4);
// ---- 扫描结果 ----
if (_sprites.Count > 0)
{
var included = _sprites.Where(s => !(_excludeBig && s.big)).ToList();
long totalPixels = included.Sum(s => (long)s.w * s.h);
long bigCount = _sprites.Count(s => s.big);
int autoSize = CalcAutoSize(included, _padding);
EditorGUILayout.LabelField($"共 {_sprites.Count} 张图" +
(bigCount > 0 ? $"(其中 {bigCount} 张大图,见下方排除开关)" : ""), EditorStyles.boldLabel);
EditorGUILayout.LabelField($"合计像素: {totalPixels / 10000f:F1} 万 | 自动图集尺寸: {autoSize}");
_excludeBig = EditorGUILayout.ToggleLeft("排除大图(边长≥512,背景图等通常不进图集)", _excludeBig);
_scroll = EditorGUILayout.BeginScrollView(_scroll, GUILayout.MinHeight(180));
foreach (var s in _sprites)
{
var style = s.big ? new GUIStyle(EditorStyles.label) { normal = { textColor = Color.yellow } } : EditorStyles.label;
GUILayout.Label($"{s.w}×{s.h}{(s.big ? " [大图]" : "")} {s.path}", style);
}
EditorGUILayout.EndScrollView();
}
else
{
EditorGUILayout.HelpBox("尚未扫描(或选中对象里没有预制体)", MessageType.None);
}
EditorGUILayout.Space(8);
// ---- 设置 ----
EditorGUILayout.LabelField("图集设置", EditorStyles.boldLabel);
_outputDir = EditorGUILayout.TextField("输出目录", _outputDir);
_atlasName = EditorGUILayout.TextField("图集名称", _atlasName);
_maxSizeIndex = EditorGUILayout.Popup("最大尺寸", _maxSizeIndex, _maxSizeOptions);
_padding = EditorGUILayout.IntSlider("图集内边距(Padding)", _padding, 1, 16);
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("加入 Addressables 组(建议与预制体同组)", EditorStyles.boldLabel);
_groupIndex = EditorGUILayout.Popup("目标组", _groupIndex, _groupNames);
EditorGUILayout.Space(8);
using (new EditorGUI.DisabledScope(_sprites.Count == 0))
{
if (GUILayout.Button("创建图集", GUILayout.Height(36)))
Create();
}
}
/// <summary>扫描选中的预制体,收集全部 Sprite 引用(去重、排除引擎内置资源)</summary>
private void Scan()
{
_sprites.Clear();
var seen = new HashSet<Object>();
int prefabCount = 0;
foreach (var obj in Selection.objects)
{
string path = AssetDatabase.GetAssetPath(obj);
if (string.IsNullOrEmpty(path) || !path.EndsWith(".prefab")) continue;
var go = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (go == null) continue;
prefabCount++;
foreach (var comp in go.GetComponentsInChildren<Component>(true))
{
if (comp == null) continue;
var so = new SerializedObject(comp);
var prop = so.GetIterator();
while (prop.NextVisible(true))
{
if (prop.propertyType != SerializedPropertyType.ObjectReference) continue;
var sp = prop.objectReferenceValue as Sprite;
if (sp == null || !seen.Add(sp)) continue;
string p = AssetDatabase.GetAssetPath(sp);
if (string.IsNullOrEmpty(p) || p.StartsWith("Library/")) continue; // 引擎内置
_sprites.Add(new SpriteInfo
{
sprite = sp,
path = p,
w = Mathf.CeilToInt(sp.rect.width),
h = Mathf.CeilToInt(sp.rect.height),
big = sp.rect.width >= 512 || sp.rect.height >= 512
});
}
}
}
if (prefabCount == 0)
ShowNotification(new GUIContent("选中对象里没有预制体"));
else if (_sprites.Count == 0)
ShowNotification(new GUIContent($"扫描了 {prefabCount} 个预制体,未发现 Sprite 引用"));
else
{
// 默认图集名取第一个预制体名
if (_atlasName == "UIAtlas" || string.IsNullOrEmpty(_atlasName))
{
var first = AssetDatabase.GetAssetPath(Selection.objects[0]);
_atlasName = Path.GetFileNameWithoutExtension(first) + "Atlas";
}
ShowNotification(new GUIContent($"扫描 {prefabCount} 个预制体,发现 {_sprites.Count} 张图"));
}
}
/// <summary>按总像素估算图集边长(利用率 70%,向上取 2 的幂,范围 256~4096)</summary>
private static int CalcAutoSize(List<SpriteInfo> sprites, int padding)
{
long area = sprites.Sum(s => (long)(s.w + padding) * (s.h + padding));
double need = area / 0.7;
int side = Mathf.CeilToInt(Mathf.Sqrt((float)need));
int pow = 256;
while (pow < side && pow < 4096) pow <<= 1;
return Mathf.Clamp(pow, 256, 4096);
}
/// <summary>创建图集并(可选)加入 Addressables 组</summary>
private void Create()
{
var included = _sprites.Where(s => !(_excludeBig && s.big)).ToList();
if (included.Count == 0)
{
ShowNotification(new GUIContent("没有可打包的图片(全被排除了?)"));
return;
}
if (!AssetDatabase.IsValidFolder(_outputDir))
{
// 逐级创建目录
string cur = "Assets";
foreach (var seg in _outputDir.Substring("Assets/".Length).Split('/'))
{
string next = cur + "/" + seg;
if (!AssetDatabase.IsValidFolder(next))
AssetDatabase.CreateFolder(cur, seg);
cur = next;
}
}
string atlasPath = $"{_outputDir}/{_atlasName}.spriteatlas";
if (File.Exists(atlasPath) && !EditorUtility.DisplayDialog("覆盖确认",
$"已存在同名图集:\n{atlasPath}\n继续将覆盖重建。", "覆盖", "取消"))
return;
int maxSize = _maxSizeIndex == 0
? CalcAutoSize(included, _padding)
: int.Parse(_maxSizeOptions[_maxSizeIndex]);
// SpriteAtlas 不是 ScriptableObject(继承自 Object),直接 new 后 CreateAsset
var atlas = new SpriteAtlas();
atlas.SetIncludeInBuild(true);
// padding 通过序列化字段写入(m_EditorData.padding,该版本无公开编辑 API)
var so = new SerializedObject(atlas);
var padProp = so.FindProperty("m_EditorData.padding");
if (padProp != null)
{
padProp.intValue = _padding;
so.ApplyModifiedPropertiesWithoutUndo();
}
// Android / iOS:ASTC 6x6(移动端体积/质量均衡)
foreach (var platform in new[] { "Android", "iPhone" })
{
var ps = atlas.GetPlatformSettings(platform);
ps.overridden = true;
ps.maxTextureSize = maxSize;
ps.format = TextureImporterFormat.ASTC_6x6;
ps.compressionQuality = 50;
atlas.SetPlatformSettings(ps);
}
atlas.Add(included.Select(s => (Object)s.sprite).ToArray());
AssetDatabase.CreateAsset(atlas, atlasPath);
EditorUtility.SetDirty(atlas);
AssetDatabase.SaveAssets();
// 可选:加入 Addressables 组
string groupMsg = "";
if (_groupIndex > 0)
{
try
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
var group = settings.FindGroup(_groupNames[_groupIndex]);
if (group != null)
{
settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(atlasPath), group);
AssetDatabase.SaveAssets();
groupMsg = $"\n已加入组: {group.Name}";
}
}
catch (System.Exception e)
{
groupMsg = $"\n加入组失败(可手动添加): {e.Message}";
}
}
Debug.Log($"[图集工具] 创建完成: {atlasPath}\n图片 {included.Count} 张 | 最大尺寸 {maxSize} | Padding {_padding}{groupMsg}");
EditorUtility.DisplayDialog("图集工具",
$"创建完成!\n\n{atlasPath}\n图片 {included.Count} 张\n最大尺寸 {maxSize}\nPadding {_padding}{groupMsg}\n\n" +
"提醒:重建 Addressables 内容(Build → New Build)后生效。", "确定");
}
}