1. 程式人生 > >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很好很強大,不過我還沒用過,慚愧,不過語言終歸只是表達方式而已,解決問題的思路都是一樣的;

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的介面了,接下來的事情就靠你們了:)


——————————————————————————————————————————————————

本文轉自 https://www.cnblogs.com/flyFreeZn/p/4152881.html

感謝作者的分享