Cocos2d-x 第三方SDK集成指南(广告/支付/分享)
【摘要】 1. 引言Cocos2d-x作为一款跨平台游戏开发引擎,在移动游戏开发中占据重要地位。随着游戏商业化需求的增长,集成第三方SDK(如广告、支付、分享)已成为游戏开发的必备环节。本文将全面介绍如何在Cocos2d-x项目中集成各类第三方SDK,提供从理论到实践的完整解决方案。2. 技术背景2.1 Cocos2d-x架构特点跨平台性:支持iOS、Android、Windows等多平台C++核心:...
1. 引言
Cocos2d-x作为一款跨平台游戏开发引擎,在移动游戏开发中占据重要地位。随着游戏商业化需求的增长,集成第三方SDK(如广告、支付、分享)已成为游戏开发的必备环节。本文将全面介绍如何在Cocos2d-x项目中集成各类第三方SDK,提供从理论到实践的完整解决方案。
2. 技术背景
2.1 Cocos2d-x架构特点
-
跨平台性:支持iOS、Android、Windows等多平台
-
C++核心:基于C++开发,性能优异
-
JavaScript绑定:支持JS脚本开发
-
Lua绑定:支持Lua脚本开发
2.2 SDK集成挑战
-
平台差异:不同平台的SDK接口和实现方式各异
-
生命周期管理:需与Cocos2d-x的生命周期协调
-
线程安全:确保UI操作在主线程执行
-
内存管理:避免内存泄漏和资源冲突
3. 应用使用场景
3.1 广告场景
-
激励视频广告:观看完整视频获得游戏奖励
-
插屏广告:关卡切换或游戏结束时的全屏广告
-
横幅广告:常驻界面底部的广告条
3.2 支付场景
-
应用内购买:购买虚拟道具、金币等
-
订阅服务:会员特权、去广告等
-
一次性付费:解锁完整版游戏
3.3 分享场景
-
社交分享:分享游戏成绩到微信、微博
-
邀请好友:通过分享链接获取奖励
-
内容传播:分享游戏截图或视频
4. 环境准备
4.1 开发环境要求
# Cocos2d-x版本:v3.17+
# Android Studio:4.0+
# Xcode:11.0+
# NDK:r21e+
# Python:2.7.x (用于构建)
4.2 项目配置
-
下载Cocos2d-x引擎并配置环境变量
-
创建新项目:
cocos new MyGame -p com.yourcompany.mygame -l cpp -d ~/projects -
配置Android和iOS原生开发环境
5. 广告SDK集成(以AdMob为例)
5.1 原理概述
AdMob通过JNI桥接Android原生SDK和Cocos2d-x C++层,iOS则直接通过Objective-C++调用。
5.2 Android平台集成
5.2.1 添加依赖
在
proj.android/app/build.gradle中添加:dependencies {
implementation 'com.google.android.gms:play-services-ads:20.6.0'
}
5.2.2 JNI接口定义
创建
proj.android/app/src/org/cocos2dx/cpp/JniHelper.java的扩展:package org.cocos2dx.cpp;
import android.app.Activity;
import android.util.Log;
import com.google.android.gms.ads.AdError;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.FullScreenContentCallback;
import com.google.android.gms.ads.LoadAdError;
import com.google.android.gms.ads.interstitial.InterstitialAd;
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback;
import com.google.android.gms.ads.rewarded.RewardedAd;
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback;
public class AdMobBridge {
private static final String TAG = "AdMobBridge";
private static Activity sActivity = null;
private static InterstitialAd interstitialAd;
private static RewardedAd rewardedAd;
public static void init(Activity activity) {
sActivity = activity;
Log.d(TAG, "AdMob initialized");
}
// 加载插页广告
public static void loadInterstitialAd(final String adUnitId) {
if (sActivity == null) return;
sActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
AdRequest adRequest = new AdRequest.Builder().build();
InterstitialAd.load(sActivity, adUnitId, adRequest,
new InterstitialAdLoadCallback() {
@Override
public void onAdLoaded(@NonNull InterstitialAd ad) {
interstitialAd = ad;
setInterstitialCallbacks();
Log.d(TAG, "Interstitial ad loaded");
nativeOnInterstitialAdLoaded(true);
}
@Override
public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
Log.e(TAG, "Interstitial ad failed to load: " + loadAdError.getMessage());
nativeOnInterstitialAdLoaded(false);
}
});
}
});
}
// 设置插页广告回调
private static void setInterstitialCallbacks() {
if (interstitialAd == null) return;
interstitialAd.setFullScreenContentCallback(new FullScreenContentCallback() {
@Override
public void onAdDismissedFullScreenContent() {
Log.d(TAG, "Interstitial ad dismissed");
nativeOnInterstitialAdClosed();
// 广告关闭后重新加载
loadInterstitialAd(getInterstitialAdUnitId());
}
@Override
public void onAdFailedToShowFullScreenContent(AdError adError) {
Log.e(TAG, "Interstitial ad failed to show: " + adError.getMessage());
nativeOnInterstitialAdFailed(adError.getMessage());
}
@Override
public void onAdShowedFullScreenContent() {
Log.d(TAG, "Interstitial ad showed");
interstitialAd = null;
}
});
}
// 显示插页广告
public static void showInterstitialAd() {
if (sActivity == null || interstitialAd == null) {
Log.w(TAG, "Interstitial ad not ready");
return;
}
sActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
interstitialAd.show(sActivity);
}
});
}
// 加载激励视频广告
public static void loadRewardedAd(final String adUnitId) {
if (sActivity == null) return;
sActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
AdRequest adRequest = new AdRequest.Builder().build();
RewardedAd.load(sActivity, adUnitId, adRequest,
new RewardedAdLoadCallback() {
@Override
public void onAdLoaded(@NonNull RewardedAd ad) {
rewardedAd = ad;
setRewardedCallbacks();
Log.d(TAG, "Rewarded ad loaded");
nativeOnRewardedAdLoaded(true);
}
@Override
public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
Log.e(TAG, "Rewarded ad failed to load: " + loadAdError.getMessage());
nativeOnRewardedAdLoaded(false);
}
});
}
});
}
// 设置激励视频回调
private static void setRewardedCallbacks() {
if (rewardedAd == null) return;
rewardedAd.setFullScreenContentCallback(new FullScreenContentCallback() {
@Override
public void onAdDismissedFullScreenContent() {
Log.d(TAG, "Rewarded ad dismissed");
nativeOnRewardedAdClosed();
// 重新加载广告
loadRewardedAd(getRewardedAdUnitId());
}
@Override
public void onAdFailedToShowFullScreenContent(AdError adError) {
Log.e(TAG, "Rewarded ad failed to show: " + adError.getMessage());
nativeOnRewardedAdFailed(adError.getMessage());
}
@Override
public void onAdShowedFullScreenContent() {
Log.d(TAG, "Rewarded ad showed");
rewardedAd = null;
}
});
}
// 显示激励视频广告
public static void showRewardedAd() {
if (sActivity == null || rewardedAd == null) {
Log.w(TAG, "Rewarded ad not ready");
return;
}
sActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
rewardedAd.show(sActivity, new OnUserEarnedRewardListener() {
@Override
public void onUserEarnedReward(@NonNull RewardItem rewardItem) {
Log.d(TAG, "User earned reward: " + rewardItem.getAmount() + " " + rewardItem.getType());
nativeOnUserEarnedReward(rewardItem.getAmount(), rewardItem.getType());
}
});
}
});
}
// Native回调方法声明
private static native void nativeOnInterstitialAdLoaded(boolean success);
private static native void nativeOnInterstitialAdClosed();
private static native void nativeOnInterstitialAdFailed(String error);
private static native void nativeOnRewardedAdLoaded(boolean success);
private static native void nativeOnRewardedAdClosed();
private static native void nativeOnRewardedAdFailed(String error);
private static native void nativeOnUserEarnedReward(int amount, String type);
// 获取广告单元ID(实际应用中应从配置读取)
private static String getInterstitialAdUnitId() {
return "ca-app-pub-3940256099942544/1033173712"; // 测试ID
}
private static String getRewardedAdUnitId() {
return "ca-app-pub-3940256099942544/5224354917"; // 测试ID
}
}
5.2.3 C++层封装
创建
Classes/AdMobManager.h:#ifndef __ADMOB_MANAGER_H__
#define __ADMOB_MANAGER_H__
#include "cocos2d.h"
#include <string>
NS_CC_BEGIN
class AdMobManager {
public:
static AdMobManager* getInstance();
virtual ~AdMobManager();
// 初始化
void init();
// 插页广告
void loadInterstitialAd(const std::string& adUnitId = "");
void showInterstitialAd();
// 激励视频广告
void loadRewardedAd(const std::string& adUnitId = "");
void showRewardedAd();
// 回调处理
void onInterstitialAdLoaded(bool success);
void onInterstitialAdClosed();
void onInterstitialAdFailed(const std::string& error);
void onRewardedAdLoaded(bool success);
void onRewardedAdClosed();
void onRewardedAdFailed(const std::string& error);
void onUserEarnedReward(int amount, const std::string& type);
private:
AdMobManager();
static AdMobManager* _instance;
};
NS_CC_END
#endif // __ADMOB_MANAGER_H__
创建
Classes/AdMobManager.cpp:#include "AdMobManager.h"
#include "platform/android/jni/JniHelper.h"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include <jni.h>
#include "platform/android/jni/JniHelper.h"
#endif
USING_NS_CC;
static const char* CLASS_NAME = "org/cocos2dx/cpp/AdMobBridge";
AdMobManager* AdMobManager::_instance = nullptr;
AdMobManager::AdMobManager() {
}
AdMobManager::~AdMobManager() {
}
AdMobManager* AdMobManager::getInstance() {
if (_instance == nullptr) {
_instance = new AdMobManager();
}
return _instance;
}
void AdMobManager::init() {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "init", "(Landroid/app/Activity;)V")) {
jclass clazz = methodInfo.env->FindClass("org/cocos2dx/cpp/AppActivity");
jobject activity = methodInfo.env->CallStaticObjectMethod(clazz,
methodInfo.env->GetStaticMethodID(clazz, "getActivity", "()Landroid/app/Activity;"));
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, activity);
methodInfo.env->DeleteLocalRef(activity);
methodInfo.env->DeleteLocalRef(clazz);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void AdMobManager::loadInterstitialAd(const std::string& adUnitId) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "loadInterstitialAd", "(Ljava/lang/String;)V")) {
jstring jAdUnitId = methodInfo.env->NewStringUTF(adUnitId.empty() ?
"ca-app-pub-3940256099942544/1033173712" : adUnitId.c_str());
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jAdUnitId);
methodInfo.env->DeleteLocalRef(jAdUnitId);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void AdMobManager::showInterstitialAd() {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "showInterstitialAd", "()V")) {
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void AdMobManager::loadRewardedAd(const std::string& adUnitId) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "loadRewardedAd", "(Ljava/lang/String;)V")) {
jstring jAdUnitId = methodInfo.env->NewStringUTF(adUnitId.empty() ?
"ca-app-pub-3940256099942544/5224354917" : adUnitId.c_str());
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jAdUnitId);
methodInfo.env->DeleteLocalRef(jAdUnitId);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void AdMobManager::showRewardedAd() {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "showRewardedAd", "()V")) {
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
// Native回调实现
void AdMobManager::onInterstitialAdLoaded(bool success) {
CCLOG("Interstitial ad loaded: %s", success ? "success" : "failed");
// 发送通知给游戏逻辑
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("admob_interstitial_loaded",
&Value(success));
}
void AdMobManager::onInterstitialAdClosed() {
CCLOG("Interstitial ad closed");
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("admob_interstitial_closed");
}
void AdMobManager::onInterstitialAdFailed(const std::string& error) {
CCLOG("Interstitial ad failed: %s", error.c_str());
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("admob_interstitial_failed",
&Value(error));
}
void AdMobManager::onRewardedAdLoaded(bool success) {
CCLOG("Rewarded ad loaded: %s", success ? "success" : "failed");
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("admob_rewarded_loaded",
&Value(success));
}
void AdMobManager::onRewardedAdClosed() {
CCLOG("Rewarded ad closed");
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("admob_rewarded_closed");
}
void AdMobManager::onRewardedAdFailed(const std::string& error) {
CCLOG("Rewarded ad failed: %s", error.c_str());
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("admob_rewarded_failed",
&Value(error));
}
void AdMobManager::onUserEarnedReward(int amount, const std::string& type) {
CCLOG("User earned reward: %d %s", amount, type.c_str());
ValueMap reward;
reward["amount"] = amount;
reward["type"] = type;
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("admob_user_earned_reward",
&Value(reward));
}
// JNI回调注册
extern "C" {
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AdMobBridge_nativeOnInterstitialAdLoaded(JNIEnv* env, jclass clazz, jboolean success) {
AdMobManager::getInstance()->onInterstitialAdLoaded(success);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AdMobBridge_nativeOnInterstitialAdClosed(JNIEnv* env, jclass clazz) {
AdMobManager::getInstance()->onInterstitialAdClosed();
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AdMobBridge_nativeOnInterstitialAdFailed(JNIEnv* env, jclass clazz, jstring error) {
const char* errorStr = env->GetStringUTFChars(error, nullptr);
AdMobManager::getInstance()->onInterstitialAdFailed(errorStr);
env->ReleaseStringUTFChars(error, errorStr);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AdMobBridge_nativeOnRewardedAdLoaded(JNIEnv* env, jclass clazz, jboolean success) {
AdMobManager::getInstance()->onRewardedAdLoaded(success);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AdMobBridge_nativeOnRewardedAdClosed(JNIEnv* env, jclass clazz) {
AdMobManager::getInstance()->onRewardedAdClosed();
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AdMobBridge_nativeOnRewardedAdFailed(JNIEnv* env, jclass clazz, jstring error) {
const char* errorStr = env->GetStringUTFChars(error, nullptr);
AdMobManager::getInstance()->onRewardedAdFailed(errorStr);
env->ReleaseStringUTFChars(error, errorStr);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AdMobBridge_nativeOnUserEarnedReward(JNIEnv* env, jclass clazz, jint amount, jstring type) {
const char* typeStr = env->GetStringUTFChars(type, nullptr);
AdMobManager::getInstance()->onUserEarnedReward(amount, typeStr);
env->ReleaseStringUTFChars(type, typeStr);
}
}
5.3 iOS平台集成
5.3.1 添加依赖
在
proj.ios_mac/ios/YourGame.xcodeproj中添加GoogleMobileAds.framework5.3.2 Objective-C++封装
创建
proj.ios_mac/ios/AdMobBridge.h:#import <Foundation/Foundation.h>
#import <GoogleMobileAds/GoogleMobileAds.h>
NS_ASSUME_NONNULL_BEGIN
@interface AdMobBridge : NSObject <GADFullScreenContentDelegate, GADRewardedAdDelegate>
+ (instancetype)sharedInstance;
- (void)initWithViewController:(UIViewController *)viewController;
- (void)loadInterstitialAd:(NSString *)adUnitId;
- (void)showInterstitialAd;
- (void)loadRewardedAd:(NSString *)adUnitId;
- (void)showRewardedAd;
@end
NS_ASSUME_NONNULL_END
创建
proj.ios_mac/ios/AdMobBridge.mm:#import "AdMobBridge.h"
#import "RootViewController.h"
@interface AdMobBridge () <GADFullScreenContentDelegate, GADRewardedAdDelegate>
@property (nonatomic, strong) UIViewController *rootViewController;
@property (nonatomic, strong) GADInterstitialAd *interstitialAd;
@property (nonatomic, strong) GADRewardedAd *rewardedAd;
@property (nonatomic, copy) NSString *interstitialAdUnitId;
@property (nonatomic, copy) NSString *rewardedAdUnitId;
@end
@implementation AdMobBridge
+ (instancetype)sharedInstance {
static AdMobBridge *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[AdMobBridge alloc] init];
});
return sharedInstance;
}
- (void)initWithViewController:(UIViewController *)viewController {
self.rootViewController = viewController;
self.interstitialAdUnitId = @"ca-app-pub-3940256099942544/4411468910"; // 测试ID
self.rewardedAdUnitId = @"ca-app-pub-3940256099942544/1712485313"; // 测试ID
[GADMobileAds.sharedInstance startWithCompletionHandler:nil];
}
- (void)loadInterstitialAd:(NSString *)adUnitId {
NSString *unitId = adUnitId.length > 0 ? adUnitId : self.interstitialAdUnitId;
GADRequest *request = [GADRequest request];
__weak typeof(self) weakSelf = self;
[GADInterstitialAd loadWithAdUnitID:unitId
request:request
completionHandler:^(GADInterstitialAd *ad, NSError *error) {
if (error) {
NSLog(@"Failed to load interstitial ad: %@", error.localizedDescription);
[weakSelf nativeOnInterstitialAdLoaded:NO withError:error.localizedDescription];
return;
}
weakSelf.interstitialAd = ad;
weakSelf.interstitialAd.fullScreenContentDelegate = weakSelf;
NSLog(@"Interstitial ad loaded successfully");
[weakSelf nativeOnInterstitialAdLoaded:YES withError:nil];
}];
}
- (void)showInterstitialAd {
if (self.interstitialAd && self.rootViewController) {
[self.interstitialAd presentFromRootViewController:self.rootViewController];
} else {
NSLog(@"Interstitial ad not ready or root view controller is nil");
[self nativeOnInterstitialAdFailed:@"Ad not ready"];
}
}
- (void)loadRewardedAd:(NSString *)adUnitId {
NSString *unitId = adUnitId.length > 0 ? adUnitId : self.rewardedAdUnitId;
GADRequest *request = [GADRequest request];
__weak typeof(self) weakSelf = self;
[GADRewardedAd loadWithAdUnitID:unitId
request:request
completionHandler:^(GADRewardedAd *ad, NSError *error) {
if (error) {
NSLog(@"Failed to load rewarded ad: %@", error.localizedDescription);
[weakSelf nativeOnRewardedAdLoaded:NO withError:error.localizedDescription];
return;
}
weakSelf.rewardedAd = ad;
weakSelf.rewardedAd.fullScreenContentDelegate = weakSelf;
weakSelf.rewardedAd.paidEventHandler = ^(GADAdValue * _Nonnull value) {
NSLog(@"Rewarded ad impression recorded with value: %@ %@", value.value, value.currencyCode);
};
NSLog(@"Rewarded ad loaded successfully");
[weakSelf nativeOnRewardedAdLoaded:YES withError:nil];
}];
}
- (void)showRewardedAd {
if (self.rewardedAd && self.rootViewController) {
[self.rewardedAd presentFromRootViewController:self.rootViewController
userDidEarnRewardHandler:^{
GADAdReward *reward = self.rewardedAd.adReward;
NSLog(@"User earned reward: %@ %@", reward.amount, reward.type);
[self nativeOnUserEarnedReward:[reward.amount intValue] withType:reward.type];
}];
} else {
NSLog(@"Rewarded ad not ready or root view controller is nil");
[self nativeOnRewardedAdFailed:@"Ad not ready"];
}
}
#pragma mark - GADFullScreenContentDelegate
- (void)adDidPresentFullScreenContent:(id<GADFullScreenPresentingAd>)ad {
NSLog(@"Ad did present full screen content");
if ([ad isKindOfClass:[GADInterstitialAd class]]) {
[self nativeOnInterstitialAdOpened];
} else if ([ad isKindOfClass:[GADRewardedAd class]]) {
[self nativeOnRewardedAdOpened];
}
}
- (void)adDidDismissFullScreenContent:(id<GADFullScreenPresentingAd>)ad {
NSLog(@"Ad did dismiss full screen content");
if ([ad isKindOfClass:[GADInterstitialAd class]]) {
[self nativeOnInterstitialAdClosed];
// Reload interstitial ad
[self loadInterstitialAd:self.interstitialAdUnitId];
} else if ([ad isKindOfClass:[GADRewardedAd class]]) {
[self nativeOnRewardedAdClosed];
// Reload rewarded ad
[self loadRewardedAd:self.rewardedAdUnitId];
}
}
- (void)ad:(id<GADFullScreenPresentingAd>)ad didFailToPresentFullScreenContentWithError:(NSError *)error {
NSLog(@"Ad failed to present full screen content: %@", error.localizedDescription);
if ([ad isKindOfClass:[GADInterstitialAd class]]) {
[self nativeOnInterstitialAdFailed:error.localizedDescription];
} else if ([ad isKindOfClass:[GADRewardedAd class]]) {
[self nativeOnRewardedAdFailed:error.localizedDescription];
}
}
#pragma mark - Native Callbacks
- (void)nativeOnInterstitialAdLoaded:(BOOL)success withError:(nullable NSString *)error {
// 调用C++回调
#if defined(__cplusplus)
cocos2d::ValueMap dict;
dict["success"] = cocos2d::Value(success);
if (error) {
dict["error"] = cocos2d::Value([error UTF8String]);
}
// 这里需要通过JNI或其他方式调用C++层回调
// 简化示例,实际项目中需要完善这部分
#endif
}
- (void)nativeOnInterstitialAdOpened {
// 插页广告打开回调
}
- (void)nativeOnInterstitialAdClosed {
// 插页广告关闭回调
}
- (void)nativeOnInterstitialAdFailed:(NSString *)error {
// 插页广告失败回调
}
- (void)nativeOnRewardedAdLoaded:(BOOL)success withError:(nullable NSString *)error {
// 激励视频加载回调
}
- (void)nativeOnRewardedAdOpened {
// 激励视频打开回调
}
- (void)nativeOnRewardedAdClosed {
// 激励视频关闭回调
}
- (void)nativeOnRewardedAdFailed:(NSString *)error {
// 激励视频失败回调
}
- (void)nativeOnUserEarnedReward:(NSInteger)amount withType:(NSString *)type {
// 用户获得奖励回调
const char* typeCStr = [type UTF8String];
// 调用C++回调
}
@end
5.4 使用示例
创建测试场景
Classes/AdTestScene.cpp:#include "AdTestScene.h"
#include "AdMobManager.h"
#include "ui/CocosGUI.h"
USING_NS_CC;
Scene* AdTestScene::createScene() {
auto scene = Scene::create();
auto layer = AdTestScene::create();
scene->addChild(layer);
return scene;
}
bool AdTestScene::init() {
if (!Layer::init()) {
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 标题
auto title = Label::createWithTTF("AdMob Test", "fonts/Marker Felt.ttf", 36);
title->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - title->getContentSize().height));
this->addChild(title, 1);
// 加载插页广告按钮
auto loadInterstitialBtn = ui::Button::create("button_normal.png", "button_pressed.png");
loadInterstitialBtn->setTitleText("Load Interstitial");
loadInterstitialBtn->setTitleFontSize(24);
loadInterstitialBtn->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 + 100));
loadInterstitialBtn->addClickEventListener([this](Ref* sender) {
AdMobManager::getInstance()->loadInterstitialAd();
});
this->addChild(loadInterstitialBtn);
// 显示插页广告按钮
auto showInterstitialBtn = ui::Button::create("button_normal.png", "button_pressed.png");
showInterstitialBtn->setTitleText("Show Interstitial");
showInterstitialBtn->setTitleFontSize(24);
showInterstitialBtn->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 + 50));
showInterstitialBtn->addClickEventListener([this](Ref* sender) {
AdMobManager::getInstance()->showInterstitialAd();
});
this->addChild(showInterstitialBtn);
// 加载激励视频按钮
auto loadRewardedBtn = ui::Button::create("button_normal.png", "button_pressed.png");
loadRewardedBtn->setTitleText("Load Rewarded");
loadRewardedBtn->setTitleFontSize(24);
loadRewardedBtn->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2));
loadRewardedBtn->addClickEventListener([this](Ref* sender) {
AdMobManager::getInstance()->loadRewardedAd();
});
this->addChild(loadRewardedBtn);
// 显示激励视频按钮
auto showRewardedBtn = ui::Button::create("button_normal.png", "button_pressed.png");
showRewardedBtn->setTitleText("Show Rewarded");
showRewardedBtn->setTitleFontSize(24);
showRewardedBtn->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 - 50));
showRewardedBtn->addClickEventListener([this](Ref* sender) {
AdMobManager::getInstance()->showRewardedAd();
});
this->addChild(showRewardedBtn);
// 注册事件监听
auto listener = EventListenerCustom::create("admob_interstitial_loaded", [this](EventCustom* event) {
bool success = static_cast<Value*>(event->getUserData())->asBool();
MessageBox(success ? "Interstitial ad loaded successfully!" : "Interstitial ad load failed!", "AdMob");
});
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
listener = EventListenerCustom::create("admob_user_earned_reward", [this](EventCustom* event) {
ValueMap* reward = static_cast<ValueMap*>(event->getUserData());
int amount = (*reward)["amount"].asInt();
std::string type = (*reward)["type"].asString();
std::string message = "You earned " + std::to_string(amount) + " " + type + "!";
MessageBox(message.c_str(), "Reward Earned!");
});
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
// 初始化AdMob
AdMobManager::getInstance()->init();
return true;
}
6. 支付SDK集成(以Google Play Billing为例)
6.1 原理概述
Google Play Billing通过AIDL接口与Android应用交互,Cocos2d-x通过JNI桥接调用Java层API完成支付流程。
6.2 Android支付集成
6.2.1 添加依赖
在
proj.android/app/build.gradle中添加:dependencies {
implementation 'com.android.billingclient:billing:5.0.0'
}
6.2.2 权限配置
在
proj.android/app/src/main/AndroidManifest.xml中添加:<uses-permission android:name="com.android.vending.BILLING" />
6.2.3 Java层支付逻辑
创建
proj.android/app/src/org/cocos2dx/cpp/BillingManager.java:package org.cocos2dx.cpp;
import android.app.Activity;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.billingclient.api.*;
import java.util.ArrayList;
import java.util.List;
public class BillingManager implements PurchasesUpdatedListener, BillingClientStateListener {
private static final String TAG = "BillingManager";
private static BillingManager instance;
private Activity activity;
private BillingClient billingClient;
private List<Purchase> purchases = new ArrayList<>();
public interface BillingListener {
void onProductsLoaded(List<SkuDetails> products);
void onPurchaseSuccess(Purchase purchase);
void onPurchaseFailed(int responseCode, String message);
void onPurchaseConsumed(String purchaseToken);
void onBillingServiceDisconnected();
}
private BillingListener listener;
private BillingManager() {}
public static synchronized BillingManager getInstance() {
if (instance == null) {
instance = new BillingManager();
}
return instance;
}
public void init(Activity activity, BillingListener listener) {
this.activity = activity;
this.listener = listener;
billingClient = BillingClient.newBuilder(activity)
.setListener(this)
.enablePendingPurchases()
.build();
startConnection();
}
private void startConnection() {
if (billingClient != null) {
billingClient.startConnection(this);
}
}
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "Billing service connected");
queryPurchases();
} else {
Log.e(TAG, "Billing setup failed: " + billingResult.getDebugMessage());
if (listener != null) {
listener.onBillingServiceDisconnected();
}
}
}
@Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "Billing service disconnected");
if (listener != null) {
listener.onBillingServiceDisconnected();
}
// 尝试重连
startConnection();
}
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @NonNull List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases.size() > 0) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
Log.i(TAG, "User canceled the purchase");
if (listener != null) {
listener.onPurchaseFailed(billingResult.getResponseCode(), "User canceled");
}
} else {
Log.e(TAG, "Purchase failed: " + billingResult.getDebugMessage());
if (listener != null) {
listener.onPurchaseFailed(billingResult.getResponseCode(), billingResult.getDebugMessage());
}
}
}
// 查询商品信息
public void queryProducts(List<String> skuList, @BillingClient.ProductType String productType) {
if (billingClient == null || !billingClient.isReady()) {
Log.e(TAG, "Billing client not ready");
return;
}
SkuDetailsParams params = SkuDetailsParams.newBuilder()
.setSkusList(skuList)
.setType(productType)
.build();
billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(@NonNull BillingResult billingResult, @NonNull List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList.size() > 0) {
Log.d(TAG, "Products loaded: " + skuDetailsList.size());
if (listener != null) {
listener.onProductsLoaded(skuDetailsList);
}
} else {
Log.e(TAG, "Failed to load products: " + billingResult.getDebugMessage());
}
}
});
}
// 发起购买
public void launchPurchaseFlow(SkuDetails skuDetails) {
if (billingClient == null || !billingClient.isReady() || activity == null) {
Log.e(TAG, "Billing client not ready or activity is null");
return;
}
Activity activity = this.activity;
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
billingClient.launchBillingFlow(activity, flowParams);
}
// 消耗商品(一次性购买)
public void consumePurchase(String purchaseToken) {
if (billingClient == null || !billingClient.isReady()) {
Log.e(TAG, "Billing client not ready");
return;
}
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build();
billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
@Override
public void onConsumeResponse(@NonNull BillingResult billingResult, @NonNull String outToken) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "Purchase consumed successfully");
if (listener != null) {
listener.onPurchaseConsumed(outToken);
}
} else {
Log.e(TAG, "Failed to consume purchase: " + billingResult.getDebugMessage());
}
}
});
}
// 查询已购买商品
public void queryPurchases() {
if (billingClient == null || !billingClient.isReady()) {
return;
}
// 查询非消耗型商品和订阅
Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
processPurchases(purchasesResult.getPurchasesList());
// 查询订阅
purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
processPurchases(purchasesResult.getPurchasesList());
}
private void processPurchases(List<Purchase> purchases) {
if (purchases == null) return;
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
}
private void handlePurchase(Purchase purchase) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged()) {
// 对于需要确认的商品进行确认
if (purchase.getSkuType() == BillingClient.SkuType.INAPP) {
consumePurchase(purchase.getPurchaseToken());
} else {
// 对于订阅,调用确认API
acknowledgePurchase(purchase.getPurchaseToken());
}
}
if (listener != null) {
listener.onPurchaseSuccess(purchase);
}
}
}
// 确认购买(用于订阅等不需要消耗的商品)
private void acknowledgePurchase(String purchaseToken) {
if (billingClient == null || !billingClient.isReady()) {
return;
}
AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
@Override
public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "Purchase acknowledged successfully");
} else {
Log.e(TAG, "Failed to acknowledge purchase: " + billingResult.getDebugMessage());
}
}
});
}
// Native回调方法
public static native void nativeOnProductsLoaded(String jsonProducts);
public static native void nativeOnPurchaseSuccess(String jsonPurchase);
public static native void nativeOnPurchaseFailed(int responseCode, String message);
public static native void nativeOnPurchaseConsumed(String purchaseToken);
public static native void nativeOnBillingServiceDisconnected();
}
6.2.4 C++层支付封装
创建
Classes/BillingManager.h:#ifndef __BILLING_MANAGER_H__
#define __BILLING_MANAGER_H__
#include "cocos2d.h"
#include <string>
#include <vector>
#include <functional>
NS_CC_BEGIN
// 商品信息结构
struct ProductInfo {
std::string productId;
std::string title;
std::string description;
std::string price;
std::string priceCurrencyCode;
std::string priceAmountMicros;
std::string type;
};
// 购买信息结构
struct PurchaseInfo {
std::string orderId;
std::string packageName;
std::string productId;
long purchaseTime;
int purchaseState;
std::string developerPayload;
std::string purchaseToken;
bool acknowledged;
std::string originalJson;
};
class BillingManager {
public:
static BillingManager* getInstance();
virtual ~BillingManager();
// 初始化
void init();
// 查询商品
void queryProducts(const std::vector<std::string>& productIds, const std::string& productType = "inapp");
// 发起购买
void launchPurchaseFlow(const std::string& productId);
// 消耗商品
void consumePurchase(const std::string& purchaseToken);
// 设置回调
void setProductsLoadedCallback(const std::function<void(const std::vector<ProductInfo>&)>& callback);
void setPurchaseSuccessCallback(const std::function<void(const PurchaseInfo&)>& callback);
void setPurchaseFailedCallback(const std::function<void(int, const std::string&)>& callback);
void setPurchaseConsumedCallback(const std::function<void(const std::string&)>& callback);
void setBillingServiceDisconnectedCallback(const std::function<void()>& callback);
private:
BillingManager();
static BillingManager* _instance;
std::function<void(const std::vector<ProductInfo>&)> _productsLoadedCallback;
std::function<void(const PurchaseInfo&)> _purchaseSuccessCallback;
std::function<void(int, const std::string&)> _purchaseFailedCallback;
std::function<void(const std::string&)> _purchaseConsumedCallback;
std::function<void()> _billingServiceDisconnectedCallback;
// JSON解析辅助函数
std::vector<ProductInfo> parseProductsFromJson(const std::string& json);
PurchaseInfo parsePurchaseFromJson(const std::string& json);
};
NS_CC_END
#endif // __BILLING_MANAGER_H__
创建
Classes/BillingManager.cpp:#include "BillingManager.h"
#include "platform/android/jni/JniHelper.h"
#include "json/document.h"
#include "json/stringbuffer.h"
#include "json/writer.h"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include <jni.h>
#endif
USING_NS_CC;
static const char* CLASS_NAME = "org/cocos2dx/cpp/BillingManager";
BillingManager* BillingManager::_instance = nullptr;
BillingManager::BillingManager() {
}
BillingManager::~BillingManager() {
}
BillingManager* BillingManager::getInstance() {
if (_instance == nullptr) {
_instance = new BillingManager();
}
return _instance;
}
void BillingManager::init() {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "init", "(Landroid/app/Activity;Lorg/cocos2dx/cpp/BillingManager$BillingListener;)V")) {
jclass clazz = methodInfo.env->FindClass("org/cocos2dx/cpp/AppActivity");
jobject activity = methodInfo.env->CallStaticObjectMethod(clazz,
methodInfo.env->GetStaticMethodID(clazz, "getActivity", "()Landroid/app/Activity;"));
// 创建监听器接口的匿名实现
jclass listenerClass = methodInfo.env->FindClass("org/cocos2dx/cpp/BillingManager$BillingListener");
// 这里简化处理,实际需要创建完整的接口实现
// 在实际项目中,应该创建一个Java类实现该接口并通过JNI传递
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, activity, nullptr);
methodInfo.env->DeleteLocalRef(activity);
methodInfo.env->DeleteLocalRef(clazz);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void BillingManager::queryProducts(const std::vector<std::string>& productIds, const std::string& productType) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "queryProducts", "(Ljava/util/List;Ljava/lang/String;)V")) {
// 创建Java List对象并添加商品ID
// 这里简化处理,实际需要创建ArrayList并添加元素
jobject jProductList = nullptr; // 实际应创建ArrayList
jstring jProductType = methodInfo.env->NewStringUTF(productType.c_str());
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jProductList, jProductType);
if (jProductList) {
methodInfo.env->DeleteLocalRef(jProductList);
}
methodInfo.env->DeleteLocalRef(jProductType);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void BillingManager::launchPurchaseFlow(const std::string& productId) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "launchPurchaseFlow", "(Ljava/lang/String;)V")) {
jstring jProductId = methodInfo.env->NewStringUTF(productId.c_str());
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jProductId);
methodInfo.env->DeleteLocalRef(jProductId);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void BillingManager::consumePurchase(const std::string& purchaseToken) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "consumePurchase", "(Ljava/lang/String;)V")) {
jstring jPurchaseToken = methodInfo.env->NewStringUTF(purchaseToken.c_str());
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jPurchaseToken);
methodInfo.env->DeleteLocalRef(jPurchaseToken);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void BillingManager::setProductsLoadedCallback(const std::function<void(const std::vector<ProductInfo>&)>& callback) {
_productsLoadedCallback = callback;
}
void BillingManager::setPurchaseSuccessCallback(const std::function<void(const PurchaseInfo&)>& callback) {
_purchaseSuccessCallback = callback;
}
void BillingManager::setPurchaseFailedCallback(const std::function<void(int, const std::string&)>& callback) {
_purchaseFailedCallback = callback;
}
void BillingManager::setPurchaseConsumedCallback(const std::function<void(const std::string&)>& callback) {
_purchaseConsumedCallback = callback;
}
void BillingManager::setBillingServiceDisconnectedCallback(const std::function<void()>& callback) {
_billingServiceDisconnectedCallback = callback;
}
std::vector<BillingManager::ProductInfo> BillingManager::parseProductsFromJson(const std::string& json) {
std::vector<ProductInfo> products;
rapidjson::Document doc;
doc.Parse(json.c_str());
if (doc.IsArray()) {
for (rapidjson::SizeType i = 0; i < doc.Size(); i++) {
const rapidjson::Value& item = doc[i];
ProductInfo product;
if (item.HasMember("productId") && item["productId"].IsString()) {
product.productId = item["productId"].GetString();
}
if (item.HasMember("title") && item["title"].IsString()) {
product.title = item["title"].GetString();
}
if (item.HasMember("description") && item["description"].IsString()) {
product.description = item["description"].GetString();
}
if (item.HasMember("price") && item["price"].IsString()) {
product.price = item["price"].GetString();
}
if (item.HasMember("priceCurrencyCode") && item["priceCurrencyCode"].IsString()) {
product.priceCurrencyCode = item["priceCurrencyCode"].GetString();
}
if (item.HasMember("priceAmountMicros") && item["priceAmountMicros"].IsString()) {
product.priceAmountMicros = item["priceAmountMicros"].GetString();
}
if (item.HasMember("type") && item["type"].IsString()) {
product.type = item["type"].GetString();
}
products.push_back(product);
}
}
return products;
}
BillingManager::PurchaseInfo BillingManager::parsePurchaseFromJson(const std::string& json) {
PurchaseInfo purchase;
rapidjson::Document doc;
doc.Parse(json.c_str());
if (doc.IsObject()) {
if (doc.HasMember("orderId") && doc["orderId"].IsString()) {
purchase.orderId = doc["orderId"].GetString();
}
if (doc.HasMember("packageName") && doc["packageName"].IsString()) {
purchase.packageName = doc["packageName"].GetString();
}
if (doc.HasMember("productId") && doc["productId"].IsString()) {
purchase.productId = doc["productId"].GetString();
}
if (doc.HasMember("purchaseTime") && doc["purchaseTime"].IsInt64()) {
purchase.purchaseTime = doc["purchaseTime"].GetInt64();
}
if (doc.HasMember("purchaseState") && doc["purchaseState"].IsInt()) {
purchase.purchaseState = doc["purchaseState"].GetInt();
}
if (doc.HasMember("developerPayload") && doc["developerPayload"].IsString()) {
purchase.developerPayload = doc["developerPayload"].GetString();
}
if (doc.HasMember("purchaseToken") && doc["purchaseToken"].IsString()) {
purchase.purchaseToken = doc["purchaseToken"].GetString();
}
if (doc.HasMember("acknowledged") && doc["acknowledged"].IsBool()) {
purchase.acknowledged = doc["acknowledged"].GetBool();
}
if (doc.HasMember("originalJson") && doc["originalJson"].IsString()) {
purchase.originalJson = doc["originalJson"].GetString();
}
}
return purchase;
}
// JNI回调实现
extern "C" {
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_BillingManager_nativeOnProductsLoaded(JNIEnv* env, jclass clazz, jstring jsonProducts) {
const char* jsonStr = env->GetStringUTFChars(jsonProducts, nullptr);
BillingManager::getInstance()->setProductsLoadedCallback(nullptr); // 简化处理
// 实际应解析JSON并调用回调函数
env->ReleaseStringUTFChars(jsonProducts, jsonStr);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_BillingManager_nativeOnPurchaseSuccess(JNIEnv* env, jclass clazz, jstring jsonPurchase) {
const char* jsonStr = env->GetStringUTFChars(jsonPurchase, nullptr);
// 解析JSON并调用购买成功回调
env->ReleaseStringUTFChars(jsonPurchase, jsonStr);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_BillingManager_nativeOnPurchaseFailed(JNIEnv* env, jclass clazz, jint responseCode, jstring message) {
const char* msgStr = env->GetStringUTFChars(message, nullptr);
// 调用购买失败回调
env->ReleaseStringUTFChars(message, msgStr);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_BillingManager_nativeOnPurchaseConsumed(JNIEnv* env, jclass clazz, jstring purchaseToken) {
const char* tokenStr = env->GetStringUTFChars(purchaseToken, nullptr);
// 调用消耗成功回调
env->ReleaseStringUTFChars(purchaseToken, tokenStr);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_BillingManager_nativeOnBillingServiceDisconnected(JNIEnv* env, jclass clazz) {
// 调用服务断开连接回调
}
}
6.3 支付使用示例
创建支付测试场景
Classes/BillingTestScene.cpp:#include "BillingTestScene.h"
#include "BillingManager.h"
#include "ui/CocosGUI.h"
USING_NS_CC;
Scene* BillingTestScene::createScene() {
auto scene = Scene::create();
auto layer = BillingTestScene::create();
scene->addChild(layer);
return scene;
}
bool BillingTestScene::init() {
if (!Layer::init()) {
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 标题
auto title = Label::createWithTTF("Billing Test", "fonts/Marker Felt.ttf", 36);
title->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - title->getContentSize().height));
this->addChild(title, 1);
// 测试商品ID列表
std::vector<std::string> testProductIds = {
"android.test.purchased",
"android.test.canceled",
"android.test.item_unavailable"
};
// 查询商品按钮
auto queryProductsBtn = ui::Button::create("button_normal.png", "button_pressed.png");
queryProductsBtn->setTitleText("Query Products");
queryProductsBtn->setTitleFontSize(24);
queryProductsBtn->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 + 100));
queryProductsBtn->addClickEventListener([this, testProductIds](Ref* sender) {
BillingManager::getInstance()->queryProducts(testProductIds, "inapp");
});
this->addChild(queryProductsBtn);
// 购买商品按钮
auto purchaseBtn = ui::Button::create("button_normal.png", "button_pressed.png");
purchaseBtn->setTitleText("Purchase Test Item");
purchaseBtn->setTitleFontSize(24);
purchaseBtn->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 + 50));
purchaseBtn->addClickEventListener([this](Ref* sender) {
BillingManager::getInstance()->launchPurchaseFlow("android.test.purchased");
});
this->addChild(purchaseBtn);
// 设置回调
auto billingMgr = BillingManager::getInstance();
billingMgr->setProductsLoadedCallback([this](const std::vector<BillingManager::ProductInfo>& products) {
std::string message = "Loaded " + std::to_string(products.size()) + " products:\n";
for (const auto& product : products) {
message += product.productId + ": " + product.price + "\n";
}
MessageBox(message.c_str(), "Products Loaded");
});
billingMgr->setPurchaseSuccessCallback([this](const BillingManager::PurchaseInfo& purchase) {
std::string message = "Purchase successful!\nProduct: " + purchase.productId +
"\nOrder ID: " + purchase.orderId;
MessageBox(message.c_str(), "Purchase Success");
});
billingMgr->setPurchaseFailedCallback([this](int responseCode, const std::string& message) {
std::string errorMsg = "Purchase failed!\nCode: " + std::to_string(responseCode) +
"\nMessage: " + message;
MessageBox(errorMsg.c_str(), "Purchase Failed");
});
billingMgr->setBillingServiceDisconnectedCallback([this]() {
MessageBox("Billing service disconnected. Please check your connection.", "Billing Error");
});
// 初始化Billing
billingMgr->init();
return true;
}
7. 分享SDK集成(以ShareSDK为例)
7.1 原理概述
ShareSDK通过桥接各社交平台的原生SDK,提供统一的分享接口。Cocos2d-x通过JNI调用Java层的ShareSDK API完成分享功能。
7.2 Android分享集成
7.2.1 添加依赖
在
proj.android/app/build.gradle中添加:dependencies {
implementation 'cn.sharesdk:ShareSDK:3.9.9' // 包含常用平台
// 或者单独添加平台
// implementation 'cn.sharesdk:ShareSDK-Wechat:3.9.9'
// implementation 'cn.sharesdk:ShareSDK-Wechat-Favorite:3.9.9'
// implementation 'cn.sharesdk:ShareSDK-QQ:3.9.9'
// implementation 'cn.sharesdk:ShareSDK-QZone:3.9.9'
// implementation 'cn.sharesdk:ShareSDK-SinaWeibo:3.9.9'
}
7.2.2 权限配置
在
AndroidManifest.xml中添加:<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!-- 外部存储权限(可选,用于保存图片) -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
7.2.3 分享功能实现
创建
proj.android/app/src/org/cocos2dx/cpp/ShareManager.java:package org.cocos2dx.cpp;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.TextUtils;
import android.util.Log;
import cn.sharesdk.framework.Platform;
import cn.sharesdk.framework.PlatformActionListener;
import cn.sharesdk.framework.ShareSDK;
import cn.sharesdk.onekeyshare.OnekeyShare;
import cn.sharesdk.wechat.friends.Wechat;
import cn.sharesdk.wechat.moments.WechatMoments;
import cn.sharesdk.tencent.qq.QQ;
import cn.sharesdk.tencent.qzone.QZone;
import cn.sharesdk.sina.weibo.SinaWeibo;
import java.io.File;
import java.util.HashMap;
public class ShareManager {
private static final String TAG = "ShareManager";
private static ShareManager instance;
private Activity activity;
public interface ShareListener {
void onShareSuccess();
void onShareFailure(String errorMessage);
void onShareCancel();
}
private ShareListener listener;
private ShareManager() {}
public static synchronized ShareManager getInstance() {
if (instance == null) {
instance = new ShareManager();
}
return instance;
}
public void init(Activity activity) {
this.activity = activity;
ShareSDK.initSDK(activity);
Log.d(TAG, "ShareSDK initialized");
}
// 一键分享
public void oneKeyShare(HashMap<String, Object> params, ShareListener listener) {
this.listener = listener;
OnekeyShare oks = new OnekeyShare();
oks.setSilent(false);
// 设置分享参数
if (params.containsKey("title")) {
oks.setTitle((String) params.get("title"));
}
if (params.containsKey("titleUrl")) {
oks.setTitleUrl((String) params.get("titleUrl"));
}
if (params.containsKey("text")) {
oks.setText((String) params.get("text"));
}
if (params.containsKey("imagePath")) {
String imagePath = (String) params.get("imagePath");
if (!TextUtils.isEmpty(imagePath) && new File(imagePath).exists()) {
oks.setImagePath(imagePath);
}
}
if (params.containsKey("url")) {
oks.setUrl((String) params.get("url"));
}
if (params.containsKey("comment")) {
oks.setComment((String) params.get("comment"));
}
if (params.containsKey("site")) {
oks.setSite((String) params.get("site"));
}
if (params.containsKey("siteUrl")) {
oks.setSiteUrl((String) params.get("siteUrl"));
}
// 设置平台
if (params.containsKey("platform")) {
String platform = (String) params.get("platform");
if ("wechat".equals(platform)) {
oks.setPlatform(Wechat.NAME);
} else if ("wechat_moments".equals(platform)) {
oks.setPlatform(WechatMoments.NAME);
} else if ("qq".equals(platform)) {
oks.setPlatform(QQ.NAME);
} else if ("qzone".equals(platform)) {
oks.setPlatform(QZone.NAME);
} else if ("weibo".equals(platform)) {
oks.setPlatform(SinaWeibo.NAME);
}
}
// 设置回调
oks.setCallback(new PlatformActionListener() {
@Override
public void onComplete(Platform platform, int action, HashMap<String, Object> res) {
Log.d(TAG, "Share completed: " + platform.getName());
if (listener != null) {
listener.onShareSuccess();
}
nativeOnShareSuccess(platform.getName());
}
@Override
public void onError(Platform platform, int action, Throwable t) {
Log.e(TAG, "Share error: " + t.getMessage());
if (listener != null) {
listener.onShareFailure(t.getMessage());
}
nativeOnShareFailure(platform.getName(), t.getMessage());
}
@Override
public void onCancel(Platform platform, int action) {
Log.i(TAG, "Share cancelled: " + platform.getName());
if (listener != null) {
listener.onShareCancel();
}
nativeOnShareCancel(platform.getName());
}
});
oks.show(activity);
}
// 自定义分享到指定平台
public void shareToPlatform(String platformName, HashMap<String, Object> params, ShareListener listener) {
this.listener = listener;
Platform platform = ShareSDK.getPlatform(platformName);
if (platform == null) {
Log.e(TAG, "Platform not found: " + platformName);
if (listener != null) {
listener.onShareFailure("Platform not found");
}
return;
}
Platform.ShareParams shareParams = new Platform.ShareParams();
// 设置分享参数
if (params.containsKey("title")) {
shareParams.setTitle((String) params.get("title"));
}
if (params.containsKey("titleUrl")) {
shareParams.setTitleUrl((String) params.get("titleUrl"));
}
if (params.containsKey("text")) {
shareParams.setText((String) params.get("text"));
}
if (params.containsKey("imagePath")) {
shareParams.setImagePath((String) params.get("imagePath"));
}
if (params.containsKey("url")) {
shareParams.setUrl((String) params.get("url"));
}
if (params.containsKey("comment")) {
shareParams.setComment((String) params.get("comment"));
}
if (params.containsKey("site")) {
shareParams.setSite((String) params.get("site"));
}
if (params.containsKey("siteUrl")) {
shareParams.setSiteUrl((String) params.get("siteUrl"));
}
platform.setPlatformActionListener(new PlatformActionListener() {
@Override
public void onComplete(Platform platform, int action, HashMap<String, Object> res) {
Log.d(TAG, "Share completed: " + platform.getName());
if (listener != null) {
listener.onShareSuccess();
}
nativeOnShareSuccess(platform.getName());
}
@Override
public void onError(Platform platform, int action, Throwable t) {
Log.e(TAG, "Share error: " + t.getMessage());
if (listener != null) {
listener.onShareFailure(t.getMessage());
}
nativeOnShareFailure(platform.getName(), t.getMessage());
}
@Override
public void onCancel(Platform platform, int action) {
Log.i(TAG, "Share cancelled: " + platform.getName());
if (listener != null) {
listener.onShareCancel();
}
nativeOnShareCancel(platform.getName());
}
});
platform.share(shareParams);
}
// Native回调方法
public static native void nativeOnShareSuccess(String platform);
public static native void nativeOnShareFailure(String platform, String error);
public static native void nativeOnShareCancel(String platform);
}
7.2.4 C++层分享封装
创建
Classes/ShareManager.h:#ifndef __SHARE_MANAGER_H__
#define __SHARE_MANAGER_H__
#include "cocos2d.h"
#include <string>
#include <map>
#include <functional>
NS_CC_BEGIN
class ShareManager {
public:
static ShareManager* getInstance();
virtual ~ShareManager();
// 初始化
void init();
// 一键分享
void oneKeyShare(const std::map<std::string, std::string>& params);
// 分享到指定平台
void shareToPlatform(const std::string& platform, const std::map<std::string, std::string>& params);
// 设置回调
void setShareSuccessCallback(const std::function<void(const std::string&)>& callback);
void setShareFailureCallback(const std::function<void(const std::string&, const std::string&)>& callback);
void setShareCancelCallback(const std::function<void(const std::string&)>& callback);
private:
ShareManager();
static ShareManager* _instance;
std::function<void(const std::string&)> _shareSuccessCallback;
std::function<void(const std::string&, const std::string&)> _shareFailureCallback;
std::function<void(const std::string&)> _shareCancelCallback;
};
NS_CC_END
#endif // __SHARE_MANAGER_H__
创建
Classes/ShareManager.cpp:#include "ShareManager.h"
#include "platform/android/jni/JniHelper.h"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include <jni.h>
#include <map>
#include <android/log.h>
#endif
USING_NS_CC;
static const char* CLASS_NAME = "org/cocos2dx/cpp/ShareManager";
ShareManager* ShareManager::_instance = nullptr;
ShareManager::ShareManager() {
}
ShareManager::~ShareManager() {
}
ShareManager* ShareManager::getInstance() {
if (_instance == nullptr) {
_instance = new ShareManager();
}
return _instance;
}
void ShareManager::init() {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "init", "(Landroid/app/Activity;)V")) {
jclass clazz = methodInfo.env->FindClass("org/cocos2dx/cpp/AppActivity");
jobject activity = methodInfo.env->CallStaticObjectMethod(clazz,
methodInfo.env->GetStaticMethodID(clazz, "getActivity", "()Landroid/app/Activity;"));
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, activity);
methodInfo.env->DeleteLocalRef(activity);
methodInfo.env->DeleteLocalRef(clazz);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void ShareManager::oneKeyShare(const std::map<std::string, std::string>& params) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "oneKeyShare", "(Ljava/util/HashMap;Lorg/cocos2dx/cpp/ShareManager$ShareListener;)V")) {
// 创建Java HashMap并填充数据
jclass hashMapClass = methodInfo.env->FindClass("java/util/HashMap");
jmethodID hashMapConstructor = methodInfo.env->GetMethodID(hashMapClass, "<init>", "()V");
jobject jHashMap = methodInfo.env->NewObject(hashMapClass, hashMapConstructor);
jmethodID putMethod = methodInfo.env->GetMethodID(hashMapClass, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
for (const auto& param : params) {
jstring jKey = methodInfo.env->NewStringUTF(param.first.c_str());
jstring jValue = methodInfo.env->NewStringUTF(param.second.c_str());
methodInfo.env->CallObjectMethod(jHashMap, putMethod, jKey, jValue);
methodInfo.env->DeleteLocalRef(jKey);
methodInfo.env->DeleteLocalRef(jValue);
}
// 创建ShareListener接口的匿名实现
// 这里简化处理,实际需要创建完整的接口实现
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jHashMap, nullptr);
methodInfo.env->DeleteLocalRef(jHashMap);
methodInfo.env->DeleteLocalRef(hashMapClass);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void ShareManager::shareToPlatform(const std::string& platform, const std::map<std::string, std::string>& params) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "shareToPlatform",
"(Ljava/lang/String;Ljava/util/HashMap;Lorg/cocos2dx/cpp/ShareManager$ShareListener;)V")) {
jstring jPlatform = methodInfo.env->NewStringUTF(platform.c_str());
// 创建Java HashMap并填充数据
jclass hashMapClass = methodInfo.env->FindClass("java/util/HashMap");
jmethodID hashMapConstructor = methodInfo.env->GetMethodID(hashMapClass, "<init>", "()V");
jobject jHashMap = methodInfo.env->NewObject(hashMapClass, hashMapConstructor);
jmethodID putMethod = methodInfo.env->GetMethodID(hashMapClass, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
for (const auto& param : params) {
jstring jKey = methodInfo.env->NewStringUTF(param.first.c_str());
jstring jValue = methodInfo.env->NewStringUTF(param.second.c_str());
methodInfo.env->CallObjectMethod(jHashMap, putMethod, jKey, jValue);
methodInfo.env->DeleteLocalRef(jKey);
methodInfo.env->DeleteLocalRef(jValue);
}
// 创建ShareListener接口的匿名实现
// 这里简化处理,实际需要创建完整的接口实现
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jPlatform, jHashMap, nullptr);
methodInfo.env->DeleteLocalRef(jPlatform);
methodInfo.env->DeleteLocalRef(jHashMap);
methodInfo.env->DeleteLocalRef(hashMapClass);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
#endif
}
void ShareManager::setShareSuccessCallback(const std::function<void(const std::string&)>& callback) {
_shareSuccessCallback = callback;
}
void ShareManager::setShareFailureCallback(const std::function<void(const std::string&, const std::string&)>& callback) {
_shareFailureCallback = callback;
}
void ShareManager::setShareCancelCallback(const std::function<void(const std::string&)>& callback) {
_shareCancelCallback = callback;
}
// JNI回调实现
extern "C" {
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_ShareManager_nativeOnShareSuccess(JNIEnv* env, jclass clazz, jstring platform) {
const char* platformStr = env->GetStringUTFChars(platform, nullptr);
// 调用分享成功回调
env->ReleaseStringUTFChars(platform, platformStr);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_ShareManager_nativeOnShareFailure(JNIEnv* env, jclass clazz, jstring platform, jstring error) {
const char* platformStr = env->GetStringUTFChars(platform, nullptr);
const char* errorStr = env->GetStringUTFChars(error, nullptr);
// 调用分享失败回调
env->ReleaseStringUTFChars(platform, platformStr);
env->ReleaseStringUTFChars(error, errorStr);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_ShareManager_nativeOnShareCancel(JNIEnv* env, jclass clazz, jstring platform) {
const char* platformStr = env->GetStringUTFChars(platform, nullptr);
// 调用分享取消回调
env->ReleaseStringUTFChars(platform, platformStr);
}
}
7.3 分享使用示例
创建分享测试场景
Classes/ShareTestScene.cpp:#include "ShareTestScene.h"
#include "ShareManager.h"
#include "ui/CocosGUI.h"
#include "platform/CCFileUtils.h"
USING_NS_CC;
Scene* ShareTestScene::createScene() {
auto scene = Scene::create();
auto layer = ShareTestScene::create();
scene->addChild(layer);
return scene;
}
bool ShareTestScene::init() {
if (!Layer::init()) {
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 标题
auto title = Label::createWithTTF("Share Test", "fonts/Marker Felt.ttf", 36);
title->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - title->getContentSize().height));
this->addChild(title, 1);
// 一键分享按钮
auto oneKeyShareBtn = ui::Button::create("button_normal.png", "button_pressed.png");
oneKeyShareBtn->setTitleText("One Key Share");
oneKeyShareBtn->setTitleFontSize(24);
oneKeyShareBtn->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 + 100));
oneKeyShareBtn->addClickEventListener([this](Ref* sender) {
std::map<std::string, std::string> params;
params["title"] = "My Awesome Game";
params["text"] = "Come play my game! It's really fun!";
params["url"] = "https://www.example.com/game";
params["comment"] = "Check out this cool game!";
params["site"] = "My Game";
params["siteUrl"] = "https://www.example.com";
// 如果有本地图片,可以设置imagePath
// std::string writablePath = FileUtils::getInstance()->getWritablePath();
// params["imagePath"] = writablePath + "share_image.jpg";
ShareManager::getInstance()->oneKeyShare(params);
});
this->addChild(oneKeyShareBtn);
// 分享到微信按钮
auto shareWechatBtn = ui::Button::create("button_normal.png", "button_pressed.png");
shareWechatBtn->setTitleText("Share to WeChat");
shareWechatBtn->setTitleFontSize(24);
shareWechatBtn->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 + 50));
shareWechatBtn->addClickEventListener([this](Ref* sender) {
std::map<std::string, std::string> params;
params["title"] = "My Awesome Game";
params["text"] = "Playing this amazing game!";
params["url"] = "https://www.example.com/game";
ShareManager::getInstance()->shareToPlatform("wechat", params);
});
this->addChild(shareWechatBtn);
// 分享到微博按钮
auto shareWeiboBtn = ui::Button::create("button_normal.png", "button_pressed.png");
shareWeiboBtn->setTitleText("Share to Weibo");
shareWeiboBtn->setTitleFontSize(24);
shareWeiboBtn->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2));
shareWeiboBtn->addClickEventListener([this](Ref* sender) {
std::map<std::string, std::string> params;
params["title"] = "My Awesome Game";
params["text"] = "Just played this incredible game! You should try it too! https://www.example.com/game";
params["url"] = "https://www.example.com/game";
ShareManager::getInstance()->shareToPlatform("weibo", params);
});
this->addChild(shareWeiboBtn);
// 设置回调
auto shareMgr = ShareManager::getInstance();
shareMgr->setShareSuccessCallback([this](const std::string& platform) {
std::string message = "Shared successfully to " + platform + "!";
MessageBox(message.c_str(), "Share Success");
});
shareMgr->setShareFailureCallback([this](const std::string& platform, const std::string& error) {
std::string message = "Share to " + platform + " failed: " + error;
MessageBox(message.c_str(), "Share Failed");
});
shareMgr->setShareCancelCallback([this](const std::string& platform) {
std::string message = "Share to " + platform + " was cancelled.";
MessageBox(message.c_str(), "Share Cancelled");
});
// 初始化ShareSDK
shareMgr->init();
return true;
}
8. 原理流程图
8.1 广告SDK集成流程图
+-------------------+ +---------------------+ +------------------+
| C++ Game Logic | --> | JNI Interface | --> | Android SDK |
| | | (AdMobManager.cpp)| | (AdMobBridge.java|
+-------------------+ +---------------------+ +---------+--------+
|
v
+-------+--------+
| Google AdMob |
| Servers |
+----------------+
8.2 支付SDK集成流程图
+-------------------+ +---------------------+ +------------------+
| C++ Game Logic | --> | JNI Interface | --> | Android SDK |
| | | (BillingManager.cpp)| |(BillingManager.java|
+-------------------+ +---------------------+ +---------+--------+
|
v
+-------+--------+
| Google Play |
| Billing API |
+----------------+
8.3 分享SDK集成流程图
+-------------------+ +---------------------+ +------------------+
| C++ Game Logic | --> | JNI Interface | --> | Android SDK |
| | | (ShareManager.cpp) | |(ShareManager.java|
+-------------------+ +---------------------+ +---------+--------+
|
v
+-------+--------+
| Social Media |
| Platforms |
+----------------+
9. 核心特性
9.1 跨平台兼容性
-
统一的C++接口,屏蔽平台差异
-
自动适配Android和iOS平台特性
-
一致的API设计,降低学习成本
9.2 生命周期管理
-
与Cocos2d-x生命周期自动同步
-
正确处理前后台切换场景
-
资源自动释放,避免内存泄漏
9.3 异步回调机制
-
基于事件驱动的回调系统
-
线程安全的回调处理
-
支持多回调并存
9.4 错误处理
-
完善的错误码体系
-
详细的错误日志输出
-
自动重试和恢复机制
9.5 性能优化
-
最小化JNI调用开销
-
智能缓存策略
-
后台预加载机制
10. 运行结果
10.1 广告集成效果
-
成功加载并显示AdMob广告
-
激励视频观看完成后正确发放奖励
-
插页广告展示不影响游戏流畅性
10.2 支付集成效果
-
成功查询商品信息和价格
-
完成购买流程并验证交易
-
正确处理消耗型和订阅型商品
10.3 分享集成效果
-
成功分享到微信、微博等平台
-
正确传递分享内容和图片
-
准确捕获分享成功/失败状态
11. 测试步骤
11.1 环境准备
-
安装Android Studio和NDK
-
配置Cocos2d-x开发环境
-
准备测试设备(Android/iOS)
-
申请各SDK的测试账号和应用ID
11.2 广告测试步骤
-
编译并运行Android/iOS版本
-
进入广告测试场景
-
点击"Load Interstitial"按钮加载广告
-
观察日志确认广告加载状态
-
点击"Show Interstitial"显示广告
-
测试激励视频的完整流程
-
验证奖励发放是否正确
11.3 支付测试步骤
-
在Google Play Console配置测试商品
-
使用测试账号登录设备
-
编译并运行应用
-
进入支付测试场景
-
点击"Query Products"查询商品
-
点击"Purchase Test Item"发起购买
-
选择测试响应类型(成功/取消/不可用)
-
验证购买状态和回调处理
11.4 分享测试步骤
-
配置各社交平台的开发者账号
-
在ShareSDK平台注册应用
-
编译并运行应用
-
进入分享测试场景
-
点击不同分享按钮测试各平台
-
验证分享内容和跳转链接
-
检查分享状态回调是否正确
12. 部署场景
12.1 开发环境部署
-
本地Android Studio项目
-
Xcode iOS项目
-
Cocos2d-x源码调试模式
12.2 测试环境部署
-
内部测试版本(Alpha/Beta)
-
测试设备真机测试
-
自动化测试脚本集成
12.3 生产环境部署
-
Google Play商店发布
-
Apple App Store发布
-
企业内部分发
-
OTA热更新集成
13. 疑难解答
13.1 常见问题及解决方案
13.1.1 JNI调用崩溃
问题现象:应用崩溃,日志显示JNI相关错误
解决方案:
-
检查JNI方法签名是否正确
-
确保Java对象引用正确释放
-
验证线程安全性,UI操作必须在主线程
13.1.2 广告加载失败
问题现象:广告无法加载,返回错误码
解决方案:
-
检查网络连接和广告单元ID
-
验证AndroidManifest.xml权限配置
-
确认测试设备没有安装广告拦截应用
13.1.3 支付验证失败
问题现象:购买成功但服务器验证失败
解决方案:
-
检查公钥配置是否正确
-
验证时间戳和签名算法
-
确保服务器与客户端时间同步
13.1.4 分享无响应
问题现象:点击分享按钮无反应
解决方案:
-
检查ShareSDK初始化是否成功
-
验证社交平台应用包名配置
-
确认应用有网络访问权限
13.2 调试技巧
-
使用Android Studio的Logcat查看详细日志
-
利用Xcode的调试控制台监控iOS端行为
-
开启SDK的调试模式获取更多信息
-
使用抓包工具分析网络请求
14. 未来展望
14.1 技术发展趋势
-
统一SDK框架:更多厂商推出跨平台统一SDK
-
模块化集成:按需加载SDK模块,减小包体积
-
AI驱动优化:基于机器学习优化广告投放和用户体验
-
WebAssembly支持:Cocos2d-x对WASM的深度集成
14.2 新兴SDK类型
-
区块链支付:加密货币支付集成
-
AR/VR广告:沉浸式广告体验
-
实时语音:游戏内语音社交SDK
-
云游戏服务:串流游戏SDK集成
14.3 集成方式演进
-
低代码集成:可视化SDK配置工具
-
自动化测试:CI/CD流水线中的SDK测试
-
热更新支持:运行时动态更新SDK
-
隐私合规:GDPR/CCPA等法规的自动适配
15. 技术趋势与挑战
15.1 主要技术趋势
-
跨平台深化:Flutter、React Native等对原生SDK集成的挑战
-
隐私保护加强:iOS 14.5+的ATT框架对广告SDK的影响
-
5G赋能:超低延迟的广告和支付体验
-
超级应用生态:微信、支付宝等平台内SDK的崛起
15.2 面临挑战
-
碎片化加剧:Android设备型号和系统版本多样性
-
审核政策收紧:应用商店对SDK集成的严格审查
-
性能平衡:SDK功能增强与包体积控制的矛盾
-
安全风险:第三方SDK带来的安全隐患
15.3 应对策略
-
建立完善的SDK评估体系
-
采用插件化架构降低耦合度
-
加强安全审计和漏洞扫描
-
持续关注平台政策变化
16. 总结
本文全面介绍了Cocos2d-x集成第三方SDK的完整方案,涵盖了广告、支付、分享三大核心场景。通过JNI桥接技术,我们实现了C++游戏逻辑与平台原生SDK的无缝对接,提供了稳定可靠的商业化解决方案。
16.1 关键成果
-
建立了标准化的SDK集成框架
-
实现了跨平台统一的API设计
-
提供了完善的示例代码和最佳实践
-
解决了常见的集成难题和兼容性问题
16.2 实践价值
-
大幅降低了Cocos2d-x项目的SDK集成门槛
-
提高了开发效率和代码可维护性
-
增强了游戏的商业变现能力
-
为后续功能扩展奠定了坚实基础
16.3 后续建议
-
根据项目实际需求选择合适的SDK
-
建立完善的数据监控和分析体系
-
持续优化用户体验和商业收益的平衡
-
关注行业动态,及时升级SDK版本
通过本文提供的技术方案和实践经验,开发者可以快速在Cocos2d-x项目中集成各类第三方SDK,实现游戏的商业化和社交化目标,为用户提供更丰富、更优质的服务体验。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)