Cocos Creator与iOS原生深度整合:AdMob广告回调与激励视频奖励发放实战指南
在移动游戏开发中,广告变现与用户体验的平衡一直是个技术难点。本文将深入探讨如何通过Cocos Creator的JavaScript/TypeScript与iOS原生代码的深度整合,实现AdMob广告系统的完整闭环,特别是激励视频观看后的游戏内奖励发放机制。
1. 环境准备与基础配置
1.1 Cocos Creator项目设置
首先确保你的Cocos Creator项目已正确配置iOS平台支持:
# 检查Cocos Creator版本 cocos -v # 构建iOS项目 cocos compile -p ios在构建前,需在project.json中确认以下配置:
{ "engine": "cocos2d-x", "platform": "ios", "jsbFolder": "src" }1.2 iOS工程配置
Xcode工程需要添加AdMob SDK和相关依赖:
- 手动下载AdMob SDK(推荐版本8.9.0+)
- 将以下框架添加到工程:
GoogleMobileAds.frameworkUserMessagingPlatform.framework(用于GDPR合规)
在Build Settings中确认:
Other Linker Flags包含-ObjCEnable Modules (C and Objective-C)设为YES
2. 原生代码架构设计
2.1 广告管理器实现
创建AdmobManager单例类,统一管理各类广告:
// AdmobManager.h #import <Foundation/Foundation.h> #import "RootViewController.h" typedef NS_ENUM(NSInteger, AdLoadStatus) { AdLoadStatusNotLoaded, AdLoadStatusLoading, AdLoadStatusReady }; @interface AdmobManager : NSObject + (instancetype)sharedInstance; - (void)initializeWithViewController:(RootViewController *)viewController; // 广告控制方法 + (void)showRewardedAd; + (void)showInterstitialAd; + (void)showBannerAd; @end对应的.m文件实现核心广告加载逻辑:
// AdmobManager.m #import <GoogleMobileAds/GoogleMobileAds.h> @interface AdmobManager() <GADFullScreenContentDelegate> @property (nonatomic, strong) GADRewardedAd *rewardedAd; @property (nonatomic, strong) GADInterstitialAd *interstitialAd; @property (nonatomic, strong) GADBannerView *bannerView; @property (nonatomic, weak) RootViewController *rootViewController; @property (nonatomic, assign) AdLoadStatus rewardedAdStatus; @end @implementation AdmobManager + (instancetype)sharedInstance { static AdmobManager *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[AdmobManager alloc] init]; }); return instance; } - (void)initializeWithViewController:(RootViewController *)viewController { self.rootViewController = viewController; [[GADMobileAds sharedInstance] startWithCompletionHandler:nil]; [self loadRewardedAd]; [self loadInterstitial]; [self setupBannerAd]; } - (void)loadRewardedAd { self.rewardedAdStatus = AdLoadStatusLoading; GADRequest *request = [GADRequest request]; [GADRewardedAd loadWithAdUnitID:@"YOUR_AD_UNIT_ID" request:request completionHandler:^(GADRewardedAd *ad, NSError *error) { if (error) { NSLog(@"Rewarded ad failed to load: %@", error); self.rewardedAdStatus = AdLoadStatusNotLoaded; return; } self.rewardedAd = ad; self.rewardedAd.fullScreenContentDelegate = self; self.rewardedAdStatus = AdLoadStatusReady; }]; } // 其他广告类型实现类似... @end3. Cocos到原生的通信桥梁
3.1 JavaScript调用原生方法
在Cocos TypeScript中,通过jsb.reflection.callStaticMethod调用原生代码:
// AdmobBridge.ts const {ccclass, property} = cc._decorator; @ccclass export default class AdmobBridge extends cc.Component { private static _instance: AdmobBridge = null; public static get instance(): AdmobBridge { return AdmobBridge._instance; } onLoad() { AdmobBridge._instance = this; cc.game.addPersistRootNode(this.node); } showRewardedAd(onReward: Function, context: any): void { if (cc.sys.os === cc.sys.OS_IOS) { jsb.reflection.callStaticMethod( "AdmobManager", "showRewardedAd" ); this._rewardCallback = onReward.bind(context); } } private _rewardCallback: Function = null; // 提供给原生调用的奖励发放方法 public onUserRewarded(): void { if (this._rewardCallback) { this._rewardCallback(); this._rewardCallback = null; } } }3.2 原生回调JavaScript
创建ObjectToJs.mm文件处理原生到JavaScript的通信:
// ObjectToJs.mm #import "ObjectToJs.h" #import "cocos2d.h" #import "cocos/scripting/js-bindings/jswrapper/SeApi.h" @implementation ObjectToJs + (instancetype)sharedInstance { static ObjectToJs *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[ObjectToJs alloc] init]; }); return instance; } - (void)dispatchRewardToJS { std::string jsCallStr = "cc.find('AdmobBridge').getComponent('AdmobBridge').onUserRewarded();"; se::ScriptEngine::getInstance()->evalString(jsCallStr.c_str()); } @end在广告回调中触发奖励发放:
// 在AdmobManager.m中 - (void)adDidDismissFullScreenContent:(id<GADFullScreenPresentingAd>)ad { if ([ad isKindOfClass:[GADRewardedAd class]] && self.userEarnedReward) { [[ObjectToJs sharedInstance] dispatchRewardToJS]; } [self loadRewardedAd]; // 重新加载广告 }4. 高级功能与优化
4.1 广告状态同步
实现Cocos与原生之间的广告状态同步:
// 在AdmobBridge.ts中添加 interface AdStatus { isRewardedAdReady: boolean; isInterstitialReady: boolean; } getAdStatus(): Promise<AdStatus> { return new Promise((resolve) => { if (cc.sys.os === cc.sys.OS_IOS) { const status = jsb.reflection.callStaticMethod( "AdmobManager", "getAdStatus" ); resolve(status); } else { resolve({ isRewardedAdReady: false, isInterstitialReady: false }); } }); }对应的Objective-C实现:
// AdmobManager.m + (NSDictionary *)getAdStatus { return @{ @"isRewardedAdReady": @([AdmobManager sharedInstance].rewardedAdStatus == AdLoadStatusReady), @"isInterstitialReady": @([AdmobManager sharedInstance].interstitialAdStatus == AdLoadStatusReady) }; }4.2 内存管理注意事项
跨语言通信中的内存管理要点:
- Objective-C到JavaScript的字符串转换:
std::string jsString = [@"Objective-C String" UTF8String];- 避免循环引用:
__weak typeof(self) weakSelf = self; [self.rewardedAd presentFromRootViewController:self.viewController userDidEarnRewardHandler:^{ typeof(self) strongSelf = weakSelf; if (!strongSelf) return; // 处理奖励 }];- 及时释放资源:
// TypeScript中 onDestroy() { this._rewardCallback = null; AdmobBridge._instance = null; }5. 调试与问题排查
5.1 常见问题解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 广告加载失败 | 网络问题/AdUnitID错误 | 检查网络连接,确认AdUnitID正确 |
| JavaScript调用无响应 | 方法名不匹配 | 检查类名和方法名大小写 |
| 奖励未发放 | 回调函数未正确设置 | 确保rewardCallback在显示广告前设置 |
| 内存泄漏 | 循环引用 | 使用weak/strong dance模式 |
5.2 调试技巧
- 在Xcode中启用调试日志:
NSLog(@"%@", @"详细调试信息");- 在Cocos Creator中监听JavaScript异常:
window.onerror = function(message, source, lineno, colno, error) { console.error("Global error:", message, error); };- 使用Safari开发者工具调试iOS WebView:
- 在iOS设置中启用Web检查器
- 通过Safari的"开发"菜单连接设备
6. 性能优化建议
- 广告预加载策略:
// 游戏启动时预加载广告 - (void)preloadAds { [self loadRewardedAd]; [self loadInterstitial]; } // 广告使用后立即重新加载 - (void)adDidDismissFullScreenContent:(id<GADFullScreenPresentingAd>)ad { if ([ad isKindOfClass:[GADRewardedAd class]]) { [self loadRewardedAd]; } else if ([ad isKindOfClass:[GADInterstitialAd class]]) { [self loadInterstitial]; } }- 减少跨语言调用频率:
// 批量获取广告状态而不是多次单独查询 const adStatus = await AdmobBridge.instance.getAdStatus(); if (adStatus.isRewardedAdReady) { // 显示激励视频按钮 }- 线程安全注意事项:
dispatch_async(dispatch_get_main_queue(), ^{ // 所有UI操作和广告展示必须在主线程 [self.rewardedAd presentFromRootViewController:self.rootViewController userDidEarnRewardHandler:^{ // 奖励处理 }]; });在实际项目中,我们发现激励视频的最佳展示时机是在游戏自然停顿点(如关卡结束、角色死亡复活等)。通过合理的预加载策略,可以将广告准备时间对用户体验的影响降到最低。