赞
踩
大约一个月前收到领导新布置的任务,要用Unity直接接入发行方ios sdk。当时我一下子就懵了,ios的Object-c没接触过啊,Unity和ios该怎么交互呀,完全什么都不懂。接到消息的那一刻整个人状态都不好了,查阅了很多资料完全没有头绪也看不进去任何有关OC的基础知识。还好有我们部门大杨哥耐心的讲了一遍怎么弄。经过大杨锅的讲解还有Google理解出来的一些知识,现已完整的对接完好几个IOS 的SDK的接入工作。其实ios sdk接入并没有你想的那么难,接下来我会举例说明,跟大家分享一下我学到的东西,让新手同学不要跟我一样上来就懵。
Unity与IOS交互层C#代码编写
Unity接 iOS SDK你需要了解的Objective-C基础知识
Unity 与IOS交互工作原理
ios 小7手游sdk接入演示
Unity :Unity 19.4.2f1 Personal
Xcode : Xcode 12.2
Unity 广泛的支持原生插件,即用 C、C++、Objective-C 等编写的原生代码库。插件允许游戏代码(用 C# 编写)调用这些库中的函数。
为了使用原生插件,首先需要使用基于 C 的语言编写函数来访问所需的功能并将它们编译到库中。在 Unity 中,还需要创建一个 C# 脚本来调用本机库中的函数。
原生插件应提供一个简单的 C 接口供 C# 脚本随后向其他用户脚本公开。当某些低级渲染事件发生时(例如,创建图形设备时),Unity 也可以调用原生插件导出的函数。现在编写一下C#的脚本,此代码来源于实际项目部分截取。
using System.Runtime.InteropServices; namespace GameChannel { public class ChannelManager : Singleton<ChannelManager> { #if UNITY_IOS //c#中宏的概念 ,意思是当前平台是iOS [DllImport("__Internal")] private static extern void SDk_Login(); //登录 [DllImport("__Internal")] private static extern void SDk_Logout();//注销 [DllImport("__Internal")] private static extern void SDk_SwitchAccount();//切换账号(可选参数) [DllImport("__Internal")] private static extern void SDk_Pay(string payData);//支付 [DllImport("__Internal")] private static extern void SDk_Data(string thisdata); //向渠道发送游戏数据 //当前平台是安卓,交互层插件对外调用名称是通用的,但是方式上是略有不同用宏的概念做区分。 #elif UNITY_ANDROID } }
对上述代码做下说明:在 iOS 上,插件以静态方式链接到可执行文件中,因此我们必须使用“ __Internal” 作为库名。其他的平台会通过动态的方式加载插件 [DllImport (“PluginName”)]名称。C#调用其他模块的接口都是通过DllImport的方式来实现的。例如c#定义了void SDk_Login()方法,在ios的object-c中 也一定有void SDk_Login()方法。
Unity项目开发,iOS平台要接SDK的话,就需要写Objective-C原生代码的,对于没使用过Objective-C的小伙伴不要慌。我一说你就懂了。
.h : 头文件作为一种包含功能函数、数据接口声明的载体文件,主要用于保存程序的声明,而定义文件用于保存程序的实现
.m : 它是对.h头文件中方法的实现,外部不能访问
.mm : 源代码文件。和.m文件类似,唯一的不同点就是,除了可以包含Objective-C和C代码以外,还可以包含C++代码。
当你需要在源代码中包含头文件的时候,你可以使用#include编译选项也可以使用#import ,但是OC官方更推荐的方法是:#import。这个跟java的import 导包思想上非常相似。
例如 #import “UnityIos.h” 和 #import <Foundation/Foundation.h>两种。使用""引入的是本地工程的文件,而使用<>引入的是系统库的文件。
@interface是类为对象提供特性描述(接口),@implementation是对@interface定义接口具体的实现。这两个跟java中的接口的定义与实现上思想上是一致的。
加号(+)的方法为类方法,这类方法是可以直接用类名来调用的。
减号(-)的方法为实例方法,必须使用这个类的实例才可以调用它。
NSLog打印日志。如 NSlog(@"")
NSString : 字符串
CGfloat : 浮点值的基本类型
NSInteger : 整型
BOOL : 布尔型
Unity和OC要传递数据,常用的就是json格式。但是OC还和java的不一样。
//json字符串转化成字典
-(NSDictionary*)getJsonDic:(NSString*)jsonString{
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:nil];
}
//字典转化成json字符串
-(NSString*)arrayToJson:(NSMutableDictionary *)dic{
NSError *parseError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&parseError];
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
这些知识仅做作为基础了解,其他详细的用法大家可以找下Google和度娘
熟悉工作原理前大家不妨看一下之前写的博客:Unity 导出Xcode 项目的结构 作为一个了解,每个Unity ios Xcode 项目都会有如下结构:
UnityFramework 库文件部分,其中包含源、插件和相关框架。它还生成 UnityFramework.framework 文件。
Unity-iPhone 主启动器部分,其中包含应用程序表示数据并会运行该库。Unity-iPhone 目标对 UnityFramework 目标具有单一依赖关系。
要将 Unity 集成到另一个 Xcode 项目中,必须将两个 Xcode 项目(原生项目和 Unity 生成的项目)合并到一个 Xcode 工作空间中,并将 UnityFramework.framework 文件添加到原生 Xcode 项目的应用程序 (Application) 目标的嵌入式二进制文件 (Embedded Binaries) 中。完成此操作后,可以使用 UnityFramework 类来控制 Unity 运行时。Unity直接导出Xcode工程目录结构如下图:
要想了解原理就要找到程序的入口,熟悉OC或者C的朋友一定知道main方法,这是整个程序的入口。我们先看下MainApp/main.mm,这个文件做了什么呢。
从代码中大概读懂的意思将/Frameworks/UnityFramework.framework库文件加载到应用程序中。然后看下UnityFramework/UnityFramework.h,通过UnityFramework Objective-C 类(该类是 UnityFramework.framework 的主体类)的实例来控制 Unity 运行时:其中的属性方法我罗列一下。
+(UnityFramework*)getInstance :单例类方法,可将实例返回到 UnityFramework。
-(UnityAppController*)appController :返回 UIApplicationDelegate 的 UnityAppController 子类。这是原生端的根 Unity 类,可以访问应用程序的视图相关对象,例如 UIView、UIViewControllers、CADisplayLink 或 DisplayConnection。
-(void)setDataBundleId:(const char*)bundleId:设置捆绑包,Unity 运行时应在其中查找 Data 文件夹。应在调用 runUIApplicationMainWithArgc 或 runEmbeddedWithArgc 之前调用此方法。
-(void)runUIApplicationMainWithArgc:(int)argc argv:(char*[])argv:从没有其他视图的主要方法中运行 Unity 的默认方式。
-(void)runEmbeddedWithArgc:(int)argc argv:(char*[])argv appLaunchOpts:(NSDictionary*)appLaunchOpts:存在其他视图时,如果需要运行 Unity,需要调用此方法。
-(void)unloadApplication :调用此方法可卸载 Unity,并在卸载完成后接收对 UnityFrameworkListener 的回调。Unity 将释放占用的大部分内存,但不会全部释放。
-(void)registerFrameworkListener:(id)obj :注册监听器对象,用于接收 UnityFramework 生命周期相关事件的回调。
-(void)unregisterFrameworkListener:(id)obj:取消注册监听器对象。
-(void)showUnityWindow:在显示非 Unity 视图时调用此方法,也会显示已经在运行的 Unity 视图。
-(void)pause:(bool)pause:暂停 Unity
-(void)setExecuteHeader:(const MachHeader*)header:必须在运行 Unity 之前调用此命令,CrashReporter 才能正常工作。
-(void)sendMessageToGOWithName:(const char*)goName functionName:(const char*)name message:(const char*)msg:此方法是 UnitySendMessage 的代理。它通过名称查找游戏对象,并使用单字符串消息参数来调用 functionName。
(void)quitApplication:(int)exitCode:调用此方法可完全卸载 Unity,并在 Unity 退出后接收对 UnityFrameworkListener 的回调。Unity 将释放所有内存。
注意:进行此调用后,将无法在同一进程中再次运行 Unity。可在 AppController 上设置 quitHandler 以覆盖默认进程终止
然后再看下Classes/main.mm,这个文件做了什么,根据代码得知(UIApplicaitonMain方法),程序需要创建UnityAppController对象,也就是说UnityAppController.mm才是真正的程序入口。
正常情况下要接入小7苹果sdk,公司商务或者运营会提供相应的参数和接入文档。本文演示不提供文档和参数。
进入正题把小7 ios对应的库文件加入进来,首先工程Libraries 创建一个文件夹(例如SDK文件夹),把小7依赖所有库放到创建的SDK文件夹下,然后Libraries右键add files to Unity-iPhone …把文件添加进来(下图是添加后的图),xcode 会自动把小7的文件添加到对应的库和引用文件上。
按小7的文档要求配置好info.plist文件然后需要设置的属性也都弄好,准备工作就完事,然后进行下一步。接入sdk其实可以在UnityAppController.mm文件中进行的。但是为了清晰,创建一个UnityIos.m(UnityIos.h可忽略)外部引用放在Libraries/SDK文件夹下。
有同学会奇怪我在Unity c#层定义好了例如登陆的方法,也没看到在OC中调用啊?怎么拉起登陆啊。年轻人勿要着急继续看。在UnityIos.m中我们定义一个和c#层SDk_Login()名一样的方法体。请看如下代码:(这一部分代码是真实项目中部分截取,其中包含了小7 sdk 完整的登陆 支付 切换账号等功能
#import "UnityIos.h" #import <Foundation/Foundation.h> #import <AdSupport/AdSupport.h> #import <SMSDK/SMSDK.h> #import "UnityAppController.h" #import "UnityInterface.h" @implementation UnityIos //调用sdk登陆 void SDk_Login(){ NSLog(@"SDk_Login"); [SMSDK smLogin]; } //调用sdk切换账号功能 void SDk_Logout(){ NSLog(@"SDk_Logout"); [SMSDK smLogout]; } //这个方法只是为了兼容sdk void SDk_SwitchAccount(){ NSLog(@"SDk_SwitchAccount"); } //调用sdk支付,游戏传入sdk需要的参数数据(游戏客户端协定好字段要统一) void SDk_pay(void *payData){ NSLog(@"SDk_pay"); //直接传json,oc无法识别所以要进行一个转化 NSString *idList = [NSString stringWithUTF8String:payData]; NSData *jsonData = [idList dataUsingEncoding:NSUTF8StringEncoding]; NSError *err; NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err]; if(err) { NSLog(@"json解析失败:%@",err); return; } NSLog(@"dic解析:%@",dic); NSString *_price = [NSString stringWithFormat:@"%@",[dic valueForKey:@"price"]]; NSString *_p_level = [NSString stringWithFormat:@"%@",[dic valueForKey:@"playerlevel"]]; NSString *_game_sign= [NSString stringWithFormat:@"%@",[dic valueForKey:@"game_sign"]]; NSString *_subject= [NSString stringWithFormat:@"%@",[dic valueForKey:@"subject"]]; NSString *_game_area= [NSString stringWithFormat:@"%@",[dic valueForKey:@"area"]]; NSString *_game_role_id= [NSString stringWithFormat:@"%@",[dic valueForKey:@"roleId"]]; NSString *_game_role_name= [NSString stringWithFormat:@"%@",[dic valueForKey:@"roleName"]]; NSString *_game_guid= [NSString stringWithFormat:@"%@",[dic valueForKey:@"guid"]]; SMPayInfo *payInfo = [[SMPayInfo alloc] init]; payInfo.game_orderid =[NSString stringWithFormat:@"%@",[dic valueForKey:@"orderid"]]; //游戏订单号 60个字符 payInfo.game_sign =_game_sign; //服务器返回的签名 payInfo.game_price =_price; //价格单位:元 payInfo.subject =_subject; //道具简介 payInfo.game_area =_game_area; //角色所在区服 payInfo.game_level =_p_level; //角色等级 payInfo.game_role_id =_game_role_id; //角色ID payInfo.game_role_name =_game_role_name; //角色名称 payInfo.notify_id = @"-1"; //回调通知ID 默认可以在后台填写 payInfo.extends_info_data = @""; //自定义扩展数据 payInfo.game_guid=_game_guid; //游戏登陆后服务器通过token解析拿到的guid //調用支付接口 [SMSDK smPayWithNewPayInfo:payInfo]; } // 把格式化的JSON格式的字符串转换成字典 // @param jsonString JSON格式的字符串 // @return 返回字典 - (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString { if (jsonString == nil) { return nil; } NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; NSError *err; NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err]; if(err) { NSLog(@"json解析失败:%@",err); return nil; } return dic; } @end
上面的代码看完了。还会有点小困惑,既然在Libraries/SDK/UnityIos.m文件下,OC是如何找到登陆方法的呢?UnityIos.m定义了SDk_Login(),这个方法在Classes/Native中能找到解释,我给大家看一个图啊.
这个图就是在Unity c#层定义好的同名方法,编译成了c++代码放到了Xcode工程路径下,成了沟通OC和c#的桥梁。游戏用户点击登陆按钮即可调用OC的登陆方法完成登陆的客户端流程。
至于回调为什么放在UnityAppController.mm里,有两个点,其一小7 sdk设计问题,要做个全局回调我不知道怎么弄,其二是游戏把初始化放在生命周期哪里处理的,不这样写我也没什么好办法,毕竟我不是一个真正的ios开发者。正常的接sdk方法和回调都可以写在UnityIos.m文件里面的。小7比较特殊啊所以这样写。到这里接入结束喽,回调和相关代码放在下面了。
#import <SMSDK/SMSDK.h> #define SMSDKAppKey @"小7后台申请的appKey" @implementation UnityAppController //展示部分核心需要部分 - (BOOL)application:(UIApplication*)app openURL:(NSURL*)url options:(NSDictionary<NSString*, id>*)options { id sourceApplication = options[UIApplicationOpenURLOptionsSourceApplicationKey], annotation = options[UIApplicationOpenURLOptionsAnnotationKey]; NSMutableDictionary<NSString*, id>* notifData = [NSMutableDictionary dictionaryWithCapacity: 3]; if (url) notifData[@"url"] = url; if (sourceApplication) notifData[@"sourceApplication"] = sourceApplication; if (annotation) notifData[@"annotation"] = annotation; AppController_SendNotificationWithArg(kUnityOnOpenURL, notifData); return [SMSDK handleApplication:app openURL:url sourceApplication:[options valueForKey:@"UIApplicationOpenURLOptionsSourceApplicationKey"] annotation:[options valueForKey:@"UIApplicationOpenURLOptionsAnnotationKey"]]; } //生命周期启动时调用sdk初始化方法(ios的生命周期跟安卓概念差不多) - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { //省略了部分代码 //设置全局回调 //初始化 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SMSDKInitCallback:) name:SMSDKInitDidFinishNotification object:nil]; //登陆 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SMSDKLoginCallback:) name:SMSDKLoginNotification object:nil]; //注销 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SMSDKLogoutCallback:) name:SMSDKLogoutNotification object:nil]; //支付 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SMSDKPayResultCallback:) name:SMSDKPayResultNotification object:nil]; //使用appKey初始化SDK [SMSDK smInitWithAppKey:SMSDKAppKey]; return YES; } //初始化回调 - (void)SMSDKInitCallback:(NSNotification *)notify { if (notify.object == kSMSDKSuccessResult) { NSLog(@"初始化成功"); } else if (notify.object == kSMSDKFailedResult) { NSLog(@"初始化失敗"); [SMSDK smInitWithAppKey:SMSDKAppKey]; //初始化失敗可以重新初始化 } } //登陆回调 - (void)SMSDKLoginCallback:(NSNotification *)notify { NSLog(@"SMSDKLoginCallback"); if (notify.object == kSMSDKSuccessResult) { NSLog(@"login callback success"); NSString *idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; NSString *deviceUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; NSString *deviceModel = [[UIDevice currentDevice] model]; NSString *token = notify.userInfo[kSMSDKLoginTokenKey]; NSLog(@"解析token========:%@",token); //这一步是和服务器协定需要的参数,这里不做真实展示每个游戏逻辑都不一样 NSDictionary *resultDict=@{@"":@1,@"":@"",@"":"",@"token":token,@"deviceUdid":deviceUUID,@"deviceId":deviceModel,@"idFa":idfa }; NSData *data = [NSJSONSerialization dataWithJSONObject:resultDict options:NSJSONWritingPrettyPrinted error:nil]; NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"json解析:%@",string); //向 Unity发送数据 // 参数1绑定的场景 参数2 为对象上的脚本的一个成员方法名称(脚本名称不限制) //参数3传递的json数据。这个跟安卓的一样。 UnitySendMessage("GameLaunch","OnSdkLoginSuc",[string cStringUsingEncoding:NSASCIIStringEncoding]); } else { NSLog(@"login callback fail"); } } //切换账号回调 - (void)SMSDKLogoutCallback:(NSNotification *)notify { NSLog(@"LogoutCallback"); } //支付结果回调 - (void)SMSDKPayResultCallback:(NSNotification *)notify { //支付結果 if (notify.object == kSMSDKSuccessResult) { //支付成功,刷新用戶數據 NSLog(@"支付成功"); } else if (notify.object == kSMSDKUserCancelResult) { NSLog(@"支付取消"); } else if (notify.object == kSMSDKFailedResult) { //支付錯誤,刷新用戶數據,保障不漏單 NSString *errMsg = notify.userInfo[kSMSDKErrorShowKey]; errMsg = errMsg && [errMsg isEqualToString:@""] ? errMsg : @"支付失敗"; NSLog(@"支付错误"); } }
为了方便只展示结果,展示初始化成功,意味着 sdk初始化接入是没问题的
应标题那句话接入ios SDK没有你想的那么难,这不是噱头这是我真实的感受。我是一个搞安卓SDK的程序员,ios里面的很多思想都是和安卓Java 相通的文中我也做过解释。只要你会用安卓接sdk,那么ios也不是那么难。大家要是有兴趣可以看下Android sdk接入Unity 与 Android交互通信 之OPPO篇。
刚开始弄的时候,我承认我非常无助,找了oc语法大全。看了一会就看不下了,即使看了一会也就忘了。等真正去操作的时候(实践才是正道,光看知识点一会就忘),发现真没那么难。思想上跟安卓接sdk一样的,就是语法略微不同。稍微查一下就知道怎么搞了。如果文章对你有帮助留下一个赞呗,你的支持是我继续写下去的动力。
写博文不易,希望大家多多支持,如有不对大家多多指正。写出来就是记录、学习和成长的过程。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。