using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEditor.iOS.Xcode.Custom; using UnityEngine; public class XCodeProjectMod { // Channel 插件存放目录(BuildIpa 前由 ClientPackage.BuildIpa 从 _sdkPath 拷贝到 Assets/Plugins/iOS/Channel) private static readonly string iOSPluginPath = Application.dataPath + "/Plugins/iOS/Channel"; [UnityEditor.Callbacks.PostProcessBuild(999)] public static void OnPostprocessBuild(BuildTarget buildTarget, string path) { if (buildTarget != BuildTarget.iOS) { return; } DoPBXProject(path); BuildPlist(path); ModifyFile(path); // 生成 .xcworkspace 结构,方便开发者在 Xcode 中手动 Add Package Dependency // SPM 依赖必须通过 .xcworkspace 管理,.xcodeproj 单独无法正确 resolve // 开发者需在 Xcode 中手动 File → Add Package Dependency 添加以下仓库,并选择所需的产品包: // - Firebase: https://github.com/firebase/firebase-ios-sdk // - Facebook: https://github.com/facebook/facebook-ios-sdk // - Adjust: https://github.com/adjust/ios_sdk CreateWorkspaceWithSPM(path); } static void DoPBXProject(string path) { string _projectPath = PBXProject.GetPBXProjectPath(path); PBXProject project = new PBXProject(); project.ReadFromString(File.ReadAllText(_projectPath)); string targetGUID = project.TargetGuidByName(PBXProject.GetUnityTargetName()); string fwTargetGUID = project.TargetGuidByName("UnityFramework"); // 1.1 环境要求:iOS 13.0 及以上 project.SetBuildProperty(targetGUID, "IPHONEOS_DEPLOYMENT_TARGET", "13.0"); project.SetBuildProperty(fwTargetGUID, "IPHONEOS_DEPLOYMENT_TARGET", "13.0"); project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-ObjC"); // Firebase 调试日志参数 project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRAnalyticsDebugEnabled"); project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRDebugEnabled"); project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRAnalyticsVerboseLoggingEnabled"); // WebKit(文档要求) project.AddFrameworkToProject(fwTargetGUID, "WebKit.framework", true); //UnityFramework project.AddBuildProperty(fwTargetGUID, "OTHER_LDFLAGS", "-ObjC"); // 添加 GoogleService-Info.plist 和 funshine.plist 到工程 string[] configPlists = { "GoogleService-Info.plist", "funshine.plist" }; foreach (var plistName in configPlists) { string srcPath = iOSPluginPath + "/" + plistName; if (File.Exists(srcPath)) { string destPath = path + "/" + plistName; File.Copy(srcPath, destPath, true); string fileGuid = project.AddFile(plistName, plistName, PBXSourceTree.Source); project.AddFileToBuild(targetGUID, fileGuid); } else { Debug.LogWarning("[XCodeProjectMod] " + plistName + " 未找到: " + srcPath); } } // Adjust SDK 依赖的系统框架(文档要求 Status 设为 Optional) project.AddFrameworkToProject(fwTargetGUID, "AdServices.framework", true); project.AddFrameworkToProject(fwTargetGUID, "AdSupport.framework", true); project.AddFrameworkToProject(fwTargetGUID, "AppTrackingTransparency.framework", true); project.AddFrameworkToProject(fwTargetGUID, "StoreKit.framework", true); File.WriteAllText(_projectPath, project.WriteToString()); } /// /// 生成 Unity-iPhone.xcworkspace 结构,仅包含 contents.xcworkspacedata。 /// SPM 依赖由开发者在 Xcode 中手动 File → Add Package Dependency 添加并选择所需产品包: /// - Firebase: https://github.com/firebase/firebase-ios-sdk /// - Facebook: https://github.com/facebook/facebook-ios-sdk /// - Adjust: https://github.com/adjust/ios_sdk /// Package.resolved 由 Xcode 在 resolve 后自动生成,代码不做处理。 /// private static void CreateWorkspaceWithSPM(string projectPath) { string workspacePath = projectPath + "/Unity-iPhone.xcworkspace"; string xcodeprojAbsPath = projectPath + "/Unity-iPhone.xcodeproj"; if (!Directory.Exists(xcodeprojAbsPath)) { Debug.LogWarning("[XCodeProjectMod] xcodeproj not found, skip creating workspace: " + xcodeprojAbsPath); return; } Directory.CreateDirectory(workspacePath); string contentsXml = @" "; File.WriteAllText(workspacePath + "/contents.xcworkspacedata", contentsXml); Debug.Log("[XCodeProjectMod] Unity-iPhone.xcworkspace created at: " + workspacePath); Debug.Log("[XCodeProjectMod] 请在 Xcode 中打开 .xcworkspace,手动 File → Add Package Dependency 添加 SPM 仓库:"); Debug.Log("[XCodeProjectMod] - Firebase: https://github.com/firebase/firebase-ios-sdk"); Debug.Log("[XCodeProjectMod] - Facebook: https://github.com/facebook/facebook-ios-sdk"); Debug.Log("[XCodeProjectMod] - Adjust: https://github.com/adjust/ios_sdk"); } private static void BuildPlist(string path) { string _plistPath = path + "/Info.plist"; PlistDocument _plist = new PlistDocument(); _plist.ReadFromString(File.ReadAllText(_plistPath)); PlistElementDict _rootDict = _plist.root; _rootDict.SetString("NSUserTrackingUsageDescription", "Please rest assured, granting permission will not access your private information on other websites. This permission is only used to identify the device and ensure service security and enhance browsing experience"); // Facebook (funshine.plist: kFacebookAppID / kFacebookAppSecret) _rootDict.SetString("FacebookAppID", "1445747540428317"); _rootDict.SetString("FacebookClientToken", "1103765897fb67ffc898e6a8ee2d9aec"); _rootDict.SetString("FacebookDisplayName", "EZ PZ: Byte Heroes"); // 适配欧盟地区政策法规 - Google Analytics 默认允许 _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE", true); _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE", true); _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA", true); _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS", true); // App 名称本地化 _rootDict.SetBoolean("Application has localized display name", true); _rootDict.SetString("CFBundleDevelopmentRegion", "english"); _rootDict.SetString("Localization native development region", "english"); PlistElementArray nsURLTypes = _rootDict.CreateArray("CFBundleURLTypes"); var dict0 = nsURLTypes.AddDict(); dict0.SetString("CFBundleTypeRole", "Editor"); dict0.SetString("CFBundleURLName", "fb"); var schemesArray = dict0.CreateArray("CFBundleURLSchemes"); schemesArray.AddString("fb1445747540428317"); var array1 = _rootDict.CreateArray("LSApplicationQueriesSchemes"); array1.AddString("fbapi"); array1.AddString("fb-messenger-api"); array1.AddString("fbauth2"); array1.AddString("fbshareextension"); File.WriteAllText(_plistPath, _plist.WriteToString()); } private static void ModifyFile(string projectPath) { //修改UnityAppController.mm 文件 var _xclass = new XClass(projectPath + "/Classes/UnityAppController.mm"); //引入fimpfunSDK _xclass.WriteBelow("#include \"PluginBase/AppDelegateListener.h\"", "#import \"fimpfunSDK.h\""); //unity交互调用的方法 string newCode = "\n" + "extern \"C\" void IOSUniyMessageHandle(const char* jsonString) {\n" + " [GetAppController().fimpfunBridge HandleGameMessage:[NSString stringWithUTF8String:jsonString]];\n" + "}"; _xclass.WriteBelow("extern \"C\" ScreenOrientation UnityCurrentOrientation() { return GetAppController().unityView.contentOrientation; }", newCode); //初始化fimpfunSDK newCode = "\n" + " _fimpfunBridge = [[FimpfunBridge alloc] init];\n [_fimpfunBridge YngameSDKInit:application didFinishLaunchingWithOptions:launchOptions];"; _xclass.WriteBelow("::printf(\"-> applicationDidFinishLaunching()\\n\");", newCode); // fimpfunSDK 生命周期 _xclass.WriteBelow("- (BOOL)application:(UIApplication*)app openURL:(NSURL*)url options:(NSDictionary*)options\n{", "\n [_fimpfunBridge application:app openURL:url options:options];"); _xclass.WriteBelow("::printf(\"-> applicationDidBecomeActive()\\n\");", "[_fimpfunBridge applicationDidBecomeActive:application];"); //修改UnityAppController.h 文件 _xclass = new XClass(projectPath + "/Classes/UnityAppController.h"); _xclass.WriteBelow("@class DisplayConnection;", "@class FimpfunBridge;"); _xclass.WriteBelow("DisplayConnection* _mainDisplay;", "FimpfunBridge* _fimpfunBridge;"); _xclass.WriteBelow("@property (readonly, copy, nonatomic) DisplayConnection* mainDisplay;", "@property (readonly, copy, nonatomic) FimpfunBridge* fimpfunBridge;"); // 3.1 适配欧盟政策:在 viewDidAppear 中调用 adaptingEUGDPR(文档要求) // UnityViewControllerBase.mm 在较新 Unity 版本(如 2022.3+)中可能不存在 string viewControllerPath = projectPath + "/Classes/UnityViewControllerBase.mm"; if (System.IO.File.Exists(viewControllerPath)) { var _viewController = new XClass(viewControllerPath); _viewController.WriteBelow("@implementation UnityViewControllerBase", "\n- (void)viewDidAppear:(BOOL)animated {\n [super viewDidAppear:animated];\n [GetAppController().fimpfunBridge viewDidAppear];\n}\n"); } else { Debug.LogWarning("[XCodeProjectMod] UnityViewControllerBase.mm 文件不存在,跳过 viewDidAppear 注入。此 Unity 版本可能已移除该文件。"); } // 创建 InfoPlist.strings(App 名称本地化) CreateInfoPlistStrings(projectPath); } /// /// 创建 InfoPlist.strings 文件用于 App 名称本地化 /// private static void CreateInfoPlistStrings(string projectPath) { // 英文(默认) string enDir = projectPath + "/English.lproj"; if (!Directory.Exists(enDir)) { Directory.CreateDirectory(enDir); } string enStringsPath = enDir + "/InfoPlist.strings"; if (!File.Exists(enStringsPath)) { File.WriteAllText(enStringsPath, "CFBundleDisplayName = \"EZ PZ\";\n"); } // 中文 string zhDir = projectPath + "/zh-Hans.lproj"; if (!Directory.Exists(zhDir)) { Directory.CreateDirectory(zhDir); } string zhStringsPath = zhDir + "/InfoPlist.strings"; if (!File.Exists(zhStringsPath)) { File.WriteAllText(zhStringsPath, "CFBundleDisplayName = \"懒人三国\";\n"); } // 将 InfoPlist.strings 添加到 Xcode 工程 string _projectPath = PBXProject.GetPBXProjectPath(projectPath); try { PBXProject project = new PBXProject(); project.ReadFromString(File.ReadAllText(_projectPath)); string targetGUID = project.TargetGuidByName(PBXProject.GetUnityTargetName()); // 英文 string enRelPath = "English.lproj/InfoPlist.strings"; string enFileGuid = project.AddFile(enRelPath, enRelPath, PBXSourceTree.Source); project.AddFileToBuild(targetGUID, enFileGuid); // 中文 string zhRelPath = "zh-Hans.lproj/InfoPlist.strings"; string zhFileGuid = project.AddFile(zhRelPath, zhRelPath, PBXSourceTree.Source); project.AddFileToBuild(targetGUID, zhFileGuid); // 添加 Sign In with Apple entitlements AddEntitlements(projectPath, project, targetGUID); File.WriteAllText(_projectPath, project.WriteToString()); } catch (System.Exception e) { Debug.LogError("[XCodeProjectMod] 将 InfoPlist.strings 添加到 Xcode 工程失败: " + e.Message); } } /// /// 添加 Sign In with Apple 等 entitlements /// private static void AddEntitlements(string projectPath, PBXProject project, string targetGUID) { string entPath = projectPath + "/" + PBXProject.GetUnityTargetName() + ".entitlements"; // 如果 entitlements 文件不存在,创建 if (!File.Exists(entPath)) { string entContent = "\n" + "\n" + "\n" + "\n" + "\tcom.apple.developer.applesignin\n" + "\t\n" + "\t\tDefault\n" + "\t\n" + "\n" + "\n"; File.WriteAllText(entPath, entContent); } else { // 已有 entitlements 文件,检查是否已有 Sign In with Apple string existingContent = File.ReadAllText(entPath); if (!existingContent.Contains("com.apple.developer.applesignin")) { existingContent = existingContent.Replace("", "\tcom.apple.developer.applesignin\n" + "\t\n" + "\t\tDefault\n" + "\t\n" + ""); File.WriteAllText(entPath, existingContent); } } // 设置 CODE_SIGN_ENTITLEMENTS build property(不加入 Copy Bundle Resources) // entitlements 文件只需要在 Build Settings 中引用,不需要加入任何 Build Phase string entFileName = PBXProject.GetUnityTargetName() + ".entitlements"; project.SetBuildProperty(targetGUID, "CODE_SIGN_ENTITLEMENTS", entFileName); } private static string GetBuildPath() { string _buildPath = Application.dataPath + "/../IOS_BUILD"; if (!Directory.Exists(_buildPath)) { Directory.CreateDirectory(_buildPath); } return new DirectoryInfo(_buildPath).FullName; } public static string[] GetBuildLevels() { List _sceneNames = new List(); foreach (var _scene in EditorBuildSettings.scenes) { if (_scene == null) { continue; } if (_scene.enabled) { _sceneNames.Add(_scene.path); } } return _sceneNames.ToArray(); } private static void CopyDirectory(string sourceDir, string destDir) { Directory.CreateDirectory(destDir); foreach (var file in Directory.GetFiles(sourceDir)) { string destFile = Path.Combine(destDir, Path.GetFileName(file)); File.Copy(file, destFile, true); } foreach (var dir in Directory.GetDirectories(sourceDir)) { string destSubDir = Path.Combine(destDir, Path.GetFileName(dir)); CopyDirectory(dir, destSubDir); } } private static bool s_IsAppend = false; public static void BuildIOSProject_Append() { string _buildPath = GetBuildPath(); UnityEngine.Debug.Log(_buildPath); if (string.IsNullOrEmpty(_buildPath)) { return; } s_IsAppend = true; PlayerSettings.SetScriptingBackend(BuildTargetGroup.iOS, ScriptingImplementation.IL2CPP); BuildPipeline.BuildPlayer(ClientPackage.baseLevels, _buildPath, BuildTarget.iOS, BuildOptions.AcceptExternalModificationsToPlayer); } public static void BuildIOSProject_Replace() { s_IsAppend = false; PlayerSettings.SetScriptingBackend(BuildTargetGroup.iOS, ScriptingImplementation.IL2CPP); BuildPipeline.BuildPlayer(ClientPackage.baseLevels, GetBuildPath(), BuildTarget.iOS, BuildOptions.None); } [MenuItem("Build/ipa")] public static void BuildIPA() { var p = new System.Diagnostics.Process(); p.StartInfo.FileName = "osascript"; p.StartInfo.Arguments = string.Format("-e 'tell application \"Terminal\" \n activate \n do script \"cd {0} && sh {1} {2}\" \n end tell'", Application.dataPath + "/../", "buildipa.sh", GetBuildPath()); p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = false; p.Start(); p.WaitForExit(); } }