Unity - macOS App 插件 (一)

  • 摘要

本次内容为创建一个macOS端的SDK供Unity使用的过程记录,记录了关于SDK创建、交互、打包等过程及其遇到的问题。

  • Unity导出macOS的版本限制

   Unity 2018.4、2019.3以及更改的版本才支持Untiy 直接导出Xcode 工程(仅限于在mac 上导出),其他的版本只能导出app喽。

  • 制作插件有哪些资源可以使用

   参照Unity的说明文档 macOS 播放器:适用于 IL2CPP 的 C++ 源代码插件 可以看到,我们可以向iOS那样从Unity端向你的插件端(C++端)发送消息了,并且支持同步函数返回,

  • macOS的插件限制

   既然上面已经说过了,我们可以像iOS插件一样,从Unity端向你的插件端发送消息,那么我们是不是也可以使用Unity的UnitySendMessage反过来发消息尼?结论可能会让你失望了,Unity的Mac平台下库中并没有这个方法可以提供给你使用,

  • macOS 插件如何实现UnitySendMessage消息

   原理:我们可以参照iOS的block的原理,通过函数指针作为函数参数传递给你的使用方,使用方通过调用函数地址进行回调,

     优点:可以方便的进行函数的回调,缺点:你的函数指针的作用域必须时时存在,否则会出现野指针的问题

   实现:

1. 我们需要在C#端定义一个函数模型

1 ///--- 回调函数的定义,定义一个函数的类型
2 public delegate void callBackMac(string strType, string strData);

 

2. 在你的C#端定义一个函数实现

1 ///---- 回调函数实现
2 void UnityMethodCalledFromXcode(string strType,string strData)
3 {
4     Debug.Log(strType);
5     Debug.Log(strData);
6     ..... //这里执行你自己的处理内容
7 }

 

3. 在你的macOS 的代码中生命一个函数的block定义,用于接收和调用回调方法

1 ///---  函数回调的block方法,参数信息需要同C#端的定义保持一致
2 typedef void (*Unity_Callback1)(const char * msgData,const char * msgType);

 

4. 准备工作都做好了,我们将信息串联起

在macOS 插件中定义一个用于接收Unity消息的方法,方法定义可以参照macOS 播放器:适用于 IL2CPP 的 C++ 源代码插件的方法定义

 1 ///--- 定义接收SDK的方法
 2 //定义全局的变量,用于保存Untiy传递的回调函数地址
 3 extern "C"{
 4     Unity_Callback1 sCallback = NULL;
 5 }
 6 ///定义接收Unity回调的方法
 7 extern "C" void _MacSDKSetCallBack(Unity_Callback1 callbackFunc)
 8 {
 9     sCallback = callbackFunc;
10 }
11 ///也可以定义函数的参数作类型的
12 extern "C" void _MacSDKFunc1(const char * someThing,Unity_Callback1 callbackFunc)
13 {
14   ///在函数内容回调
15   callbackFunc(strdup([@"msgData ...." UTF8String]),strdup([@"msgType ...." UTF8String]));
16 }

 

 

在C#端如何使用

 1 ///在unity的Awake中设置Unity的回调方法
 2 private void Awake(){
 3   _MacSDKSetCallBack(UnityMethodCalledFromXcode);
 4 }
 5 
 6 ///在你需要的位置调用函数参数作为回调的方法
 7 _MacSDKFunc1("someThing",UnityMethodCalledFromXcode);
 8 
 9 ///定义设置Unity回调的方法   
10 [DllImport("__Internal")]
11 private static extern void _MacSDKSetCallBack(callBackMacSDK funcPoint);
12 
13 ///定义设置Unity函数回调
14 [DllImport("__Internal")]
15 private static extern void _MacSDKFunc1(string someThing ,callBackMacSDK funcPoint);

 

  • 插件实现

  以上原理的部分差不多讲完了,我们来看下如何用于实践创建出属于你自己的macOS插件

  1. 插件选型:

   如何选型,我们可以看下macOS下常用的几种库的类型,Framework、bundle、.a 等类型,untiy 对于插件类型的解释和加载方法可以参考 Plugin Inspector, 在实践中遇到了使用Framework 时,需要进行一些特殊的设置才能使用,所以我最后选型使用了bundle

  2. 创建一个macOS的bundel,

   打开xcode 选择创建一个项目--【macOS】--【Bundle】然后按顺序继续就可以创建一个你的Bundle了。测试创建了JYMacSDK.Bundle

  3. 创建交互的c++文件  

  JYUnityMacHelper.h文件定义

 1 //
 2 //  JYUnityMacHelper.h
 3 //  JYMacSDK
 4 //
 5 //  Created on 2021/8/23.
 6 //
 7 
 8 
 9 #import <Foundation/Foundation.h>
10 
11 NS_ASSUME_NONNULL_BEGIN
12 
13 typedef void (*Unity_Callback1)(const char * message,const char * msgType);
14 
15 @interface JYUnityMacHelper : NSObject
16 
17 @end
18 
19 NS_ASSUME_NONNULL_END
  

  JYUnityMacHelper.mm 文件定义
 1 //
 2 //  JYUnityMacHelper.m
 3 //  JYMacSDK
 4 //
 5 //  Created by zlongame on 2021/8/23.
 6 //
 7 
 8 #import "JYUnityMacHelper.h"
 9 
10 extern "C"{
11     extern NSString* CreateNSString (const char* string);
12     Unity_Callback1 sCallback = NULL;
13 }
14 
15 @implementation JYUnityMacHelper
16 
17 +(void)JYFunc2:(NSString *)someThing
18 {
19     if (sCallback) {
20         sCallback(strdup([@"MacSDKFunc2" UTF8String]),strdup([someThing UTF8String]));
21     }
22 }
23 
24 @end
25 
26 extern "C" NSString* CreateNSString (const char* string)
27 {
28     if (string)
29         return [NSString stringWithUTF8String: string];
30     else
31         return [NSString stringWithUTF8String: ""];
32 }
33 
34 ///定义接收Unity回调的方法
35 extern "C" void _MacSDKSetCallBack(Unity_Callback1 callbackFunc)
36 {
37     sCallback = callbackFunc;
38 }
39 
40 ///也可以定义函数的参数作类型的
41 extern "C" void _MacSDKFunc1(const char * someThing,Unity_Callback1 callbackFunc)
42 {
43     ///在函数参数回调
44     callbackFunc(strdup([@"MacSDKFunc1" UTF8String]),strdup(someThing));
45 }
46 
47 ///使用全局函数返回内容
48 extern "C" void _MacSDKFunc2(const char * someThing)
49 {
50     [JYUnityMacHelper JYFunc2:CreateNSString(someThing)];
51 }

 

4. 编译

 编译生成的JYMacSDK.Bundle文件复制到Unity工程的目录【Assets】--【Plugins】下

5. 使用

  JYMAC_SDK.cs 文件实现
 1 using UnityEngine;
 2 using System.Runtime.InteropServices;
 3 
 4 public class JYMAC_SDK : MonoBehaviour
 5 {
 6     public  static JYMAC_SDK     Manager         = null;
 7 
 8     ////////------------------  生命周期    ------------------
 9     // Start is called before the first frame update
10     void Start()
11     {
12 
13     }
14 
15     // Update is called once per frame
16     void Update()
17     {
18 
19     }
20 
21     private void Awake()
22     {
23         if (null == Manager)
24         {
25             Manager = this;
26 #if UNITY_STANDALONE_OSX
27             _MacSDKSetCallBack(UnityMethodCalledFromXcode);
28 #endif
29         }
30     }
31 
32     public void DoTestEventFunc1()
33     {
34         _MacSDKFunc1("someThing 1", UnityMethodCalledFromXcode);
35     }
36 
37     public void DoTestEventFunc2()
38     {
39         _MacSDKFunc2("someThing 2");
40     }
41     
42     ////////------------------  回调方法    ------------------
43     //回调方法
44     /// <summary>
45     /// 用来接收SDK的回调信息
46     /// </summary>
47     /// <param name="strType">回调类型</param>
48     /// <param name="strData">回调内容</param>
49     void UnityMethodCalledFromXcode(string strType,string strData)
50     {
51 #if UNITY_STANDALONE_OSX
52         Debug.Log(strType);
53         Debug.Log(strData);
54 #endif
55     }
56 
57     ////////------------------  和Mac SDK 的交互接口  ------------------
58     // mac SDK 的接口
59 #if UNITY_STANDALONE_OSX
60 
61     public delegate void callBackMacSDK(string strType, string strData);
62 
63     [DllImport("JYMacSDK")]
64     private static extern void _MacSDKSetCallBack(callBackMacSDK UnityMethodCalledFromXcode);
65 
66     [DllImport("JYMacSDK")]
67     private static extern void _MacSDKFunc1(string someThing,callBackMacSDK UnityMethodCalledFromXcode);
68 
69     [DllImport("JYMacSDK")]
70     private static extern void _MacSDKFunc2(string someThing);
71 
72 #endif
73 
74 }

 

 6. 测试内容

  创建两个Demo按钮按钮,

  按钮一链接事件调用DoTestEventFunc1

  按钮二链接事件调用DoTestEventFunc2

  运行并查看控制台日志,可以看到在

  调用DoTestEventFunc1 是日志显示为

 1 MacSDKFunc1
 2  #0 GetStacktrace(int)
 3  #1 DebugStringToFile(DebugStringToFileData const&)
 4  #2 DebugLogHandler_CUSTOM_Internal_Log(LogType, LogOption, ScriptingBackendNativeStringPtrOpaque*, ScriptingBackendNativeObjectPtrOpaque*)
 5  #3  (Mono JIT Code) (wrapper managed-to-native) UnityEngine.DebugLogHandler:Internal_Log (UnityEngine.LogType,UnityEngine.LogOption,string,UnityEngine.Object)
 6  #8 -[NSApplication(NSResponder) sendAction:to:from:]
 7  #9 _NSGestureRecognizerSendActions
 8  #10 -[NSGestureRecognizer _updateGesture]
 9  #11 ___NSGestureRecognizerUpdate_block_invoke
10  #12 _NSGestureRecognizerRemoveObjectsFromArrayAndApplyBlocks
11  #13 _NSGestureRecognizerUpdate
12  #14 -[NSWindow(NSGestureRecognizer_Routing) _sendEventToGestureRecognizers:requireAcceptsFirstMouse:]
13  #15 -[NSWindow(NSEventRouting) sendEvent:]
14  #16 -[NSApplication(NSEvent) sendEvent:]
15  #17 -[PlayerApplication sendEvent:]
16  #18 -[NSApplication _handleEvent:]
17  #19 -[NSApplication run]
18  #20 NSApplicationMain
19  #21 PlayerMain(int, char const**)
20  #22 main
21  #23 start
22 
23 someThing 1
24  #0 GetStacktrace(int)
25  #1 DebugStringToFile(DebugStringToFileData const&)
26  #2 DebugLogHandler_CUSTOM_Internal_Log(LogType, LogOption, ScriptingBackendNativeStringPtrOpaque*, ScriptingBackendNativeObjectPtrOpaque*)
27  #3  (Mono JIT Code) (wrapper managed-to-native) UnityEngine.DebugLogHandler:Internal_Log (UnityEngine.LogType,UnityEngine.LogOption,string,UnityEngine.Object)
28  #8 -[NSApplication(NSResponder) sendAction:to:from:]
29  #9 _NSGestureRecognizerSendActions
30  #10 -[NSGestureRecognizer _updateGesture]
31  #11 ___NSGestureRecognizerUpdate_block_invoke
32  #12 _NSGestureRecognizerRemoveObjectsFromArrayAndApplyBlocks
33  #13 _NSGestureRecognizerUpdate
34  #14 -[NSWindow(NSGestureRecognizer_Routing) _sendEventToGestureRecognizers:requireAcceptsFirstMouse:]
35  #15 -[NSWindow(NSEventRouting) sendEvent:]
36  #16 -[NSApplication(NSEvent) sendEvent:]
37  #17 -[PlayerApplication sendEvent:]
38  #18 -[NSApplication _handleEvent:]
39  #19 -[NSApplication run]
40  #20 NSApplicationMain
41  #21 PlayerMain(int, char const**)
42  #22 main
43  #23 start

 

 

调用DoTestEventFunc2 是日志显示为 

1 MacSDKFunc2
 2  #0 GetStacktrace(int)
 3  #1 DebugStringToFile(DebugStringToFileData const&)
 4  #2 DebugLogHandler_CUSTOM_Internal_Log(LogType, LogOption, ScriptingBackendNativeStringPtrOpaque*, ScriptingBackendNativeObjectPtrOpaque*)
 5  #3  (Mono JIT Code) (wrapper managed-to-native) UnityEngine.DebugLogHandler:Internal_Log (UnityEngine.LogType,UnityEngine.LogOption,string,UnityEngine.Object)
 6  #8 -[NSApplication(NSResponder) sendAction:to:from:]
 7  #9 _NSGestureRecognizerSendActions
 8  #10 -[NSGestureRecognizer _updateGesture]
 9  #11 ___NSGestureRecognizerUpdate_block_invoke
10  #12 _NSGestureRecognizerRemoveObjectsFromArrayAndApplyBlocks
11  #13 _NSGestureRecognizerUpdate
12  #14 -[NSWindow(NSGestureRecognizer_Routing) _sendEventToGestureRecognizers:requireAcceptsFirstMouse:]
13  #15 -[NSWindow(NSEventRouting) sendEvent:]
14  #16 -[NSApplication(NSEvent) sendEvent:]
15  #17 -[PlayerApplication sendEvent:]
16  #18 -[NSApplication _handleEvent:]
17  #19 -[NSApplication run]
18  #20 NSApplicationMain
19  #21 PlayerMain(int, char const**)
20  #22 main
21  #23 start
22 
23 someThing 2
24  #0 GetStacktrace(int)
25  #1 DebugStringToFile(DebugStringToFileData const&)
26  #2 DebugLogHandler_CUSTOM_Internal_Log(LogType, LogOption, ScriptingBackendNativeStringPtrOpaque*, ScriptingBackendNativeObjectPtrOpaque*)
27  #3  (Mono JIT Code) (wrapper managed-to-native) UnityEngine.DebugLogHandler:Internal_Log (UnityEngine.LogType,UnityEngine.LogOption,string,UnityEngine.Object)
28  #8 -[NSApplication(NSResponder) sendAction:to:from:]
29  #9 _NSGestureRecognizerSendActions
30  #10 -[NSGestureRecognizer _updateGesture]
31  #11 ___NSGestureRecognizerUpdate_block_invoke
32  #12 _NSGestureRecognizerRemoveObjectsFromArrayAndApplyBlocks
33  #13 _NSGestureRecognizerUpdate
34  #14 -[NSWindow(NSGestureRecognizer_Routing) _sendEventToGestureRecognizers:requireAcceptsFirstMouse:]
35  #15 -[NSWindow(NSEventRouting) sendEvent:]
36  #16 -[NSApplication(NSEvent) sendEvent:]
37  #17 -[PlayerApplication sendEvent:]
38  #18 -[NSApplication _handleEvent:]
39  #19 -[NSApplication run]
40  #20 NSApplicationMain
41  #21 PlayerMain(int, char const**)
42  #22 main
43  #23 start

 

  •  结论

  以上为本次macOS插件交互的全部内容,仅做记录使用

 

上一篇:【Unity3D 灵巧小知识点】 ☀️ | Unity中如何使用代码切换场景


下一篇:[2021.10.25]<呆头熊的开发日记>UI界面&场景切换