[原创]cocos2d-x + Lua接入iOS原生SDK的实现方案

相信很多朋友在使用cocos2d-x+lua开发游戏时都遇到过接入iOS原生SDK的问题,比如常见的接应用内支付SDK,广告SDK或是一些社交平台SDK等等,我也没少接过这类SDK。这篇文章主要是对我做过项目中接入iOS原生SDK实现方案的一个总结,在这里分享给大家,希望对自己和大家的开发工作都有帮助。

在展开正文之前,先做几点说明:

1.我这里说的iOS原生SDK是指那些完全用Objective-C语言开发,为原生iOS程序设计的SDK。swift很好很强大,不过我还没用过,惭愧,不过语言终归只是表达方式而已,解决问题的思路都是一样的;

2.这里假设游戏的主要逻辑使用lua实现,对于主要逻辑使用C++实现的,用本文的思路一样可行,并且设计上更简单,接着往下看就知道了:)

3.本文以quick-cocos2d-x 2.1版本为例进行讲解,主要因为这个是我之前做项目用得最多的一个版本,-x新版本变动比较大,但是,还是那句话,解决问题的思路是相同的。

-------------------正式开始的分割线-------------------

好了,我们正式开始。开门见山!

解决这种接入问题,实际上最主要是解决不同语言交互的问题,一旦跨过语言交互的障碍,剩下的事情就so easy!

由于涉及到Lua,C++,Objective-C三种语言,这个问题表面上看起来错综复杂,但其实只要冷静地(=。=)梳理,我们便可以得到正确的思路。

因为我们游戏的逻辑主要是用Lua实现的(前面已经做过假设),而SDK是用Objective-C实现,所以这里我们需要解决Lua与Objective-C的交互问题,即最终希望达到的目标是,在Lua层面“调用”Objective-C的代码(注意这里的调用是加引号的,间接的调用),而当Objective-C层面收到SDK的回调,再通知Lua。我们知道,Lua并没有简单的方法直接和Objective-C交流,但是Lua可以通过Lua Binding和C/C++交流,而我们又知道,C++和Objective-C可以混编,即C++可以直接调用(这里调用没引号,是真的直接调用)Objective-C的代码。想到这里,思路就很明显了,我们可以使用C++为Lua和Objective-C的交互充当桥梁,进而实现Lua到Objective-C的交互。

根据上面的分析,我们可以用如下图表达我们的思路,我们这里将语言交互的过程分成了4个小部分:

整个语言交互的过程可以总结为:Lua调用Lua Binding的C++接口,C++接口调用混编的Objective-C接口,而Objective-C通过block形式的回调,将结果通知给C++,C++通过Lua的C API将最终结果返回给Lua。这样一趟下来,就完成了Lua与Objective-C的整个交互过程。

简单的说一下这4部分:

1.Lua Binding

将C/C++接口导出给Lua调用的方法,由于篇幅的原因这里就不展开了,具体可以参考Lua的文档,以及网上其他地方的文章。

2.混编

Objective-C的一大优点就是可以和C与C++混编使用,就像同一个语言一样共存在一个实现文件里面。具体混编规则也不说了,这里只提两个小细节:

一,在XCode下混编的实现文件后缀是.mm,而不能是.cpp或者是.m;

二,混编的实现文件引用头文件的地方,C++或者C的用#include,而Objective-C用#import,相互没有影响。

3.Block回调

Block是Objective-C一个非常棒的特性,更棒的是在Block里面还可以直接写C++代码:)具体想了解的可以看苹果官方文档

其实在最初,我曾经尝试过使用发送通知的方式来实现Objective-C对C++的回调,即Objective-C收到SDK回调,给C++部分发送附带回调信息的通知,虽然cocos2d-x中有现成的NotificationCenter来帮助实现,但这种方式的一个显而易见的弊端是大大增加了C++代码和Objective-C代码的耦合度,Objective-C部分也要混编C++调用C++的NotificationCenter发通知,C++部分也要混编Objective-C代码,调用C++的NotificationCenter收通知,这种结构实在是有够烦躁的。

相比之下,使用Block回调就干净利落太多,Objective-C这边一切都是纯粹的,它并不需要知道自己要被C++调用还是Objective-C调用,也不需要花很多精力在返回回调上,只需要干好自己的本职工作,然后在适当的时候调用Block就一切搞定。

4.Lua C API

Lua C API用于C/C++与Lua的交互,在cocos2d-x中这些C API已经被封装成了更加易用的C++ Class API。这里要提到的是,在用这套API调用Lua函数的时候,为了传参,需要参数入栈的操作,这个入栈的顺序影响到了Lua函数接受到参数的顺序,不过好在规则很简单:先入栈的参数排在前或者说是入栈顺序和实参顺序相同。举例,如果C++这边调用Lua函数func时,入栈的顺序是A,B,C,那么就是调用函数func(A,B,C)

-------------------渐入佳境的分割线-------------------

在整个语言交互的过程中,如果认为Lua是顶层,Objective-C是底层,那么在实际游戏中交互的过程就是一个自顶向下的过程,然而我们在实现各层级代码的时候,需要自底向上完成,因为在顶层Lua代码中的逻辑,是由底层Objective-C SDK的接口与功能决定的。即我们需要先根据SDK中原始的Objective-C的接口,做适合我们游戏Objective-C封装代理类,然后根据封装结果实现C++的bridge接口,最后再实现Lua的对应逻辑

根据以上分析,从层级的角度,设计如下:

除过Lua的逻辑,我们最重要需要实现的两部分内容:C++ Bridge Class 和 Objective-C SDK Delegate Class。前者是起桥梁作用的接口类,原则上不做任何与游戏逻辑相关的数据处理,而后者负责封装原始的SDK接口,接收以及初步处理SDK回调数据。前者的实现依赖于后者的实现,而后者的实现又依赖于SDK。SDK取得的数据最终通过层层传递,交给Lua逻辑处理,最终保证对数据处理的游戏逻辑尽可能多的放到Lua层中。

这样设计的好处有很多,一方面,顶层的游戏逻辑变动,不影响下层多语言交互代码,另一方面,底层的SDK变动,如版本更新甚至更换,不影响上层游戏逻辑,多层次结构有效地降低了复杂度,隔离了变化,对于频繁的需求变更,这种结构也可以保证扩展的便利。

-------------------总结的分割线-------------------

综上所述,解决接入iOS原生SDK的问题,主要需要4步:

1.根据SDK接口与功能实现Objective-C SDK Delegate Class;

2.根据Objective-C SDK Delegate Class实现对应的C++ Bridge Class;

3.根据C++ Bridge Class生成对应的Lua Binding代码;

4.写Lua层逻辑。

-------------------最后的分割线-------------------

好了,最后,又到了激动人心的上代码的环节了:)下面就以某iOS第三方计费SDK为例,来说明下实现接入的步骤。

这个SDK只有一个头文件GameBilling.h,主要使用到的方法和Protocol如下:(为了避免篇幅过长等原因,把注释和不必要的代码都删掉了)。我用代码注释的方式说明了各方法的用途。

 1 // 初始化计费SDK
 2 + (GameBilling *)initializeGameBilling;
 3
 4 // 告诉SDK游戏屏幕的Orientation,以便SDK展示正确的UI
 5 -  (void)setDialogOrientationMask:(UIInterfaceOrientationMask)orientationMask;
 6
 7 // 确认付费,显示付费UI
 8 - (void)doBillingWithUIAndBillingIndex:(NSString *)billingIndex isRepeated:(BOOL)isRepeated cpParam:(NSString*)cpParam;
 9
10 // Delegate回调,告诉调用者付费是否成功等信息
11 @protocol GameBillingDelegate<NSObject>
12 @required
13 - (void)onBillingResult:(BillingResultType)resultCode billingIndex:(NSString *)index message:(NSString *)message;
14 @end

以上前两个方法用于初始化SDK,并且和游戏的逻辑没什么太大关系,所以我们把对他们的调用放在程序开始的位置,不必导出给Lua。第三个方法在用户确认付费时使用,需要导出给Lua,当用户在游戏界面做相应操作时候调用。最后的delegate的回调,我们用前面提到的Objective-C SDK Delegate Class来接收,并作初步处理,再用Block传给C++ Bridge Class.

好的,那我们先来完成Objective-C SDK Delegate Class。这里这个Objective-C做成了个简单的单例来使用,实际可能不需要这么做。

先完成头文件,这里命名为CMGCIAPiOS.h,如下:

 1 #import "GameBilling.h"
 2
 3 // 声明Block
 4 typedef void (^BillingResultCallback)(BOOL success, NSString *index,NSString *message);
 5
 6 @interface CMGCIAPiOS : NSObject<GameBillingDelegate>
 7 {
 8     GameBilling *_sdk;
 9     NSString *_billingIndex;
10     BillingResultCallback _callback;
11 }
12
13 +(id)sharedInstance;
14
15 -(void)setDialogOrientationMask:(UIInterfaceOrientationMask)orientationMask;
16
17 -(void)doBillingWithUIAndBillingIndex:(NSString *)billingIndex
18                            isRepeated:(BOOL)isRepeated
19                               cpParam:(NSString*)cpParam
20                        resultCallback:(BillingResultCallback)callback;
21
22 @end

应该很清楚,就不多做说明了。

下面是实现文件CMGCIAPiOS.m,如下:

 1 #import "CMGCIAPiOS.h"
 2
 3 @implementation CMGCIAPiOS
 4
 5 static CMGCIAPiOS *_sharedInstance = nil;
 6
 7 + (id)sharedInstance
 8 {
 9     @synchronized(self)
10     {
11         if (_sharedInstance == nil)
12         {
13             _sharedInstance = [[CMGCIAPiOS alloc] init];
14         }
15     }
16     return _sharedInstance;
17 }
18
19 -(id) init
20 {
21     if( (self = [super init]) )
22     {
23         _sdk = [GameBilling initializeGameBilling];
24         _sdk.delegate = self;
25     }
26     return self;
27 }
28
29 -(void)setDialogOrientationMask:(UIInterfaceOrientationMask)orientationMask
30 {
31     [_sdk setDialogOrientationMask:orientationMask];
32 }
33
34 - (void)doBillingWithUIAndBillingIndex:(NSString *)billingIndex
35                             isRepeated:(BOOL)isRepeated
36                                cpParam:(NSString*)cpParam
37                         resultCallback:(BillingResultCallback)callback
38 {
39
40     if (_callback != nil)
41     {
42         [_callback release];
43         _callback = nil;
44     }
45
46     _callback = [callback copy];    // 注意要copy
47
48     [_sdk doBillingWithUIAndBillingIndex:billingIndex
49                               isRepeated:isRepeated
50                                  cpParam:cpParam];
51 }
52
53 #pragma mark - GameBillingDelegate
54 - (void)onBillingResult:(BillingResultType)resultCode
55            billingIndex:(NSString *)index
56                 message:(NSString *)message
57 {
58     BOOL b = (resultCode == BillingResultType_PaySuccess || resultCode == BillingResultType_PaySuccess_Activated);
59     NSLog(@"billing = %@ %@ %@", b ? @"yes":@"no", index, message);
60
61     if (_callback != nil)
62     {
63         _callback(b,index,message);
64
65         // 调用完成就释放掉
66         [_callback release];
67         _callback = nil;
68     }
69 }
70
71 @end

可以看到对提到的几个方法都做了封装,并且接收了回调。

下面是C++ Bridge Class部分,头文件CMGCIAP.h:

 1 #include <iostream>
 2
 3 class CMGCIAP
 4 {
 5 public:
 6     CMGCIAP();
 7     ~CMGCIAP();
 8
 9 public:
10     static CMGCIAP *sharedInstance();
11
12     bool init();
13
14     void setDoBillingCallbackScriptHandler(int scriptHandler); // for lua callback
15
16     void doBillingWithUI(const char* billingIndex,
17                          bool isRepeated,
18                          const char* cpParam);
19
20 private:
21
22     int m_doBillingCallbackScriptHandler;
23
24 };

由于用cocos2d-x的tolua工具做Lua Binding的原因,我把设置Lua回调的方法单独提出来了,如下:

1 void setDoBillingCallbackScriptHandler(int scriptHandler);

更好的做法是把这个scriptHandler放到下面这个函数中,这样接口就可以和Objective的保持一致了。

1 void doBillingWithUI(const char* billingIndex,
2                      bool isRepeated,
3                      const char* cpParam);

不过也没关系,独立设置Lua回调函数也有更灵活的优点。

注意,C++ Bridge Class头文件一定保持“纯洁性”,做纯粹的C++文件,不能出现Objective-C的任何代码,否则就破坏了上面讲到的层次结构。

下面是实现文件CMGCIAP.mm:

 1 #include "CMGCIAP.h"
 2 #include "cocos2d.h"
 3 #include "script_support/CCScriptSupport.h"
 4
 5 #import "CMGCIAPiOS.h"
 6 #import <Foundation/Foundation.h>
 7 #import <UIKit/UIKit.h>
 8
 9 USING_NS_CC;
10
11 static CMGCIAP* s_sharedInstance = NULL;
12
13 CMGCIAP::CMGCIAP()
14 {
15     m_doBillingCallbackScriptHandler = 0;
16 }
17
18 CMGCIAP::~CMGCIAP()
19 {
20
21 }
22
23 CMGCIAP *CMGCIAP::sharedInstance()
24 {
25     if (s_sharedInstance == NULL)
26     {
27         s_sharedInstance = new CMGCIAP();
28     }
29
30     return s_sharedInstance;
31 }
32
33 // init方法封装了对SDK的初始化
34 bool CMGCIAP::init()
35 {
36     // 由于是竖屏的游戏,所以这里直接设置好了
37     [[CMGCIAPiOS sharedInstance] setDialogOrientationMask:UIInterfaceOrientationMaskPortrait];
38
39     return true;
40 }
41
42 void CMGCIAP::setDoBillingCallbackScriptHandler(int scriptHandler)
43 {
44     m_doBillingCallbackScriptHandler = scriptHandler;
45 }
46
47 void CMGCIAP::doBillingWithUI(const char* billingIndex,
48                               bool isRepeated,
49                               const char* cpParam)
50 {
51
52     NSString *billingIndexString = [NSString stringWithUTF8String:billingIndex];
53
54     NSString *cpParamString = [NSString stringWithUTF8String:cpParam];
55
56     [[CMGCIAPiOS sharedInstance] doBillingWithUIAndBillingIndex:billingIndexString
57                                                      isRepeated:isRepeated
58                                                         cpParam:cpParamString
59                                                  resultCallback:^(BOOL success, NSString *index,NSString *message){
60
61         //通过Block将返回结果传给Lua,Objective-C到C++的无缝连接:)
62
63         CCLuaStack *stack = CCLuaEngine::defaultEngine()->getLuaStack();
64         stack->clean();
65         stack->pushBoolean(success);
66         stack->pushString([index UTF8String]);
67         stack->pushString([message UTF8String]);
68         stack->executeFunctionByHandler(m_doBillingCallbackScriptHandler, 3);
69
70      }];
71 }

好了,接下来只需要对C++ Bridge Class做Lua Binding,生成绑定文件,如果用tolua做绑定,绑定配置文件如下:

 1 class CMGCIAP
 2 {
 3     static CMGCIAP *sharedInstance();
 4
 5     void setDoBillingCallbackScriptHandler(LUA_FUNCTION nHandler);
 6
 7     void doBillingWithUI(const char* billingIndex,
 8                          bool isRepeated,
 9                          const char* cpParam);
10 }

OK,到这里主要的编码工作就完成了,记得要在程序的适当位置做好Lua Binding初始化工作。

如果一切顺利,在以上工作完成后,在Lua里面已经可以直接调用SDK的接口了,接下来的事情就靠你们了:)

呼~~~

文章到此也差不多了,整个思路和方案就是这些,如果有什么地方不理解或者不明白,欢迎留言讨论:)

谢谢收看!

转载请注明出处,谢谢:)

时间: 2024-10-11 07:51:05

[原创]cocos2d-x + Lua接入iOS原生SDK的实现方案的相关文章

cocos2d-x + Lua接入iOS原生SDK的实现方案[转]

相信很多朋友在使用cocos2d-x+lua开发游戏时都遇到过接入iOS原生SDK的问题,比如常见的接应用内支付SDK,广告SDK或是一些社交平台SDK等等,我也没少接过这类SDK.这篇文章主要是对我做过项目中接入iOS原生SDK实现方案的一个总结,在这里分享给大家,希望对自己和大家的开发工作都有帮助. 在展开正文之前,先做几点说明: 1.我这里说的iOS原生SDK是指那些完全用Objective-C语言开发,为原生iOS程序设计的SDK.swift很好很强大,不过我还没用过,惭愧,不过语言终归

Unity3D如何接入第三方的SDK - iOS篇

来源:http://blog.csdn.net/smlisi2/article/details/8786485 授人以鱼,不如授人以渔”,以UNITY3D调用iOS版的91SDK为例,利用C# / C / OBJ-C 交互原理,本文将详细介绍UNITY3D与iOS之间交互的解决方案. 首先,打开XCODE新建一个空项目,参照91SDK开发文档将91SDK引入并进行环境配置. 配置完毕后创建一个名为“LsSdkConector”的OBJ-C CLASS文件.完毕后,当前项目应如图: LsSdkCo

【Unity游戏开发】接入UWA_GOT的iOS版SDK以后无法正常出包

一.正文 问: RT,最近有看到UWA_GOT工具新增了iOS版本的支持,于是下载了最新的工具包进行了接入测试.是按照文档直接将UWA_GOTv2.0.1_iOS.unitypackage导入进了Unity项目内,并且做了配置.但是发现添加了UWA_GOT插件以后iOS上不能正常出包,总是卡在xcodebuild archive这一步:报错信息如下: ** ARCHIVE FAILED ** The following build commands failed: Check dependenc

IOS原生地图与高德地图

原生地图 1.什么是LBS LBS: 基于位置的服务   Location Based Service 实际应用:大众点评,陌陌,微信,美团等需要用到地图或定位的App 2.定位方式 1.GPS定位      2.基站定位      3.WIFI定位 3.框架 MapKit:地图框架,显示地图 CoreLocation:定位框架,没有地图时也可以使用定位. 4.如何使用原生地图<MapKit> 和定位<CoreLocation> MapKit: 1) 初始化MapView _map

IOS Vuforia SDK学习笔记

最近可能工作会用到关于AR方面的东西 所以找了一下有名气的AR库 发现2个:Metaio和Vuforia 先说说Metaio 我看了看网上的反应 好像这个库反响不错 用的人蛮多的.但是悲催的是 我7月1号去看的时候已经无法注册新用户了.没办法用 因为它被苹果收购了,也不知道该高兴还是该忧伤  好像目前是没办法用了.只好找了别的,比如:Vuforia Vuforia是个啥? Vuforia™是Qualcomm Connected Experiences, Inc.的产品.它是一款针对移动设备开发增

二维码扫描(iOS原生二维码扫描)

一.关于二维码扫描的第三方库有很多:例如比较常用的两个 1.ZBar SDK ZBar为我们提供了两种使用方式,一种是直接调用ZBar提供的ZBarReaderViewController打开一个扫描界面,另一种方式是使用ZBar提供的可以嵌在其他视图中的ZBarReaderView,实际项目中我们更可能会使用第二种方式,这可以让我们对界面做更多的定制,详细的百度查找相关文档来看. 2.ZXing(Github镜像地址)是一个开源的条码生成和扫描库(开源协议为Apache2.0).它不但支持众多

「Unity」Unity中接入iOS、Android平台:第零篇,内容描述

从去年年末开始,我负责在Unity中建立 接入iOS.Android相关SDK 的模板工程,一路磕磕碰碰,填了很多坑,对其中的苦闷深有体会. 所以决定将自己填过的坑整理一下,让大家能够走得更顺畅,但未经本人同意,禁止转载,谢谢. 之后的篇章中将会包括以下内容(不断更新): 导出xCode工程目录结构的重点讲解 导出Android工程后目录结构的重点讲解 Android Studio工程的重点讲解 与iOS的简单数据交互.监听.回调 与Android的简单数据交互.监听.回调 Unity下用于数据

Unity 接入 ios 平台

接到公司的新任务,Unity3d游戏接入ios 平台. 遇到的问题的解决: 1> Xcode cannot run using the selected device:可以在XCODE下设置,也可以在UNITY生成之前就设置player setrings —>Other Settings —>SDK Version,选为simulatorSDK就可以了.

ReplayKit库,iOS原生直播神器

版权声明:本文为博主原创,如需转载请注明出处. 前言 ReplayKit 是WWDC15推出的苹果原生录屏 API.在iOS9的时候主要提供的是录屏,录制完成后可以进行查看.编辑.通过指定方式分享出去. 在WWDC16上新版的 ReplayKit 提出了了 live 功能,简单说就是通过 ReplayKit 可以进行录屏直播.这对于苹果的手游直播行业有着很重要的意义. 首先给出视频地址和API文档 Go Live with ReplayKit - WWDC 2016 ReplayKit API