Unity3D腳本調用Objective C代碼實現游戲內購買


0、開篇吐槽:

一年之內從WP轉到iOS,又從iOS轉到U3D,真心傷不起。

1、Unity3D腳本調用OC代碼的原理:

      其實也沒啥神秘的,因為OC是和C互通的 ,C#又可以通過DllImport的形式調用C代碼,因此這中間就有了溝通的橋梁,具體實現會在文中提到。

2、實現iOS內購買:

      本着高大全的原則,文中將詳細的說明從iOS購買到C#調用的全部過程。本人也是iOS平台第一次寫內購代碼,經過多方學習和反復測試總算是得到了一些經驗。

      (1)、你必須知道的第一個delegate:SKProductsRequestDelegate

      SKProductsRequestDelegate:從名字就可以看出來這個delegate中的方法是請求產品的,事實上在購買的過程中,必須先通過iTunes上的產品ID查詢一次產品信息,得到一個SKProduct對象后,在使用這個對象來進行購買。下來貼出一些代碼來看看是怎么實現產品信息獲取的。

 1 // 通過產品id數組獲取產品的信息
 2 -(void)getProductsInfo:(NSMutableArray *)productsIDArray
 3 {
 4     NSSet *productIdentifiers = [NSSet setWithArray:productsIDArray];
 5     SKProductsRequest* productRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:productIdentifiers];
 6     productRequest.delegate = self;
 7     
 8     if (self.skProductsRequest != nil) {
 9         [self.skProductsRequest cancel];
10         self.skProductsRequest = nil;
11     }
12     self.skProductsRequest = productRequest;
13     [self.skProductsRequest start];
14     [productRequest release];
15     [productsIDArray release];
16 }

      以上代碼中self.skProductsRequest是一個屬性就不多說明了,非ARC因此需要做一些release的工作。productsIDArray是一個產品ID數組,內容來自iTunes的產品ID,釋放是有原因的,后面的代碼會提到。從start函數開始調用,就開始了產品信息的查詢。當有產品信息返回后,會調用下面兩個方法中的一個:

 1 #pragma mark -
 2 #pragma mark - SKProductsRequestDelegate Methods
 3 
 4 - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
 5 {
 6    // 購買時,獲取到產品信息時到處理
 7    // 如果拿到的產品列表是空,則按照購買失敗處理
 8    if (response.products == nil || response.products.count == 0) {
 9        UnitySendMessage("Interface", "purchaseFailed", "productsRequest error");
10    }
11    
12    // 獲取到產品信息后,開始購買第一個產品。目前一次只能購買一個產品。
13    SKPayment *payment = [SKPayment paymentWithProduct:response.products[0]];
14    [[SKPaymentQueue defaultQueue] addPayment:payment];
15 }
16 
17 - (void)request:(SKRequest *)request didFailWithError:(NSError *)error
18 {
19     // 目前進入游戲后如果產品信息獲取失敗,不做任何的處理
20     // 購買時,如果product info獲取失敗調用購買失敗到處理函數
21     NSString *errorStr; 
22    if (error != nil) {
23        errorStr = error.description;
24    } else {
25        errorStr = @"purchaseFailed";
26    }
27    UnitySendMessage("Interface", "purchaseFailed", [errorStr UTF8String]);
28 }

      顯而易見,第一個方法是成功后的調用,而第二個是失敗后的調用。UnitySendMessage函數是U3D的,在此需要說明一點,你的OC代碼工程需要通過Unity3D IDE生成,這樣就會有這個函數了。它是oc和U3D通信的重要方式。當然,理論上使用函數指針一樣可以實現回調函數的調用,C#中調用C時可以將delegate轉成函數指針。UnitySendMessage函數第一個參數是GameObject名稱,第二個是這個GameObject下掛着的任何腳本中的一個函數名,第三個是函數的參數。

      注意這兩句:

1 SKPayment *payment = [SKPayment paymentWithProduct:response.products[0]];
2 [[SKPaymentQueue defaultQueue] addPayment:payment];

      第一句是個示例,我只購買產品數組中第一個產品。生成一個SKPayment對象,並添加到[SKPaymentQueue defaultQueue]中,此時自動開始購買矜持。為了能夠獲得購買的過程狀態和最終結果,你需要實現SKPaymentTransactionObserver中的方法。另外你需要寫下這樣一句代碼,我是在類的init方法中寫的。

1  [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

      (2)、你必須知道的第二個delegate:SKPaymentTransactionObserver。

       這個delegate中定義了一些方法,用於監控購買交易的整個過程。具體看下面的代碼:

 1 #pragma mark -
 2 #pragma mark - SKPaymentTransactionObserver Methods
 3 // 產品購買過程中到狀態
 4 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
 5 {
 6     for (SKPaymentTransaction *transaction in transactions) {
 7         switch (transaction.transactionState) {
 8             case SKPaymentTransactionStatePurchased:
 9                 [self completeTransaction:transaction];
10                 break;
11             case SKPaymentTransactionStateFailed:
12                 [self failedTransaction:transaction];
13                 break;
14             case SKPaymentTransactionStateRestored:
15                 [self restoreTransaction:transaction];
16                 
17             default:
18                 break;
19         }
20     }
21 }
22 
23 // 購買完成的處理
24 - (void)completeTransaction:(SKPaymentTransaction *)transaction
25 {
26     // 通知u3d購買成功,返回product ID
27     UnitySendMessage("Interface", "purchaseSuccessful",[[NSString stringWithFormat:@"%@,%@",transaction.payment.productIdentifier,transaction.transactionIdentifier] UTF8String]);
28     [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
29 }
30 
31 // 購買失敗的處理
32 - (void)failedTransaction:(SKPaymentTransaction *)transaction
33 {
34     // 通知u3d購買失敗
35     // 錯誤信息為transaction.error.code
36     // SKErrorUnknown,
37     // SKErrorClientInvalid,               // client is not allowed to issue the request, etc.
38     // SKErrorPaymentCancelled,            // user cancelled the request, etc.
39     // SKErrorPaymentInvalid,              // purchase identifier was invalid, etc.
40     // SKErrorPaymentNotAllowed,           // this device is not allowed to make the payment
41     // SKErrorStoreProductNotAvailable,    // Product is not available in the current storefront
42     
43     UnitySendMessage("Interface", "purchaseFailed", [[NSString stringWithFormat:@"%d",transaction.error.code] UTF8String]);
44     [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
45 }
46 
47 // 購買到永久有效物品的處理
48 - (void)restoreTransaction:(SKPaymentTransaction *)transaction
49 {
50     // 通知u3d購買成功
51     UnitySendMessage("Interface", "purchaseSuccessful",[[NSString stringWithFormat:@"%@,%@",transaction.payment.productIdentifier,transaction.transactionIdentifier] UTF8String]);
52     [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
53 }

      核心的是- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions方法,在一次購買過程中會多次調用,我們比較關心的會是成功和失敗的狀態。針對成功和失敗做出專門的函數處理即可,處理函數比較簡單沒什么需要多說的。

      下面說下交易過程中需要注意的一點,如果用戶在游戲內點擊了購買,但是立刻讓游戲處於后台或者退出游戲(APP進程被殺死),那么此時仍舊能夠彈出系統的購買詢問,不管最后是否購買,此時我們的代碼肯定是無法執行了。但是不要擔心,經過我反復測試,在應用處於前台或者再次啟動后,仍舊后運行updatedTransactions函數。初步推測原因應該是蘋果在雲端記錄了APP是否結束了本次交易,而如何結束一次交易呢?就是調用[[SKPaymentQueue defaultQueue] finishTransaction: transaction];,所以好的處理辦法是在交易完成的處理函數中加入這個處理。

      額外說一下,還有一個

 1 - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions 函數,這個函數會在finishTransaction函數調用后執行。Apple的建議是:Your application does not typically need to implement this method but might implement it to update its own user interface to reflect that a transaction has been completed. 因此如無特別需要不需實現這個方法。

     最后別忘記在dealloc函數中執行

1  [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

    到此為止iOS內購的部分就完成了。

3、U3D腳本調用OC函數:

     在最開始就解釋了C#腳本調用OC函數的原理,下面我們來直接看看代碼的實現。首先我們需要在OC代碼中實現C函數的聲明和實現:

 1 #pragma mark -
 2 #pragma mark - C Methods Imp
 3 
 4 // 函數定義
 5 #ifdef __cplusplus
 6 extern "C" {
 7 #endif
 8     extern void getProductsInfo(char** productsIDArray, int count);
 9 #ifdef __cplusplus
10 }
11 #endif
12 
13 // 函數實現
14 #ifdef __cplusplus
15 extern "C" {
16 #endif
17     static AEPurchaseManager *aePurchaseManager;
18     
19     void getProductsInfo(char** productsIDArray, int count)
20     {
21         if(aePurchaseManager == NULL)
22         {
23             aePurchaseManager = [[AEPurchaseManager alloc] init];
24         }
25 
26         NSMutableArray* array = [[NSMutableArray alloc] init];
27         
28         for (int i = 0; i < count; i++)
29         {
30             [array addObject: [NSString stringWithCString: productsIDArray[i] encoding:NSASCIIStringEncoding]];
31         }
32         [aePurchaseManager getProductsInfo:array ];
33         
34     }
35 #ifdef __cplusplus
36 }
37 #endif

    OC的優勢就是可以直接和C進行混合編程,在實現部分的代碼中很好的體現了出來。在函數聲明中請注意不能使用OC中的類型,比如NSString。下面來看看在C#腳本中如何實現:

1     [DllImport("__Internal")]
2     private static extern void getProductsInfo(string[] productsIDArray, int count);

    只要聲明出函數即可,需要注意的是oc的.h和.m文件需要放到unity工程中,另外在生產了iOS工程文件后,還需要把.h和.m文件放到iOS工程中。因為iOS上最終生成游戲安裝包的還是Xcode。

   Update 2015.1.6:

   關於oc代碼文件的問題,並不完全是如上文所說的那樣,其實上文所說的方法本人實際上沒有使用。我使用的方法是在unity工程中創建Plugins文件夾→創建子文件夾iOS,如下圖。至於為啥必須這個名字請參見http://wiki.unity3d.com/index.php/Special_Folder_Names_in_your_Assets_Folder 。這樣在生成xcode工程時oc代碼文件自動就包含在了xcode工程中。

   

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM