- 摘要
本次内容为创建一个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插件
- 插件选型:
如何选型,我们可以看下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插件交互的全部内容,仅做记录使用