zoukankan      html  css  js  c++  java
  • 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工程中。

       

  • 相关阅读:
    java之集合Collection 3个例子
    利用 ssh 的用户配置文件 config 管理 ssh 会话
    angularJS--apply() 、digest()和watch()方法
    37.创业团队不是天堂
    Android DiskLruCache 源码解析 硬盘缓存的绝佳方案
    sublime安装AngularJS插件
    angularJS 服务--$provide里factory、service方法
    angularJS--多个控制器之间的数据共享
    angularJS---自定义过滤器
    依赖反转
  • 原文地址:https://www.cnblogs.com/klkucan/p/4137425.html
Copyright © 2011-2022 走看看