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工程中。