killapp/Assets/Editor/iOSIconPostProcessor.cs
2026-07-07 15:50:34 +08:00

172 lines
6.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#if UNITY_IOS
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using System.IO;
using System.Text;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// iOS 出包后自动替换高清 App 图标,解决 Unity 压缩导致图标模糊的问题。
/// 使用 IPostprocessBuildWithReport 确保在所有 PostProcessBuild 之后执行。
/// 使用方式:将精确尺寸的 PNG 图标放入 Assets/Icons/iOS/ 目录下,打包时自动生效。
/// 命名规则iPhone-App-180.png设备-用途-像素尺寸)可被 Xcode Asset Catalog 自动识别。
/// </summary>
public class iOSIconPostProcessor : IPostprocessBuildWithReport
{
public int callbackOrder => int.MaxValue;
private const string SOURCE_ICON_DIR = "Assets/Icons/iOS/";
// 图标配置: (idiom, scale, 点尺寸, 源文件名, 目标文件名)
// 源文件在 Assets/Icons/iOS/ 下,目标文件写入 Xcode 的 AppIcon.appiconset
private static readonly (string idiom, string scale, string size, string src, string dst)[] ICON_CONFIGS =
{
// --- iPhone ---
("iphone", "2x", "20x20", "iPhone-Notification-40.png", "Icon-iPhone-Notification-40.png"),
("iphone", "3x", "20x20", "iPhone-Notification-60.png", "Icon-iPhone-Notification-60.png"),
("iphone", "2x", "29x29", "iPhone-Settings-58.png", "Icon-iPhone-Settings-58.png"),
("iphone", "3x", "29x29", "iPhone-Settings-87.png", "Icon-iPhone-Settings-87.png"),
("iphone", "2x", "40x40", "iPhone-Spotlight-80.png", "Icon-iPhone-Spotlight-80.png"),
("iphone", "3x", "40x40", "iPhone-Spotlight-120.png", "Icon-iPhone-Spotlight-120.png"),
("iphone", "2x", "60x60", "iPhone-App-120.png", "Icon-iPhone-App-120.png"),
("iphone", "3x", "60x60", "iPhone-App-180.png", "Icon-iPhone-App-180.png"),
// --- iPad复用 iPhone 同名图标作为回退) ---
("ipad", "2x", "20x20", "iPhone-Notification-40.png", "Icon-iPad-Notification-40.png"),
("ipad", "2x", "29x29", "iPhone-Settings-58.png", "Icon-iPad-Settings-58.png"),
("ipad", "2x", "40x40", "iPhone-Spotlight-80.png", "Icon-iPad-Spotlight-80.png"),
("ipad", "2x", "76x76", "iPhone-Spotlight-80.png", "Icon-iPad-App-76.png"),
("ipad", "2x", "83.5x83.5", "iPhone-App-120.png", "Icon-iPad-Pro-App-167.png"),
// --- App Store ---
("ios-marketing", "1x", "1024x1024", "AppStore-1024.png", "Icon-AppStore-1024.png"),
};
public void OnPostprocessBuild(BuildReport report)
{
if (report.summary.platform != BuildTarget.iOS) return;
string pathToBuiltProject = report.summary.outputPath;
string iconSetPath = FindAppIconPath(pathToBuiltProject);
if (string.IsNullOrEmpty(iconSetPath))
{
Debug.LogWarning($"[iOSIconPostProcessor] 未找到 AppIcon.appiconset 目录,跳过图标替换");
return;
}
EditorApplication.delayCall += () => ReplaceIcons(iconSetPath);
}
private static void ReplaceIcons(string iconSetPath)
{
if (!Directory.Exists(iconSetPath))
{
Debug.LogWarning($"[iOSIconPostProcessor] AppIcon.appiconset 目录已不存在: {iconSetPath}");
return;
}
string projectRoot = Path.GetDirectoryName(Application.dataPath);
string sourceDir = Path.Combine(projectRoot, SOURCE_ICON_DIR);
// 清空旧图标
foreach (string oldFile in Directory.GetFiles(iconSetPath, "*.png"))
{
File.Delete(oldFile);
}
Debug.Log($"[iOSIconPostProcessor] 已清空旧图标");
// 复制新图标(源文件 → 目标 Xcode 标准名称)
var copiedDstFiles = new HashSet<string>();
foreach (var cfg in ICON_CONFIGS)
{
string src = Path.Combine(sourceDir, cfg.src);
if (File.Exists(src))
{
string dst = Path.Combine(iconSetPath, cfg.dst);
File.Copy(src, dst, overwrite: true);
copiedDstFiles.Add(cfg.dst);
}
else
{
Debug.LogWarning($"[iOSIconPostProcessor] 缺少源图标: {cfg.src}");
}
}
if (copiedDstFiles.Count > 0)
{
WriteContentsJson(iconSetPath, copiedDstFiles);
Debug.Log($"[iOSIconPostProcessor] 已替换 {copiedDstFiles.Count} 个 App 图标");
}
else
{
Debug.LogError("[iOSIconPostProcessor] 未找到任何可用图标");
}
}
private static string FindAppIconPath(string buildPath)
{
string[] candidates =
{
Path.Combine(buildPath, "Unity-iPhone/Images.xcassets/AppIcon.appiconset"),
Path.Combine(buildPath, "Images.xcassets/AppIcon.appiconset"),
Path.Combine(buildPath, "Unity-iPhone/Unity-iPhone/Images.xcassets/AppIcon.appiconset"),
};
foreach (string path in candidates)
{
if (Directory.Exists(path))
{
Debug.Log($"[iOSIconPostProcessor] 找到 AppIcon 目录: {path}");
return path;
}
}
string searchRoot = Path.Combine(buildPath, "Unity-iPhone");
if (!Directory.Exists(searchRoot)) searchRoot = buildPath;
foreach (string dir in Directory.GetDirectories(searchRoot, "AppIcon.appiconset", SearchOption.AllDirectories))
{
Debug.Log($"[iOSIconPostProcessor] 搜索找到 AppIcon 目录: {dir}");
return dir;
}
return null;
}
private static void WriteContentsJson(string iconSetPath, HashSet<string> dstFiles)
{
var sb = new StringBuilder();
sb.AppendLine("{");
sb.AppendLine(" \"images\" : [");
bool first = true;
foreach (var cfg in ICON_CONFIGS)
{
if (!dstFiles.Contains(cfg.dst))
continue;
if (!first) sb.AppendLine(",");
else sb.AppendLine();
first = false;
sb.Append($" {{\"idiom\" : \"{cfg.idiom}\", ");
sb.Append($"\"scale\" : \"{cfg.scale}\", ");
sb.Append($"\"size\" : \"{cfg.size}\", ");
sb.Append($"\"filename\" : \"{cfg.dst}\"}}");
}
sb.AppendLine();
sb.AppendLine(" ],");
sb.AppendLine(" \"info\" : {");
sb.AppendLine(" \"version\" : 1,");
sb.AppendLine(" \"author\" : \"xcode\"");
sb.AppendLine(" }");
sb.AppendLine("}");
string jsonPath = Path.Combine(iconSetPath, "Contents.json");
File.WriteAllText(jsonPath, sb.ToString());
}
}
#endif