ios优化

This commit is contained in:
yuqucheng 2026-07-07 15:50:34 +08:00
parent 718b91c5a9
commit 753cadb4a1
21 changed files with 176 additions and 57 deletions

View File

@ -31,19 +31,45 @@ public class iOSFixDuplicateShellScript
string content = File.ReadAllText(projPath); string content = File.ReadAllText(projPath);
string pattern = @"(\t+([A-F0-9]+) /\* ShellScript \*/,)\r?\n\t+\2 /\* ShellScript \*,/"; // 1. 移除连续重复的 ShellScript 行
string newContent = Regex.Replace(content, pattern, "$1"); string pattern1 = @"\t+[A-F0-9]+ /\* ShellScript \*/,(\r?\n\t+[A-F0-9]+ /\* ShellScript \*,)+";
content = Regex.Replace(content, pattern1, m =>
if (newContent != content)
{ {
File.WriteAllText(projPath, newContent); Debug.Log($"[PostProcess] 移除连续重复 ShellScript 行: {m.Value.TrimEnd().Length} chars");
Debug.Log("[PostProcess] Removed duplicate ShellScript from GameAssembly target"); return m.Groups[0].Value.Split('\n')[0]; // 只保留第一行
});
// 2. 移除整个文件中非连续的重复 ShellScript按 ID 去重)
var lines = content.Split('\n');
var seenShellScriptIds = new System.Collections.Generic.HashSet<string>();
var outputLines = new System.Collections.Generic.List<string>();
int removedCount = 0;
foreach (string line in lines)
{
var match = Regex.Match(line, @"^\t+([A-F0-9]+) /\* ShellScript \*/,?$");
if (match.Success)
{
string id = match.Groups[1].Value;
if (!seenShellScriptIds.Add(id))
{
removedCount++;
continue; // 跳过重复
}
}
outputLines.Add(line);
} }
// 修复光子矩阵 app 大小写 if (removedCount > 0)
newContent = newContent.Replace("photonmatrix.app", "PhotonMatrix.app"); {
content = string.Join("\n", outputLines);
Debug.Log($"[PostProcess] 按 ID 去重移除了 {removedCount} 个 ShellScript 条目");
}
File.WriteAllText(projPath, newContent); // 3. 修复光子矩阵 app 大小写
content = content.Replace("photonmatrix.app", "PhotonMatrix.app");
File.WriteAllText(projPath, content);
} }
private static void FixPodfile(string pathToBuiltProject) private static void FixPodfile(string pathToBuiltProject)

View File

@ -1,91 +1,162 @@
#if UNITY_IOS #if UNITY_IOS
using UnityEditor; using UnityEditor;
using UnityEditor.Callbacks; using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Collections.Generic;
using UnityEngine;
/// <summary> /// <summary>
/// iOS 出包后自动替换高清 App 图标,解决 Unity 压缩导致图标模糊的问题。 /// iOS 出包后自动替换高清 App 图标,解决 Unity 压缩导致图标模糊的问题。
/// 使用 IPostprocessBuildWithReport 确保在所有 PostProcessBuild 之后执行。
/// 使用方式:将精确尺寸的 PNG 图标放入 Assets/Icons/iOS/ 目录下,打包时自动生效。 /// 使用方式:将精确尺寸的 PNG 图标放入 Assets/Icons/iOS/ 目录下,打包时自动生效。
/// 命名规则iPhone-App-180.png设备-用途-像素尺寸)可被 Xcode Asset Catalog 自动识别。
/// </summary> /// </summary>
public class iOSIconPostProcessor public class iOSIconPostProcessor : IPostprocessBuildWithReport
{ {
public int callbackOrder => int.MaxValue;
private const string SOURCE_ICON_DIR = "Assets/Icons/iOS/"; private const string SOURCE_ICON_DIR = "Assets/Icons/iOS/";
// 各尺寸图标配置 (idiom, scale, size, 文件名) // 图标配置: (idiom, scale, 点尺寸, 源文件名, 目标文件名)
private static readonly (string idiom, string scale, string size, string file)[] ICON_CONFIGS = // 源文件在 Assets/Icons/iOS/ 下,目标文件写入 Xcode 的 AppIcon.appiconset
private static readonly (string idiom, string scale, string size, string src, string dst)[] ICON_CONFIGS =
{ {
("iphone", "2x", "20x20", "icon-20@2x.png"), // --- iPhone ---
("iphone", "3x", "20x20", "icon-20@3x.png"), ("iphone", "2x", "20x20", "iPhone-Notification-40.png", "Icon-iPhone-Notification-40.png"),
("iphone", "2x", "29x29", "icon-29@2x.png"), ("iphone", "3x", "20x20", "iPhone-Notification-60.png", "Icon-iPhone-Notification-60.png"),
("iphone", "3x", "29x29", "icon-29@3x.png"), ("iphone", "2x", "29x29", "iPhone-Settings-58.png", "Icon-iPhone-Settings-58.png"),
("iphone", "2x", "40x40", "icon-40@2x.png"), ("iphone", "3x", "29x29", "iPhone-Settings-87.png", "Icon-iPhone-Settings-87.png"),
("iphone", "3x", "40x40", "icon-40@3x.png"), ("iphone", "2x", "40x40", "iPhone-Spotlight-80.png", "Icon-iPhone-Spotlight-80.png"),
("iphone", "2x", "60x60", "icon-60@2x.png"), ("iphone", "3x", "40x40", "iPhone-Spotlight-120.png", "Icon-iPhone-Spotlight-120.png"),
("iphone", "3x", "60x60", "icon-60@3x.png"), ("iphone", "2x", "60x60", "iPhone-App-120.png", "Icon-iPhone-App-120.png"),
("ipad", "2x", "20x20", "icon-20@2x.png"), ("iphone", "3x", "60x60", "iPhone-App-180.png", "Icon-iPhone-App-180.png"),
("ipad", "2x", "29x29", "icon-29@2x.png"),
("ipad", "2x", "40x40", "icon-40@2x.png"), // --- iPad复用 iPhone 同名图标作为回退) ---
("ipad", "2x", "76x76", "icon-76@2x.png"), ("ipad", "2x", "20x20", "iPhone-Notification-40.png", "Icon-iPad-Notification-40.png"),
("ipad", "2x", "83.5x83.5", "icon-83.5@2x.png"), ("ipad", "2x", "29x29", "iPhone-Settings-58.png", "Icon-iPad-Settings-58.png"),
("ios-marketing", "1x", "1024x1024", "icon-1024.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"),
}; };
[PostProcessBuild(int.MaxValue)] public void OnPostprocessBuild(BuildReport report)
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
{ {
if (target != BuildTarget.iOS) return; if (report.summary.platform != BuildTarget.iOS) return;
string iconSetPath = Path.Combine(pathToBuiltProject, "Unity-iPhone/Images.xcassets/AppIcon.appiconset"); string pathToBuiltProject = report.summary.outputPath;
if (!Directory.Exists(iconSetPath)) string iconSetPath = FindAppIconPath(pathToBuiltProject);
if (string.IsNullOrEmpty(iconSetPath))
{ {
UnityEngine.Debug.LogWarning($"[iOSIconPostProcessor] 未找到 AppIcon.appiconset 目录: {iconSetPath}"); Debug.LogWarning($"[iOSIconPostProcessor] 未找到 AppIcon.appiconset 目录,跳过图标替换");
return; return;
} }
int copied = 0; 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) foreach (var cfg in ICON_CONFIGS)
{ {
string src = Path.GetFullPath(Path.Combine(SOURCE_ICON_DIR, cfg.file)); string src = Path.Combine(sourceDir, cfg.src);
string dst = Path.Combine(iconSetPath, cfg.file);
if (File.Exists(src)) if (File.Exists(src))
{ {
string dst = Path.Combine(iconSetPath, cfg.dst);
File.Copy(src, dst, overwrite: true); File.Copy(src, dst, overwrite: true);
copied++; copiedDstFiles.Add(cfg.dst);
} }
else else
{ {
UnityEngine.Debug.LogWarning($"[iOSIconPostProcessor] 缺少图标文件: {src}"); Debug.LogWarning($"[iOSIconPostProcessor] 缺少图标: {cfg.src}");
} }
} }
if (copied > 0) if (copiedDstFiles.Count > 0)
{ {
WriteContentsJson(iconSetPath); WriteContentsJson(iconSetPath, copiedDstFiles);
UnityEngine.Debug.Log($"[iOSIconPostProcessor] 已替换 {copied} 个 App 图标到 Xcode 项目"); Debug.Log($"[iOSIconPostProcessor] 已替换 {copiedDstFiles.Count} 个 App 图标");
}
else
{
Debug.LogError("[iOSIconPostProcessor] 未找到任何可用图标");
} }
} }
private static void WriteContentsJson(string iconSetPath) 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(); var sb = new StringBuilder();
sb.AppendLine("{"); sb.AppendLine("{");
sb.AppendLine(" \"images\" : ["); sb.AppendLine(" \"images\" : [");
for (int i = 0; i < ICON_CONFIGS.Length; i++) bool first = true;
foreach (var cfg in ICON_CONFIGS)
{ {
var cfg = ICON_CONFIGS[i]; if (!dstFiles.Contains(cfg.dst))
sb.Append(" {"); continue;
sb.Append($"\"idiom\" : \"{cfg.idiom}\", ");
if (!first) sb.AppendLine(",");
else sb.AppendLine();
first = false;
sb.Append($" {{\"idiom\" : \"{cfg.idiom}\", ");
sb.Append($"\"scale\" : \"{cfg.scale}\", "); sb.Append($"\"scale\" : \"{cfg.scale}\", ");
sb.Append($"\"size\" : \"{cfg.size}\""); sb.Append($"\"size\" : \"{cfg.size}\", ");
if (!string.IsNullOrEmpty(cfg.file)) sb.Append($"\"filename\" : \"{cfg.dst}\"}}");
sb.Append($", \"filename\" : \"{cfg.file}\"");
sb.Append("}");
if (i < ICON_CONFIGS.Length - 1) sb.Append(",");
sb.AppendLine();
} }
sb.AppendLine();
sb.AppendLine(" ],"); sb.AppendLine(" ],");
sb.AppendLine(" \"info\" : {"); sb.AppendLine(" \"info\" : {");
sb.AppendLine(" \"version\" : 1,"); sb.AppendLine(" \"version\" : 1,");

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -174,7 +174,7 @@ PlayerSettings:
buildNumber: buildNumber:
Standalone: 0 Standalone: 0
VisionOS: 0 VisionOS: 0
iPhone: 2 iPhone: 3
tvOS: 0 tvOS: 0
overrideDefaultApplicationIdentifier: 1 overrideDefaultApplicationIdentifier: 1
AndroidBundleVersionCode: 1 AndroidBundleVersionCode: 1
@ -417,6 +417,30 @@ PlayerSettings:
- {fileID: 0} - {fileID: 0}
m_Width: 120 m_Width: 120
m_Height: 120 m_Height: 120
m_Kind: 0
m_SubKind: iPhone
- m_Textures:
- {fileID: 0}
m_Width: 167
m_Height: 167
m_Kind: 0
m_SubKind: iPad
- m_Textures:
- {fileID: 0}
m_Width: 152
m_Height: 152
m_Kind: 0
m_SubKind: iPad
- m_Textures:
- {fileID: 0}
m_Width: 76
m_Height: 76
m_Kind: 0
m_SubKind: iPad
- m_Textures:
- {fileID: 2800000, guid: a7dc74acf58f7154f9a4a438ad70df1d, type: 3}
m_Width: 120
m_Height: 120
m_Kind: 3 m_Kind: 3
m_SubKind: iPhone m_SubKind: iPhone
- m_Textures: - m_Textures:
@ -453,14 +477,12 @@ PlayerSettings:
m_Height: 29 m_Height: 29
m_Kind: 1 m_Kind: 1
m_SubKind: iPhone m_SubKind: iPhone
- m_Textures: - m_Textures: []
- {fileID: 0}
m_Width: 58 m_Width: 58
m_Height: 58 m_Height: 58
m_Kind: 1 m_Kind: 1
m_SubKind: iPad m_SubKind: iPad
- m_Textures: - m_Textures: []
- {fileID: 0}
m_Width: 29 m_Width: 29
m_Height: 29 m_Height: 29
m_Kind: 1 m_Kind: 1