Cocos2d-x使用iOS游戏内付费IAP(C++篇)
转载自 http://www.ityran.com/archives/5515
原创: u0u0
前期准备
设备与账号
在开始编码之前我们需要准备测试环境。
- IAP只能真机测试,准备一台iOS设备是必须的。
- 真机调试与IAP沙盒(SandBox)测试需要IDP(IOS Developer Program)账号。
- MAC开发机一台.
本文不涉及IDP申请流程和真机调试设置,重点解析IAP相关的设置。
新建IAP付费条目
新建app ID
登录iOS Dev Center, 点击“Certificates, Identifiers & Profiles->Identifiers->App IDs”,切换到App IDs界面,再点击“+”新建用于测试的AppID,默认设置”In-App-Purchase”已开启,如下图所示:
创建发布程序
无IAP的iOS App的真机测试是不需要下面的步骤的,而有IAP的则不同,需要先建立发布程序,设置好IAP信息才能测试相关的功能。
登录iTunes Connect, 切换到“Manage Your Apps ”,点击“Add New App”新建一个待发布程序, Bundle ID选择刚才创建的App ID。
接下来的程序信息界面可随意填写,截图可使用符合大小要求的假图,先保证能创建成功、可测试,等到需要正式提交审核的时候再修改成最终截图。
为发布程序新建IAP付费项目
点击刚才创建完成的App进入“App Information”界面,再点击“Manager In-App Purchases”进入IAP管理界面。
我们点击左上角的“Create New”来新建一个IAP付费项目,接下来的Select Type界面会有5中IAP类型可供选择。如图:
前两种是主类型:
游戏中使用得最多的就是“购买游戏币”了,我们这里只关注Consumable类型,可多次购买。
更多其他类型的信息可查询StoreKitGuide.pdf。
选择“Consumable”,进入详细信息设置界面。
Product ID全服唯一,起个自己觉得舒服的名称, 一般建议:Bundle ID + IAP description.
Language需要至少一种,选择“English”,方便测试。
当完成IAP付费项目的新建后,回到“Manager In-App Purchases”界面,可以看到下面的信息。
你可以随时修改已存在的项目,即使在游戏上线后也能修改(Product ID除外),这样可以在不发布新程序的情况下,做一些促销活动。
新建IAP付费测试账号
IAP的测试至关重要,你肯定不想给钱测试,被苹果扣掉30%。苹果的SandBox提供了一整套测试相关的服务。依然在iTunes Connect中设置。
点击“Manage Users->Test User”进入测试账号添加界面,点击左上交的“Add New User”,填入Email等信息。
Note:Email地址必须是未注册过Apple ID的email,注册过的无法使用。
Select iTunes Store必须选“United States”,错选为中国区不能测试不要怪我没提醒。
到此,前期准备工作都已完成,你也许需要等待几个小时让iTunes Connect设置生效,以便代码能获取到IAP信息,接下来我们正式进入代码阶段。
IAP的C++封装
新建项目
使用tool下的create_project.py创建项目,注意project ID 必须填写为上面我们申请的APP ID,这样真机调试才能取到我们设置的IAP信息。
C++开发的游戏,付费点直接使用Object-c的IAP接口会有诸多不便,在StoreKit基础上再封装一层C++接口会方便很多。新建 IOSiAP.h和IOSiAP.mm两个文件,加入到Xcode工程。mm文件为C++和Object-c混编文件,可在里面实现两种语言的互相调用。
IAP付费流程与接口抽象
如下图所示:
首先,IAP付费首先需要客户端发起请求,获取服务器上的IAP条目信息。之所用需要这个步骤,是因为iTunes Connect后台可以修改付费条目的价格、说明等信息。
然后,客户端根据获取到的IAP条目信息展示UI,当用户点击支付后发起payment请求。
最后,等待payment的回调响应。如果成功,游戏币增加;如果失败,UI提示给用户。
从付费流程,我们可以看出需要3个接口:
- 发起products information请求,并等待数据回来。
- 获取每个product的information。
- 请求购买product,并等待响应。
具体在IOSiAP.h中的抽象如下:
class IOSiAP { public: IOSiAP(); ~IOSiAP(); void requestProducts(std::vector <std::string> &productIdentifiers); IOSProduct *iOSProductByIdentifier(std::string &identifier); void paymentWithProduct(IOSProduct *iosProduct, int quantity = 1); IOSiAPDelegate *delegate; // === internal use for object-c class === void *skProducts;// object-c SKProduct void *skTransactionObserver;// object-c TransactionObserver std::vector<IOSProduct *> iOSProducts; };
其中的identifier是IAP付费项目的“Product ID”。
IOSProduct是一个简单的数据类,存放Product information。
class IOSProduct { public: std::string productIdentifier; std::string localizedTitle; std::string localizedDescription; std::string localizedPrice;// has be localed, just display it on UI. bool isValid; int index;//internal use : index of skProducts };
IOSiAPDelegate是消息回调通知类,由具体的调用者来实现。
typedef enum { IOSIAP_PAYMENT_PURCHASING,// just notify, UI do nothing IOSIAP_PAYMENT_PURCHAED,// need unlock App Functionality IOSIAP_PAYMENT_FAILED,// remove waiting on UI, tall user payment was failed IOSIAP_PAYMENT_RESTORED,// need unlock App Functionality, consumble payment No need to care about this. IOSIAP_PAYMENT_REMOVED,// remove waiting on UI } IOSiAPPaymentEvent; class IOSiAPDelegate { public: virtual ~IOSiAPDelegate() {} // for requestProduct virtual void onRequestProductsFinish(void) = 0; virtual void onRequestProductsError(int code) = 0; // for payment virtual void onPaymentEvent(std::string &identifier, IOSiAPPaymentEvent event) = 0; };
其中的前两个消息是requestProducts()的消息回调,最后一个是payment的回调。而payment又分5种状态。
requestProducts的实现
首先我们要包含StoreKit的头文件
#import <StoreKit/StoreKit.h>
然后,需要把StoreKit.framework加入到工程里面,如下图:
equestProducts的具体实现如下:
void IOSiAP::requestProducts(std::vector <std::string> &productIdentifiers) { // 1. NSMutableSet *set = [NSMutableSet setWithCapacity:productIdentifiers.size()]; std::vector <std::string>::iterator iterator; for (iterator = productIdentifiers.begin(); iterator != productIdentifiers.end(); iterator++) { [set addObject:[NSString stringWithUTF8String:(*iterator).c_str()]]; } // 2. SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set]; // 3. iAPProductsRequestDelegate *delegate = [[iAPProductsRequestDelegate alloc] init]; delegate.iosiap = this; productsRequest.delegate = delegate; // 4. [productsRequest start]; }
要点如下:
- 转换C++的数组为Object-c的数组。
- 新建一个SKProductsRequest,用product identifiers来初始化。
- iAPProductsRequestDelegate是内部抽象的一个桥接Object-c类,用来接受StoreKit的回调,并转换到C++的回调。
- 一切准备就绪,启动request。
下面我们看下iAPProductsRequestDelegate是如何桥接的。
声明protocol:SKProductsRequestDelegate,
在interface里面定义了一个iosiap,引用到C++对象实例。
@interface iAPProductsRequestDelegate : NSObject<SKProductsRequestDelegate> @property (nonatomic, assign) IOSiAP *iosiap; @end
实现SKProductsRequestDelegate的协议接口。
@implementation iAPProductsRequestDelegate // 1. - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { // release old if (_iosiap->skProducts) { [(NSArray *)(_iosiap->skProducts) release]; } // record new product _iosiap->skProducts = [response.products retain]; for (int index = 0; index < [response.products count]; index++) { SKProduct *skProduct = [response.products objectAtIndex:index]; // check is valid bool isValid = true; for (NSString *invalidIdentifier in response.invalidProductIdentifiers) { NSLog(@"invalidIdentifier:%@", invalidIdentifier); if ([skProduct.productIdentifier isEqualToString:invalidIdentifier]) { isValid = false; break; } } IOSProduct *iosProduct = new IOSProduct; iosProduct->productIdentifier = std::string([skProduct.productIdentifier UTF8String]); iosProduct->localizedTitle = std::string([skProduct.localizedTitle UTF8String]); iosProduct->localizedDescription = std::string([skProduct.localizedDescription UTF8String]); // locale price to string NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; [formatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; [formatter setNumberStyle:NSNumberFormatterCurrencyStyle]; [formatter setLocale:skProduct.priceLocale]; NSString *priceStr = [formatter stringFromNumber:skProduct.price]; [formatter release]; iosProduct->localizedPrice = std::string([priceStr UTF8String]); iosProduct->index = index; iosProduct->isValid = isValid; _iosiap->iOSProducts.push_back(iosProduct); } } // 2. - (void)requestDidFinish:(SKRequest *)request { _iosiap->delegate->onRequestProductsFinish(); [request.delegate release]; [request release]; } // 3. - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { NSLog(@"%@", error); _iosiap->delegate->onRequestProductsError([error code]); } @end
解析如下:
- 收到响应,解析出每个product information,再转换为C++数据存储起来。
- 请求结束通知。
- 请求失败通知,2和3不会同时出现。
iOSProductByIdentifier的实现
iOSProductByIdentifier的实现简单很多,在上一个步骤中我们已存储了请求回来的数据,现在只需要查找出对应的数据返回即可。
IOSProduct *IOSiAP::iOSProductByIdentifier(std::string &identifier) { std::vector <IOSProduct *>::iterator iterator; for (iterator = iOSProducts.begin(); iterator != iOSProducts.end(); iterator++) { IOSProduct *iosProduct = *iterator; if (iosProduct->productIdentifier == identifier) { return iosProduct; } } return nullptr; }
paymentWithProduct的实现
paymentWithProduct有两个参数,第一个参数是由iOSProductByIdentifier获取的IOSProduct实例,第二个参数是购买数量,本文只涉及Consumable类型的IAP,所以需要这个参数。
void IOSiAP::paymentWithProduct(IOSProduct *iosProduct, int quantity) { SKProduct *skProduct = [(NSArray *)(skProducts) objectAtIndex:iosProduct->index]; SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:skProduct]; payment.quantity = quantity; [[SKPaymentQueue defaultQueue] addPayment:payment]; }
SKMutablePayment是异步请求,和requestProducts一样自定义了一个叫iAPTransactionObserver的Object-c类来实现桥接。
@implementation iAPTransactionObserver // 1. - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { std::string identifier([transaction.payment.productIdentifier UTF8String]); IOSiAPPaymentEvent event; switch (transaction.transactionState) { case SKPaymentTransactionStatePurchasing: event = IOSIAP_PAYMENT_PURCHASING; return; case SKPaymentTransactionStatePurchased: event = IOSIAP_PAYMENT_PURCHAED; break; case SKPaymentTransactionStateFailed: event = IOSIAP_PAYMENT_FAILED; NSLog(@"==ios payment error:%@", transaction.error); break; case SKPaymentTransactionStateRestored: // NOTE: consumble payment is NOT restorable event = IOSIAP_PAYMENT_RESTORED; break; } _iosiap->delegate->onPaymentEvent(identifier, event, transaction.payment.quantity); // 2. if (event != IOSIAP_PAYMENT_PURCHASING) { [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } } } // 3. - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { std::string identifier([transaction.payment.productIdentifier UTF8String]); _iosiap->delegate->onPaymentEvent(identifier, IOSIAP_PAYMENT_REMOVED, transaction.payment.quantity); } } @end
要点如下:
- payment的状态更新,这里有四个状态,我们一一做了映射。
- IOSIAP_PAYMENT_PURCHASING不需要做任何处理。
- IOSIAP_PAYMENT_PURCHAED这个消息里面,游戏需要把金币交付给玩家。
- IOSIAP_PAYMENT_FAILED则可能需要UI提示错误信息。
- IOSIAP_PAYMENT_RESTORED,consumble类型的IAP是没有这个消息的。
- 除了IOSIAP_PAYMENT_PURCHASING消息,其他消息在通知完上层游戏逻辑后,都需要finishTransaction处理。
- removedTransactions实则是由finishTransaction触发的回调,我们依然要把这个消息映射到上层。
测试
不少只使用C++的用户反馈不知道如何使用接口,这里给一个伪代码供参考
首先要让调用的类继承IOSiAPDelegate,重写三个消息函数。
IOSiAP的初始化很简单,记得把delegate设置为调用IOSiAP_Bridge。其他逻辑参考伪代码注释。
class IOSiAP_Bridge : public IOSiAPDelegate { public: IOSiAP_Bridge(); ~IOSiAP_Bridge(); IOSiAP *iap; virtual void onRequestProductsFinish(void); virtual void onRequestProductsError(int code); virtual void onPaymentEvent(std::string &identifier, IOSiAPPaymentEvent event, int quantity); }; IOSiAP_Bridge::IOSiAP_Bridge() { iap = new IOSiAP(); iap->delegate = this; } IOSiAP_Bridge::~IOSiAP_Bridge() { delete iap; } IOSiAP_Bridge:: requestProducts() { iap->requestProducts(identifiers); } void IOSiAP_Bridge::onRequestProductsFinish(void) { //必须在onRequestProductsFinish后才能去请求iAP产品数据。 IOSProduct *product = iap->iOSProductByIdentifier(identifier); // 然后可以发起付款请求。 iap->paymentWithProduct(product, quantity); } void IOSiAP_Bridge::onRequestProductsError(int code) { //这里requestProducts出错了,不能进行后面的所有操作。 } void IOSiAP_Bridge::onPaymentEvent(std::string &identifier, IOSiAPPaymentEvent event, int quantity) { if (event == IOSIAP_PAYMENT_PURCHAED) { //付款成功了,可以吧金币发给玩家了。 } //其他状态依情况处理掉。 }
Where to Go
你可以在这里获取到本文的源码,把工程放到Cocos2d-x 3.0 beta下的projects目录下即可运行使用。
这里没有提及接口测试,我们将在下一章JSB篇中讲解。
相关推荐
《Cocos2d-x实战:C++卷(2版)源代码》这本书是关于使用Cocos2d-x游戏引擎进行游戏开发的专业指南。Cocos2d-x是一个开源的、跨平台的游戏开发框架,广泛应用于iOS、Android、Windows等多个操作系统。本书以C++语言...
Cocos2d-x 是一个开源的、跨平台的2D游戏开发框架,广泛应用于iOS、Android、Windows等多平台的游戏开发。3.x版本是其发展中的一个重要阶段,引入了多项优化和新特性,旨在提高开发效率和性能。 本书首先会介绍...
《Cocos2d-x实战C++卷》是关东升所著的一本深入探讨Cocos2d-x游戏引擎开发的专业书籍。Cocos2d-x是一个开源的、跨平台的2D游戏开发框架,广泛应用于iOS、Android、Windows等多个操作系统。本书以C++语言为主要编程...
Cocos2d-x是一个广泛使用的开源游戏开发框架,它基于C++,同时支持Lua和JavaScript等多种脚本语言,为开发者提供了高效、跨平台的游戏开发解决方案。在3.13.1版本中,Cocos2d-x对Spine动画引擎的集成进行了更新,这...
资源名称:Cocos2d-x实战:JS卷——Cocos2d-JS开发内容简介:本书是介绍Cocos2d-x游戏编程和开发技术书籍,介绍了使用Cocos2d-JS中核心类、瓦片地图、物理引擎、音乐音效、数据持久化、网络通信、性能优化、多平台...
cocos2d-x 是一个跨平台的游戏开发框架,它基于C++,同时提供了Lua和JavaScript的绑定,让开发者可以方便地在多种操作系统上创建2D游戏、演示程序和其他图形交互应用。这个“cocos2d-x-3.8.zip”压缩包包含的是cocos...
cocos2d-x是一个基于MIT许可证的开源游戏引擎,它以快速、简单且功能强大的特性闻名,允许开发者使用C++、Lua和JavaScript进行跨平台开发,支持包括iOS、Android、Windows Phone、Blackberry以及Tizen在内的多个平台...
cocos2d-x 是一个开源的游戏开发框架,使用 C++ 语言编写,支持多平台发布,包括 iOS、Android、Windows、macOS、Linux 和 Web。cocos2d-x v3.16 是该框架的一个版本号,本文档主要介绍了该版本的安装流程以及环境...
12. **扩展与插件**:社区提供的各种扩展库和插件,如社交网络集成、广告支持、IAP(应用内购买)等,丰富了Cocos2d-x的功能。 13. **工具集**:包含了一个完整的工具链,如Cocos Studio用于界面设计,Cocos ...
Cocos2d-x 是一个跨平台的游戏引擎,可以在多种平台上运行,包括 Windows、Mac OS X、iOS、Android 等。 一、下载和安装 Cocos2d-x 首先,需要下载最新版本的 Cocos2d-x。打开浏览器,输入 cocos2d-x.org,然后...
总的来说,cocos2d-x 2.2.2是一个功能完备、易于学习和使用的2D游戏开发框架。通过深入理解并掌握这个版本,开发者可以创建出运行在多种平台上的高质量游戏,享受到cocos2d-x带来的强大开发体验。无论是初学者还是...
【cocos2d-x游戏引擎概述】 ...通过学习和分析这款三国策略游戏的源码,开发者不仅可以了解cocos2d-x框架的使用,还能深入理解游戏开发中的各种设计决策和技术实现,对提升游戏开发技能大有裨益。
cocos2d-x实战 c++卷教程及完整源码下载,使用最新cocos2d-x-3.14版本,在xcode7.3上已编译通过。 解决相关问题 1、解决源程序在高版本上无法编译问题 2、解决源程序中文注释部分,xcode上显示乱码问题 3、根据书籍...
由于Cocos2d-x支持多种编程语言(包括C++、JavaScript和Lua),Cocos2d-JS意味着开发者可以采用JavaScript来构建游戏项目。 4. PDF电子书下载:这是提供给读者的下载格式,即PDF(便携式文档格式),它是一种通用的...
Cocos2d-x是全球范围内广泛采用的游戏开发框架,尤其适用于2D游戏的制作,而Cocos2d-JS则是其JavaScript接口,它允许开发者使用JavaScript语言进行游戏逻辑的编写,极大地提高了开发效率和跨平台兼容性。 Cocos2d-x...
2. C++ 基础:cocos2d-x 使用C++作为主要编程语言,同时提供了Python、JavaScript等语言的绑定,满足不同开发者的需求。 3. 图形渲染:框架内置了基于OpenGL的图形渲染引擎,提供2D图形绘制、动画处理等功能,使...
5、【cocos2d-x IOS游戏开发-捕鱼达人5】C++中函数代理与信号插槽机制 资源引用页: 6、【cocos2d-x IOS游戏开发-捕鱼达人6】实现开始菜单 资源引用页: 7、【cocos2d-x IOS游戏开发-捕鱼达人7】游戏场景基础实现 ...
cocos2d-x 3.0是cocos2d-x系列的一个重要升级,它基于C++编写,支持跨平台开发,包括iOS、Android、Windows等多个操作系统。该版本引入了新的渲染系统、性能提升、新的动画系统以及更友好的API,极大地提高了开发...
Cocos2d-X是一款强大的开源跨平台2D游戏开发框架,它基于C++,并提供了JavaScript和Lua等多种脚本语言接口。这个“Cocos2d-X游戏源码大合集.rar”文件显然包含了大约三十个使用Cocos2d-X开发的游戏实例源代码,对于...
在移动游戏开发领域,cocos2d-x是一款广泛使用的开源游戏引擎,以其高效、跨平台的特性深受开发者喜爱。而Flash2Cocos2d-x则是一个专为cocos2d-x设计的动画工具,它使得开发者能够将Flash内容轻松地转换为可以在...