Unity與iOS交互,Unity腳本修改Xcode工程

Unity與iOS交互

  • Unity調(diào)用iOS的方法,首先在Xcode中新建一個(gè)iOS的橋接類,并且將.m的后綴修改為.mm
  • 在.h中加入以下代碼,里面的函數(shù)包括 無(wú)返回,返回字符串,返回布爾,帶參數(shù)的函數(shù)等
    這里需要注意一個(gè)問(wèn)題,傳入的參數(shù)和返回的字符串最好都使用json格式
#if defined(__cplusplus)
extern "C"{
#endif
    // 獲取系統(tǒng)語(yǔ)言
    extern char *GetLanguage();
    // 開始震動(dòng)
    extern void StartVibration(char *param);
    // 是否是全面屏
    extern bool IsFullSecreen();
    // Unity iOS 字符串內(nèi)存管理
    extern char* CharMemoryManagement(NSString *param);
#if defined(__cplusplus)
}
#endif
  • 在.mm中的實(shí)現(xiàn),這里需要注意的是,返回字符串的時(shí)候,只能通過(guò)CharMemoryManagement方法將字符串轉(zhuǎn)換成char,我試過(guò)其他很多種轉(zhuǎn)換方式,都會(huì)造成程序崩潰的問(wèn)題,Tools類里面就是方法的具體實(shí)現(xiàn),代碼與本文無(wú)關(guān)就不貼出來(lái)了
#if defined(__cplusplus)
extern "C"{
#endif
    char *GetLanguage()
    {
        NSString *str = [Tools GetLanguage];
        return CharMemoryManagement(str);
    }
    void StartVibration(char *param)
    {
        [Tools StartVibration:[NSString stringWithUTF8String:param]];
    }
    bool IsFullSecreen()
    {
        return [Tools IsFullSecreen];
    }
    // 如果方法返回的是字符串,需要使用該方法將字符串轉(zhuǎn)為char
    char* CharMemoryManagement(NSString *text)
    {
        char* ret = nullptr;
        ret = (char*) malloc([text length] + 1);
        memcpy(ret,[text UTF8String],([text length] + 1));
        return ret;
    }
#if defined(__cplusplus)
}
#endif
  • 上面的步驟完成之后,將.h和.mm文件(包括Tools等依賴的文件)拷貝到Unity工程中Assets目錄下
  • 在Unity中,新建一個(gè)cs腳本,添加以下代碼,這個(gè)腳本最好實(shí)現(xiàn)成單例,然后就可以通過(guò)Instance.IOSGetLanguage()來(lái)調(diào)用
    注意需要引用 using System.Runtime.InteropServices 命名空間
#if UNITY_IOS && !UNITY_EDITOR
    [DllImport("__Internal")] private static extern string GetLanguage();
    [DllImport("__Internal")] private static extern bool IsFullSecreen();
    [DllImport("__Internal")] private static extern void StartVibration(string param);
#endif

#region Unity to iOS
    public string IOSGetLanguage()
    {
#if UNITY_IOS && !UNITY_EDITOR
        return GetLanguage();
#else
        return "";
#endif
    }
    public void IOSStartVibration(long time)
    {
#if UNITY_IOS && !UNITY_EDITOR
        StartVibration(time.ToString());
#endif
    }
    public bool IOSIsFullSecreen()
    {
#if UNITY_IOS && !UNITY_EDITOR
        return IsFullSecreen();
#else
        return false;
#endif
    }
  • iOS通知Unity,iOS直接調(diào)用Unity方法的實(shí)現(xiàn)是非常麻煩的,通常情況下,我們都使用通知的方法,常見的場(chǎng)景是Unity調(diào)用iOS方法需要異步返回時(shí)
  • 在iOS類中加入下面代碼,然后我們就可以給Unity發(fā)送通知了,如UnitySendMessage("節(jié)點(diǎn)名稱", "方法名稱", "參數(shù),沒(méi)有就傳空字符串"",不能傳nil")
// --------- 某個(gè).mm文件中 ---------
#if defined(__cplusplus)
extern "C"{
#endif
    extern void UnitySendMessage(const char *, const char *, const char *);
#if defined(__cplusplus)
}
#endif

// --------- 需要通知Unity的iOS類中 ---------
- (void)didReceiveReward {
    // 在iOS的某個(gè)方法中,向Unity發(fā)送消息
    UnitySendMessage("iOSLibraryUnity", "OnDidReceiveReward", "收到獎(jiǎng)勵(lì)");
}

//  --------- Unity中掛在節(jié)點(diǎn)上的腳本,用來(lái)接收通知 ---------
private void Start()
{
    // 腳本掛載的節(jié)點(diǎn)名必須和UnitySendMessage發(fā)送時(shí)填的一樣
    this.name = "iOSLibraryUnity";
}
private void OnDidReceiveReward(string msg)
{
    // 接收到iOS通知
    Debug.log(msg);
}

Unity腳本修改Xcode工程

Unity要在iOS平臺(tái)發(fā)布,需要先生成Xcode工程,通常生成Xcode工程后我們還需要修改很多的配置,添加原生代碼等, 而這些是可以通過(guò)cs腳本修改的,比如修改Xcode工程的plist、添加Framework庫(kù)、拷貝文件到iOS工程、插入代碼等

  • 自動(dòng)pod
    實(shí)現(xiàn)自動(dòng)pod需要谷歌的一個(gè)插件https://github.com/googlesamples/unity-jar-resolver,該插件在谷歌相關(guān)的一些SDK中就有,如OnsSignal、Firebase等SDK,如你應(yīng)用集成有這些SDK,則不需要再下載該插件了,查看是否集成了該插件可以看你Assets目錄下有沒(méi)有ExternalDependencyManager文件,或看Assets->External Dependency Manager有沒(méi)有這個(gè)選項(xiàng)
  • 集成完插件后,在Editor目錄下新建一個(gè)Dependencies.xml的文件,里面的內(nèi)容如下,這樣在生成Xcode工程時(shí)就會(huì)自動(dòng)將下面的庫(kù)pod進(jìn)工程
<dependencies>
  <iosPods>
    <iosPod name="AFNetworking" version="4.0.1"/>
    <iosPod name="SDWebImage" version="5.8.4"/>
    <iosPod name="Masonry" version="1.1.0"/>
    <iosPod name="YYModel" version="1.0.4"/>
  </iosPods>
</dependencies>
  • 修改Xcode工程,在Editor中新建一個(gè)cs腳本,如下
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using UnityEditor.XCodeEditor;

public static class BuildiOS
{
    [PostProcessBuild(100)]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string buildPath)
    {
        if (buildTarget != BuildTarget.iOS)
            return;
        var mProjectPath = PBXProject.GetPBXProjectPath(buildPath);
        var mProject = new PBXProject();
        mProject.ReadFromString(File.ReadAllText(mProjectPath));

        var mTargetGUID = GetPBXProjectTargetGUID(mProject);
        var mFrameworkGUID = GetPBXProjectUnityFrameworkGUID(mProject);

        var mPlistPath = Path.Combine(buildPath, "Info.plist");

        // 修改Plist
        PlistModify(mPlistPath);

        // 添加系統(tǒng)庫(kù)
        SystemFrameworkAdd(mProject, mFrameworkGUID);

        // 添加文件
        FilesAdd(mProject, mTargetGUID, buildPath, mProjectPath);

        // 插入代碼
        UnityAppControllerCodesAdd(buildPath);
    }

#if UNITY_2019_3_OR_NEWER
    private static string GetPBXProjectTargetGUID(PBXProject project)
    {
        return project.GetUnityMainTargetGuid();
    }
    private static string GetPBXProjectUnityFrameworkGUID(PBXProject project)
    {
        return project.GetUnityFrameworkTargetGuid();
    }
#else
    private static string GetPBXProjectTargetGUID(PBXProject project)
    {
        return project.TargetGuidByName(PBXProject.GetUnityTargetName());
    }
    private static string GetPBXProjectUnityFrameworkGUID(PBXProject project)
    {
        return GetPBXProjectTargetGUID(project);
    }
#endif
}
  • 修改plist
private static void PlistModify(string plistPath)
{
    var plist = new PlistDocument();
    plist.ReadFromFile(plistPath);

    // plist中添加一個(gè)字符串類型的key 如隱私設(shè)置
    plist.root.SetString("NSLocationAlwaysAndWhenInUseUsageDescription",
                         "$(PRODUCT_NAME) needs to get the location. I hope you agree.";

    // plist中添加一個(gè)布爾的key
    plist.root.SetBoolean("CADisableMinimumFrameDuration", false);

    // plist中添加一個(gè)字典的key 如ATS設(shè)置
    PlistElementDict dict = plist.root.CreateDict("NSAppTransportSecurity");
    dict.SetBoolean("NSAllowsArbitraryLoads", true);
        
    // 最后保存plist
    plist.WriteToFile(plistPath);
}
  • 添加系統(tǒng)庫(kù)
private static void SystemFrameworkAdd(PBXProject project, string mFrameworkGUID) 
{
    string[] FRAMEWORKS_TO_ADD = {
        "libz.dylib",
        "libc++.dylib",
        "Security.framework",
        "SystemConfiguration.framework",
    };
    foreach (var framework in FRAMEWORKS_TO_ADD)
    {
        project.AddFrameworkToProject(mFrameworkGUID, framework, false);
    }
}
private static void SystemLibAdd(PBXProject project, string targetGuid, string lib)
{
    string fileGuid = project.AddFile("usr/lib/" + lib, "Frameworks/" + lib, PBXSourceTree.Sdk);
    project.AddFileToBuild(targetGuid, fileGuid);
}
  • 拷貝文件夾,代碼文件如.h/.m等文件會(huì)自動(dòng)拷貝的Xcode工程中,但圖片,三方的Framework、lib等文件并不會(huì)自動(dòng)拷貝到Xcode工程中,所以需要cs腳本來(lái)完成
private static void FilesAdd(PBXProject project, string mTargetGUID, string buildPath, string projectPath)
{
    string ResourcePath = "Assets/文件夾路徑";
    XcodeDirectoryProcessor copy = new XcodeDirectoryProcessor();
    copy.CopyAndAddBuildToXcode(project, mTargetGUID, ResourcePath, buildPath, "Xcode中的文件夾名稱");
    File.WriteAllText(projectPath, project.WriteToString());
}
  • 插入代碼
private static void UnityAppControllerCodesAdd(string buildPath)
{
    // 獲取Prefix.pch文件
    string mPchPath = buildPath + "/Classes/Prefix.pch";
    UnityEditor.XCodeEditor.XClass Pch = new UnityEditor.XCodeEditor.XClass(mPchPath);
    // 需要插入的代碼,例如我們?cè)趐ch中插入一段引入類的代碼
    string call = "#import \"output.h\"";
    // 代碼標(biāo)記,找到pch文件里面已經(jīng)存在的代碼,我們就可以將需要插入的代碼,插入到這行代碼下面
    string mark = "#include \"UnityInterface.h\"";
    // 開始插入代碼
    Pch.WriteBelow(mark, call);
}
  • 其他Editor中使用到的cs文件
using UnityEngine;
using System.IO;

#if UNITY_IOS
using UnityEditor.iOS.Xcode;

public static class ExtensionName
{
    public const string META = ".meta";
    public const string ARCHIVE = ".a";
    public const string FRAMEWORK = ".framework";
    public const string BUNDLE = ".bundle";
}

public class XcodeDirectoryProcessor {

    /// <summary>
    /// 添加編譯本地文件到Xcode工程
    /// </summary>
    /// <param name="pbxProject"></param>
    /// <param name="targetGuid"></param>
    /// <param name="copyDirectoryPath">源路徑</param>
    /// <param name="buildPath">拷貝路徑</param>
    /// <param name="currentDirectoryPath">拷貝路徑/currentDirectoryPath</param>
    /// <param name="needToAddBuild"></param>
    public void CopyAndAddBuildToXcode(PBXProject pbxProject, string targetGuid, string copyDirectoryPath, string buildPath, string currentDirectoryPath, bool needToAddBuild = true){

        string unityDirectoryPath = copyDirectoryPath;
        string xcodeDirectoryPath = buildPath;

        if(!string.IsNullOrEmpty(currentDirectoryPath)){
            unityDirectoryPath = Path.Combine(unityDirectoryPath, currentDirectoryPath);
            xcodeDirectoryPath = Path.Combine(xcodeDirectoryPath, currentDirectoryPath);
            Delete (xcodeDirectoryPath);
            Directory.CreateDirectory(xcodeDirectoryPath);
        }
        foreach (string filePath in Directory.GetFiles(unityDirectoryPath)){
            string extension = Path.GetExtension (filePath);
            if(extension == ExtensionName.META){
                continue;
            }
            else if(extension == ExtensionName.ARCHIVE){
                pbxProject.AddBuildProperty(
                    targetGuid, 
                    "LIBRARY_SEARCH_PATHS", 
                    "$(PROJECT_DIR)/" + currentDirectoryPath
                );
            }
            string fileName = Path.GetFileName (filePath);
            string copyPath = Path.Combine (xcodeDirectoryPath, fileName);
            if(fileName[0] == '.'){
                continue;
            }
            File.Delete(copyPath);
            File.Copy(filePath, copyPath);
            if(needToAddBuild){
                string relativePath = Path.Combine(currentDirectoryPath, fileName);
                pbxProject.AddFileToBuild(targetGuid, pbxProject.AddFile(relativePath, relativePath, PBXSourceTree.Source));
            }
        }
        //遍歷文件夾
        foreach (string directoryPath in Directory.GetDirectories(unityDirectoryPath)){
            string directoryName = Path.GetFileName (directoryPath);
            bool nextNeedToAddBuild = needToAddBuild;
            if(directoryName.Contains(ExtensionName.FRAMEWORK) || directoryName.Contains(ExtensionName.BUNDLE) || 
               directoryName == "Unity-iPhone"){
                nextNeedToAddBuild = false;
            }
            CopyAndAddBuildToXcode (
                pbxProject, targetGuid, 
                copyDirectoryPath, buildPath, Path.Combine(currentDirectoryPath, directoryName), 
                nextNeedToAddBuild
            );
            if(directoryName.Contains(ExtensionName.FRAMEWORK) || directoryName.Contains(ExtensionName.BUNDLE)){
                string relativePath = Path.Combine(currentDirectoryPath, directoryName);
                pbxProject.AddFileToBuild(targetGuid, pbxProject.AddFile(relativePath, relativePath, PBXSourceTree.Source));
                pbxProject.AddBuildProperty(
                    targetGuid, 
                    "FRAMEWORK_SEARCH_PATHS", 
                    "$(PROJECT_DIR)/" + currentDirectoryPath
                );
            }
        }
    }

    /// <summary>
    /// 拷貝目錄(拷貝時(shí)候會(huì)刪除copyPath)
    /// </summary>
    /// <param name="sourcePath"></param>
    /// <param name="copyPath"></param>
    public void CopyAndReplace(string sourcePath, string copyPath)
    {
        Delete (copyPath);
        Directory.CreateDirectory(copyPath);
        foreach (var file in Directory.GetFiles(sourcePath)){
            File.Copy(file, Path.Combine(copyPath, Path.GetFileName(file)));
        }
        foreach (var dir in Directory.GetDirectories(sourcePath)){
            CopyAndReplace(dir, Path.Combine(copyPath, Path.GetFileName(dir)));
        }
    }

    /// <summary>
    /// 刪除目錄
    /// </summary>
    /// <param name="targetDirectoryPath"></param>
    public void Delete(string targetDirectoryPath){
        if (!Directory.Exists (targetDirectoryPath)) {
            return;
        }
        string[] filePaths = Directory.GetFiles(targetDirectoryPath);
        foreach (string filePath in filePaths){
            File.SetAttributes(filePath, FileAttributes.Normal);
            File.Delete(filePath);
        }
        string[] directoryPaths = Directory.GetDirectories(targetDirectoryPath);
        foreach (string directoryPath in directoryPaths){
            Delete(directoryPath);
        }
        Directory.Delete(targetDirectoryPath, false);
    }
}

#endif

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容