unity-与ios交互


title: unity-与ios交互
categories: Unity3d
tags: [unity, ios]
date: 2021-01-31 14:16:54
comments: false
mathjax: true
toc: true

unity-与ios交互


unity 与 ios 交互

  1. 写 ios 插件, 丢到平台层目录 Assets/Plugins/iOS

    • 头文件 iOSBridgePlugin.h

      // ----------------------------------------
      // --- ios 自定义插件接口声明
      // ----------------------------------------
      
      #ifdef __cplusplus
      extern "C"{
              
              
      #endif
      
      void ShowTips(const char* goName, const char* callFnName, const char* msg);
      
      #ifdef __cplusplus
      } // extern "C"
      #endif
      
      
      
    • 实现文件 iOSBridgePlugin.mm.

      这个是混编文件 (oc + c/c++) , xcode 可以自动识别为 Objective-C++ 文件

      #import "iOSBridgePlugin.h"
      #import "Classes/Unity/UnityInterface.h" // 引入 unity 相关 api
      
      void ShowTips(const char* goName, const char* callFnName, const char* msg) {
              
              
          NSLog(@"--- ShowTips");
          NSString* go = [NSString stringWithUTF8String:goName]; // c 字符串 转成 oc 字符串, 这里一定要先转成 oc, 不然 const char* 调用后就会释放掉栈内存, 会导致 UnitySendMessage 回传 unity 失败
          NSString* fn = [NSString stringWithUTF8String:callFnName];
          NSString* content = [NSString stringWithUTF8String:msg]; 
          UIAlertController * alert = [UIAlertController
                                       alertControllerWithTitle:@"Hi, wilker."
                                       message:content
                                       preferredStyle:UIAlertControllerStyleAlert];
          
          UIAlertAction* yesButton = [UIAlertAction
                                      actionWithTitle:@"Reply"
                                      style:UIAlertActionStyleDefault
                                      handler:^(UIAlertAction * action) {
              
              
              const char* rspMsg = [[NSString stringWithFormat: @"ios replay: %@", content] UTF8String]; // oc 字符串 转成 c 字符串
              UnitySendMessage([go UTF8String], [fn UTF8String], rspMsg); // ios 调用 unity
          }];
          
          [alert addAction:yesButton];
      
          UIViewController* rootCtrl=[UIApplication sharedApplication].keyWindow.rootViewController;
          [rootCtrl presentViewController:alert animated:YES completion:nil];
      }
      
  2. 在 csharp 代码导入并使用这个插件 api

    GameMgr.cs

    using System.Collections;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class GameMgr : MonoBehaviour {
          
          
        public Text txt;
        public GameObject go; 
    
        void Start() {
          
          
            gameObject.name = "GameMgr";
            DontDestroyOnLoad(gameObject);
        }
    
        public void OnNativeCall(string data) {
          
          
            Debug.LogFormat("--- OnNativeCall, data: {0}", data);
            txt.text = data;
        }
    
        public void CallNative() {
          
          
            string msg = "hello ios-" + Random.Range(1000, 9999);
    #if UNITY_IPHONE || UNITY_IOS
            ShowTips(gameObject.name, "OnNativeCall", msg); // 调用 ios api      
    #else
            Debug.LogErrorFormat("--- no implemention on platform: {0}", Application.platform.ToString());
    #endif
        }
    
        void Update() {
          
          
            go.transform.Rotate(Vector3.up * 50 * Time.deltaTime);
        }
    
    
        // ------------------- ios native api
    #if UNITY_IPHONE || UNITY_IOS
        [DllImport("__Internal")]
        private static extern void ShowTips(string goName, string callFnName, string msg);
    #endif
    }
    

    目录目录

  3. build 一下生产 xcode 工程

  4. 打开 xcode.

    Plugins 目录会移到 Libraries 目录下

  5. cmd + R 编译并运行到手机.

    不能运行到 ios 模拟器上, 因为 unity 导出的是 arm 架构的库, 而 ios 模拟器时 x86 架构.

    • 效果


扩展 UnityAppController

  • Unity iOS接SDK,定制UnityAppController - https://www.codeleading.com/article/76004167121/

Unity3d提供了一套插件机制,可以很方便地在项目中使用自己的CustomAppController继承并重写默认的UnityAppController的方法。
在 Unity 插件目录下创建以下文件:

Assets/Plugins/iOS/CustomAppController.mm

文件名必须是 xxxxAppController.mmxxxx前缀可自选,但不能省略,如CustomAppController.mm;否则在 Build项目的时候,会被移动到错误的目录中去。

// CustomAppController.mm

#import  "UnityAppController.h"
@interface CustomAppController : UnityAppController
@end

IMPL_APP_CONTROLLER_SUBCLASS (CustomAppController)

@implementation CustomAppController

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions{
    [super application:application didFinishLaunchingWithOptions:launchOptions];
	NSLog(@"--- CustomAppController didFinishLaunchingWithOptions");
    return YES;
}

@end

注意,上面的CustomAppController.mm中有一个宏IMPL_APP_CONTROLLER_SUBCLASS,Unity 就是通过 IMPL_APP_CONTROLLER_SUBCLASS知道要使用我们定制的 CustomAppController而不是使用默认的UnityAppController


unity view 初始化完后搞事情

Classes/UI/UnityAppController+ViewHandling.h 头文件中包含了 unity controller, view 等初始化的工作, 所以可以重写里面的部分方法去搞事情.

简单版

  1. 在 [扩展 UnityAppController](#扩展 UnityAppController) 的基础上, 重写 willStartWithViewController 方法即可, 这个方法在 Classes/UI/UnityAppController+ViewHandling.h 头文件中

    // CustomAppController.mm
    #import "Classes/UI/UnityAppController+ViewHandling.h"
    
    @implementation CustomAppController
    - (void)willStartWithViewController:(UIViewController *)controller{
        [super willStartWithViewController:controller];
        NSLog(@"--- iOSAppController.willStartWithViewController, controller: %@", controller);
        
        UILabel* lbl = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 300, 50)];
        lbl.textColor = [UIColor blackColor];
        lbl.text = @"hello world 2";
        [controller.view addSubview:lbl];
    }
    @end
    

复杂版 - 监听 UintyView 生命周期

应用场景: 想要在 view 加载前后搞事情. unity 提供了一个 delegate 让你去实现并注册进去, 它就会在 view 的生命周期里通知你.

  1. 自定义一个类 (如: MyPlugin) 实现 UnityViewControllerListener 的 delegate 接口

    // MyPlugin.h
    #import "Classes/PluginBase/UnityViewControllerListener.h"
    #import <Foundation/Foundation.h>
    NS_ASSUME_NONNULL_BEGIN
    @interface MyPlugin : NSObject<UnityViewControllerListener>
    @end
    NS_ASSUME_NONNULL_END
        
    // MyPlugin.m
    #import "MyPlugin.h"
    @implementation MyPlugin
    - (void)viewWillLayoutSubviews:(NSNotification*)notification {
        NSLog(@"--- MyPlugin.viewWillLayoutSubviews");
    }
    - (void)viewDidLayoutSubviews:(NSNotification*)notification {
        NSLog(@"--- MyPlugin.viewDidLayoutSubviews");
    }
    - (void)viewWillDisappear:(NSNotification*)notification {
        NSLog(@"--- MyPlugin.viewWillDisappear");
    }
    - (void)viewDidDisappear:(NSNotification*)notification {
        NSLog(@"--- MyPlugin.viewDidDisappear");
    }
    - (void)viewWillAppear:(NSNotification*)notification {
        NSLog(@"--- MyPlugin.viewWillAppear");
    }
    // 这个可以作为 unity view 加载完成的标记. 只会调用一次
    - (void)viewDidAppear:(NSNotification*)notification {
        NSLog(@"--- MyPlugin.viewDidAppear");
        NSLog(@"--- viewDidAppear.obj: %@", notification.object);
    
        UILabel* lbl = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 300, 50)];
        lbl.textColor = [UIColor blackColor];
        lbl.text = @"hello world";
        UIViewController* rootVC = [UIApplication sharedApplication].windows[0].rootViewController;
        [rootVC.view addSubview:lbl];
        
        NSLog(@"--- rootVc: %@", rootVC);
        NSLog(@"--- rootVc.view: %@", rootVC.view); // 这个就是 unityView
    }
    @end
    
    • 纳尼? import 头文件 UnityViewControllerListener.h 后编译报错: Unable to compile in XCode: Expected identifier or '(', extern C 的几个 const 变量引发的.

      解决办法就是去改下源文件. 参考: https://answers.unity.com/questions/1251495/unable-to-compile-in-xcode-expected-identifier-or.html

      1. 修改 UnityViewControllerListener.h 内容

        // 注释掉 extern 变量声明
        //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewWillLayoutSubviews;
        //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewDidLayoutSubviews;
        //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewWillDisappear;
        //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewDidDisappear;
        //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewWillAppear;
        //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewDidAppear;
        //extern "C" __attribute__((visibility("default"))) NSString* const kUnityInterfaceWillChangeOrientation;
        //extern "C" __attribute__((visibility("default"))) NSString* const kUnityInterfaceDidChangeOrientation;
        
        // 新加 cosnt 变量
        NSString* const kUnityViewWillLayoutSubviews = @"kUnityViewWillLayoutSubviews";
        NSString* const kUnityViewDidLayoutSubviews = @"kUnityViewDidLayoutSubviews";
        NSString* const kUnityViewDidDisappear = @"kUnityViewDidDisappear";
        NSString* const kUnityViewDidAppear = @"kUnityViewDidAppear";
        NSString* const kUnityViewWillDisappear = @"kUnityViewWillDisappear";
        NSString* const kUnityViewWillAppear = @"kUnityViewWillAppear";
        NSString* const kUnityInterfaceWillChangeOrientation = @"kUnityInterfaceWillChangeOrientation";
        NSString* const kUnityInterfaceDidChangeOrientation = @"kUnityInterfaceDidChangeOrientation";
        
      2. 修改 UnityViewControllerListener.mm 内容

        // 注释掉 extern 变量定义
        //DEFINE_NOTIFICATION(kUnityViewWillLayoutSubviews);
        //DEFINE_NOTIFICATION(kUnityViewDidLayoutSubviews);
        //DEFINE_NOTIFICATION(kUnityViewWillDisappear);
        //DEFINE_NOTIFICATION(kUnityViewDidDisappear);
        //DEFINE_NOTIFICATION(kUnityViewWillAppear);
        //DEFINE_NOTIFICATION(kUnityViewDidAppear);
        //DEFINE_NOTIFICATION(kUnityInterfaceWillChangeOrientation);
        //DEFINE_NOTIFICATION(kUnityInterfaceDidChangeOrientation);
        

        done. 编译就可以通过了. (感觉这种需要动到 内部源码 的事情就不靠谱, 可能有其他更好的方式)

  2. 在 [扩展 UnityAppController](#扩展 UnityAppController) 的基础上, 重写 UnityAppControllercreateUnityView 方法. 这个方法在 Classes/UI/UnityAppController+ViewHandling.h 头文件中

    // CustomAppController.mm
    #import "Classes/PluginBase/UnityViewControllerListener.h"
    #import "Classes/UI/UnityAppController+ViewHandling.h"
    
    @interface CustomAppController : UnityAppController
    @property (strong, nonatomic) MyPlugin* plugin;
    @end
    
    @implementation CustomAppController
    - (UnityView *)createUnityView {
        NSLog(@"--- iOSAppController.createUnityView");
        self.plugin = [MyPlugin new];
        UnityRegisterViewControllerListener(plugin); // 注册监听
        UnityView* unity_view = [super createUnityView];
        NSLog(@"--- unity_view: %@", unity_view);
      return unity_view;
    }
    @end
    
  3. done. 打印出的日志

    2021-02-19 03:11:26.920198+0800 testunityios[45199:3842542] --- iOSAppController.createUnityView // 这个接口调用的比 didFinishLaunchingWithOptions 更早
    2021-02-19 03:11:26.922306+0800 testunityios[45199:3842542] --- unity_view: <UnityView: 0x10645dc40; frame = (0 0; 375 812); layer = <CAMetalLayer: 0x281690800>>
    2021-02-19 03:11:26.930272+0800 testunityios[45199:3842542] --- MyPlugin.viewWillAppear
    2021-02-19 03:11:26.989400+0800 testunityios[45199:3842542] --- iOSAppController didFinishLaunchingWithOptions
    2021-02-19 03:11:27.052447+0800 testunityios[45199:3842542] --- MyPlugin.viewWillLayoutSubviews
    2021-02-19 03:11:27.095425+0800 testunityios[45199:3842542] --- MyPlugin.viewDidLayoutSubviews
    2021-02-19 03:11:27.959829+0800 testunityios[45199:3842542] --- MyPlugin.viewWillAppear
    2021-02-19 03:11:27.960434+0800 testunityios[45199:3842542] --- MyPlugin.viewWillDisappear
    2021-02-19 03:11:27.960855+0800 testunityios[45199:3842542] --- MyPlugin.viewWillDisappear
    2021-02-19 03:11:27.963680+0800 testunityios[45199:3842542] --- MyPlugin.viewWillAppear
    
    2021-02-19 03:11:28.264654+0800 testunityios[45199:3842542] --- MyPlugin.viewWillLayoutSubviews
    2021-02-19 03:11:28.264727+0800 testunityios[45199:3842542] --- MyPlugin.viewDidLayoutSubviews
    2021-02-19 03:11:28.264775+0800 testunityios[45199:3842542] --- viewDidLayoutSubviews.obj: <UnityDefaultViewController: 0x10645f3f0>
    2021-02-19 03:11:28.265655+0800 testunityios[45199:3842542] --- MyPlugin.viewDidDisappear
    2021-02-19 03:11:28.266328+0800 testunityios[45199:3842542] --- MyPlugin.viewDidAppear // 这个可以作为 unity view 加载完成的标记. 只会调用一次
    2021-02-19 03:11:28.266369+0800 testunityios[45199:3842542] --- viewDidAppear.obj: <UnityDefaultViewController: 0x10645f3f0>
    2021-02-19 03:11:28.266905+0800 testunityios[45199:3842542] --- rootVc: <UnityDefaultViewController: 0x10645f3f0>
    2021-02-19 03:11:28.267018+0800 testunityios[45199:3842542] --- rootVc.view: <UnityView: 0x10645dc40; frame = (0 0; 375 812); autoresize = W+H; layer = <CAMetalLayer: 0x281690800>> // 这个 根节点的 view 就是 unity_view, 地址一样
    2021-02-19 03:11:28.267078+0800 testunityios[45199:3842542] --- MyPlugin.viewWillLayoutSubviews
    2021-02-19 03:11:28.267116+0800 testunityios[45199:3842542] --- MyPlugin.viewDidLayoutSubviews
    

构建 xcode 工程的后处理

  • 使用PostProcessBuild设定Unity产生的Xcode Project - https://www.cnblogs.com/pandawuwyj/p/6904770.html

  • Unity导出已配置好的Xcode工程 - https://www.jianshu.com/p/51d6b8371e0f

  • Unity iOS 插件开发与SDK接入 - https://www.yangzhenlin.com/unity-ios-plugin/

  • Embed framework to Xcode project - https://forum.unity.com/threads/embed-framework-to-xcode-project.389211/

    string framework = Path.Combine(defaultLocationInProj, coreFrameworkName);
    string fileGuid = proj.AddFile(framework, "Frameworks/" + framework, PBXSourceTree.Sdk);
    PBXProjectExtensions.AddFileToEmbedFrameworks(proj, targetGuid, fileGuid);
    

开源后处理插件 XUPorter

  • GitHub - https://github.com/onevcat/XUPorter
  • blog - https://onevcat.com/2012/12/xuporter/

参数说明

  • group:The group name in Xcode to which files and folders will added by this projmods file
  • libs: Xcode Build Phases 里需要添加的 内置库, 如: libz.dylib. (如果想添加自己的 .a 库, 需要使用 files 添加)
  • frameworks:Xcode Build Phases 里需要添加的 内置 framework, 如: Security.framework. (如果想添加自己的 .framework 库, 需要使用 files 添加)
  • headerpaths:Header Search Paths in Build Setting of Xcode
  • files:需要添加的文件. (路径为 .projmods 文件的相对路径)
  • folders:Folders which should be added. All file and folders(recursively) will be added
  • excludes:Regular expression pattern. Matched files will not be added.
  • compiler_flags: Compiler flags which should be added, e.g. “-Wno-vexing-parse”
  • linker_flags: Linker flags which should be added, e.g. “-ObjC”
  • embed_binaries: 可选字段, 支持 XCode 6+ Embed Framework 特性. 添加的 framework 必须已经在 frameworksfiles 字段里.
    • 如果是使用的第三方库是 framework 形式, 则需要用到这个字段, 路径和 files 一样是相对路径. 可以将 framework 设置为 embed 状态. 不然 运行时 会报找不到 库错误.
    • !!! 貌似有 bug, 手动添加没问题, 用插件就有问题 !!! 所以这个字段还是不要用了, 自己打个补丁吧 !!!
  • plist: edit the Info.plist file, only the urltype is supported currently. in url type settings, name and schemes are required, while role is optional and is Editor by default.

踩坑

运行时报错 找不到库

错误: dyld: Library not loaded

找了网上的方法:在 Linked Frameworks and Libraries 中,把 framework 设为 Optional、或是修改Library search path,全是扯淡,完全不管用。

最后找到如下方法:

  1. TARGETS -> General -> Frameworks, Libraries, and Embedded Content 中,点击,把第三方 framework embed 状态置为 embed & sign

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqQzEN7L-1613716436055)(http://yxbl.itengshe.com/20210214191820-1.webp)]

    然后在 build phases 中就会显示出来 embed frameworks

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MaqS2YiE-1613716436056)(http://yxbl.itengshe.com/20210214192541-1.webp)]

参考: https://www.jianshu.com/p/fe8486cb33f8


猜你喜欢

转载自blog.csdn.net/yangxuan0261/article/details/113766453