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对象后,在使用这个对象来进行购买。下来贴出一些代码来看看是怎么实现产品信息获取的。

 // 通过产品id数组获取产品的信息
-(void)getProductsInfo:(NSMutableArray *)productsIDArray
{
NSSet *productIdentifiers = [NSSet setWithArray:productsIDArray];
SKProductsRequest* productRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:productIdentifiers];
productRequest.delegate = self; if (self.skProductsRequest != nil) {
[self.skProductsRequest cancel];
self.skProductsRequest = nil;
}
self.skProductsRequest = productRequest;
[self.skProductsRequest start];
[productRequest release];
[productsIDArray release];
}

以上代码中self.skProductsRequest是一个属性就不多说明了,非ARC因此需要做一些release的工作。productsIDArray是一个产品ID数组,内容来自iTunes的产品ID,释放是有原因的,后面的代码会提到。从start函数开始调用,就开始了产品信息的查询。当有产品信息返回后,会调用下面两个方法中的一个:

 #pragma mark -
#pragma mark - SKProductsRequestDelegate Methods - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
// 购买时,获取到产品信息时到处理
// 如果拿到的产品列表是空,则按照购买失败处理
if (response.products == nil || response.products.count == ) {
UnitySendMessage("Interface", "purchaseFailed", "productsRequest error");
} // 获取到产品信息后,开始购买第一个产品。目前一次只能购买一个产品。
SKPayment *payment = [SKPayment paymentWithProduct:response.products[]];
[[SKPaymentQueue defaultQueue] addPayment:payment];
} - (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
// 目前进入游戏后如果产品信息获取失败,不做任何的处理
// 购买时,如果product info获取失败调用购买失败到处理函数
NSString *errorStr;
if (error != nil) {
errorStr = error.description;
} else {
errorStr = @"purchaseFailed";
}
UnitySendMessage("Interface", "purchaseFailed", [errorStr UTF8String]);
}

显而易见,第一个方法是成功后的调用,而第二个是失败后的调用。UnitySendMessage函数是U3D的,在此需要说明一点,你的OC代码工程需要通过Unity3D IDE生成,这样就会有这个函数了。它是oc和U3D通信的重要方式。当然,理论上使用函数指针一样可以实现回调函数的调用,C#中调用C时可以将delegate转成函数指针。UnitySendMessage函数第一个参数是GameObject名称,第二个是这个GameObject下挂着的任何脚本中的一个函数名,第三个是函数的参数。

注意这两句:

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

第一句是个示例,我只购买产品数组中第一个产品。生成一个SKPayment对象,并添加到[SKPaymentQueue defaultQueue]中,此时自动开始购买矜持。为了能够获得购买的过程状态和最终结果,你需要实现SKPaymentTransactionObserver中的方法。另外你需要写下这样一句代码,我是在类的init方法中写的。

  [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

(2)、你必须知道的第二个delegate:SKPaymentTransactionObserver。

这个delegate中定义了一些方法,用于监控购买交易的整个过程。具体看下面的代码:

 #pragma mark -
#pragma mark - SKPaymentTransactionObserver Methods
// 产品购买过程中到状态
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction]; default:
break;
}
}
} // 购买完成的处理
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
// 通知u3d购买成功,返回product ID
UnitySendMessage("Interface", "purchaseSuccessful",[[NSString stringWithFormat:@"%@,%@",transaction.payment.productIdentifier,transaction.transactionIdentifier] UTF8String]);
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
} // 购买失败的处理
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
// 通知u3d购买失败
// 错误信息为transaction.error.code
// SKErrorUnknown,
// SKErrorClientInvalid, // client is not allowed to issue the request, etc.
// SKErrorPaymentCancelled, // user cancelled the request, etc.
// SKErrorPaymentInvalid, // purchase identifier was invalid, etc.
// SKErrorPaymentNotAllowed, // this device is not allowed to make the payment
// SKErrorStoreProductNotAvailable, // Product is not available in the current storefront UnitySendMessage("Interface", "purchaseFailed", [[NSString stringWithFormat:@"%d",transaction.error.code] UTF8String]);
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
} // 购买到永久有效物品的处理
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
// 通知u3d购买成功
UnitySendMessage("Interface", "purchaseSuccessful",[[NSString stringWithFormat:@"%@,%@",transaction.payment.productIdentifier,transaction.transactionIdentifier] UTF8String]);
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

核心的是- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions方法,在一次购买过程中会多次调用,我们比较关心的会是成功和失败的状态。针对成功和失败做出专门的函数处理即可,处理函数比较简单没什么需要多说的。

下面说下交易过程中需要注意的一点,如果用户在游戏内点击了购买,但是立刻让游戏处于后台或者退出游戏(APP进程被杀死),那么此时仍旧能够弹出系统的购买询问,不管最后是否购买,此时我们的代码肯定是无法执行了。但是不要担心,经过我反复测试,在应用处于前台或者再次启动后,仍旧后运行updatedTransactions函数。初步推测原因应该是苹果在云端记录了APP是否结束了本次交易,而如何结束一次交易呢?就是调用[[SKPaymentQueue defaultQueue] finishTransaction: transaction];,所以好的处理办法是在交易完成的处理函数中加入这个处理。

额外说一下,还有一个

- (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函数中执行

  [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

到此为止iOS内购的部分就完成了。

3、U3D脚本调用OC函数:

在最开始就解释了C#脚本调用OC函数的原理,下面我们来直接看看代码的实现。首先我们需要在OC代码中实现C函数的声明和实现:

 #pragma mark -
#pragma mark - C Methods Imp // 函数定义
#ifdef __cplusplus
extern "C" {
#endif
extern void getProductsInfo(char** productsIDArray, int count);
#ifdef __cplusplus
}
#endif // 函数实现
#ifdef __cplusplus
extern "C" {
#endif
static AEPurchaseManager *aePurchaseManager; void getProductsInfo(char** productsIDArray, int count)
{
if(aePurchaseManager == NULL)
{
aePurchaseManager = [[AEPurchaseManager alloc] init];
} NSMutableArray* array = [[NSMutableArray alloc] init]; for (int i = ; i < count; i++)
{
[array addObject: [NSString stringWithCString: productsIDArray[i] encoding:NSASCIIStringEncoding]];
}
[aePurchaseManager getProductsInfo:array ]; }
#ifdef __cplusplus
}
#endif

OC的优势就是可以直接和C进行混合编程,在实现部分的代码中很好的体现了出来。在函数声明中请注意不能使用OC中的类型,比如NSString。下面来看看在C#脚本中如何实现:

     [DllImport("__Internal")]
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工程中。

Unity3D脚本调用Objective C代码实现游戏内购买的更多相关文章

  1. Unity3D脚本--经常使用代码集

    1. 訪问其他物体 1) 使用Find()和FindWithTag()命令 Find和FindWithTag是很耗费时间的命令,要避免在Update()中和每一帧都被调用的函数中使用.在Start() ...

  2. Unity3d 脚本相互调用

    unity中三种调用其他脚本函数的方法 第一种,被调用脚本函数为static类型,调用时直接用  脚本名.函数名().很不实用…… 第二种,GameObject.Find("脚本所在物体名& ...

  3. [转]unity3d 脚本参考-技术文档

    unity3d 脚本参考-技术文档 核心提示:一.脚本概览这是一个关于Unity内部脚本如何工作的简单概览.Unity内部的脚本,是通过附加自定义脚本对象到游戏物体组成的.在脚本对象内部不同志的函数被 ...

  4. [Unity3D]脚本中Start()和Awake()的区别

    Unity3D初学者经常把Awake和Start混淆. 简单说明一下,Awake在MonoBehavior创建后就立刻调用,Start将在MonoBehavior创建后在该帧Update之前,在该Mo ...

  5. Unity3D脚本中文系列教程(十六)

    Unity3D脚本中文系列教程(十五) ◆ function OnPostprocessAudio (clip:AudioClip):void 描述:◆  function OnPostprocess ...

  6. Unity3D脚本中文系列教程(十)

    http://dong2008hong.blog.163.com/blog/static/4696882720140312627682/?suggestedreading&wumii Unit ...

  7. Unity3D脚本中文系列教程(九)

    Unity3D脚本中文系列教程(八) name 对象名称hideFlags 该物体是否被隐藏,保存在场景中或被用户修改继承的函数 GetInstanceID 返回该物体的实例id继承的类函数 oper ...

  8. Unity3D脚本中文系列教程(七)

    http://dong2008hong.blog.163.com/blog/static/4696882720140311445677/?suggestedreading&wumii Unit ...

  9. Unity3D脚本中文系列教程(六)

    http://dong2008hong.blog.163.com/blog/static/469688272014031943118/ Unity3D脚本中文系列教程(五) 变量 ◆var colli ...

随机推荐

  1. 树形菜单的json字符串的拼接

    最近在学习权限管理, 要用到树形按钮, 但是字符串的拼接是一个难理解的问题, 然后从网上找了一个从前台用js来遍历组成这个json字符串, 很好! 但是没看懂... var data = [ {&qu ...

  2. 洛谷P2014 选课 (树形dp)

    10月1日更新.题目:在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习.现在有N门功课,每门课有个学分 ...

  3. hihoCoder 1305 区间求差

    #1305 : 区间求差 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定两个区间集合 A 和 B,其中集合 A 包含 N 个区间[ A1, A2 ], [ A3,  ...

  4. [转载] 散列表(Hash Table) 从理论到实用(下)

    转载自: 白话算法(6) 散列表(Hash Table) 从理论到实用(下) [澈丹,我想要个钻戒.][小北,等等吧,等我再修行两年,你把我烧了,舍利子比钻戒值钱.] ——自扯自蛋 无论开发一个程序还 ...

  5. C# WebClient 实现上传下载网络资源

    下载数据 WebClient wc = new WebClient();1 string str= wc.DownloadString("地址")://直接下载字符串 2 wc.D ...

  6. XE3随笔9:使用不同的数据类型标记数组

    unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, For ...

  7. PHP 判断数组是否为空的5大方法

    1. isset功能:判断变量是否被初始化 说明:它并不会判断变量是否为空,并且可以用来判断数组中元素是否被定义过 注意:当使用isset来判断数组元素是否被初始化过时,它的效率比array_key_ ...

  8. jsp_注释

    jsp支持两种注释的语法操作,一种是显示注释(在客户端允许看的见),另一种是隐式注释 显示注释:<!--注释内容--> 隐式注释: 格式一://单行注释 格式二:/*多行注释*/ 格式三: ...

  9. 一些常用的Bootstrap模板资源站

    2013-11-13 23:28:09   超级Bootstrap模板库:http://www.wrapbootstrap.com/ 免费的HTML5 响应式网页模板:http://html5up.n ...

  10. python递归次数和堆栈溢出问题

    在做递归的时候,测试了一下python的递归能力. 如果不设置递归次数的话,大概只能在992次左右,就会出现错误:RuntimeError: maximum recursion depth excee ...