【UnityADS实战】从零到一:构建可复用的广告管理模块

张开发
2026/4/17 12:18:07 15 分钟阅读

分享文章

【UnityADS实战】从零到一:构建可复用的广告管理模块
1. 为什么需要广告管理模块在中小型游戏项目中广告变现往往是收入的重要来源。但很多开发者初期会直接把广告代码分散写在各个场景脚本里——点击按钮时调用激励广告关卡结束时触发插屏广告主界面常驻横幅广告。这种写法短期内看似方便但随着项目迭代会暴露三个致命问题第一是代码重复。每个需要广告的场景都要复制粘贴初始化逻辑当UnityADS的API更新时你得逐个文件修改。我去年接手过一个项目光是激励广告的调用代码就散落在17个脚本里升级SDK时差点崩溃。第二是状态混乱。不同广告类型之间缺乏协调比如同时弹出激励广告和插屏广告会导致界面重叠横幅广告隐藏后其他模块不知道仍然尝试点击。实际项目中我遇到过玩家看完激励视频却被突然弹出的插屏打断奖励发放的恶性Bug。第三是维护困难。没有统一的错误处理和日志记录当广告填充率下降时你根本不知道是SDK配置问题、网络问题还是广告位设置错误。曾经有次线上事故因为某个回调函数漏写了重试逻辑导致整个游戏的广告收入归零。模块化设计能完美解决这些问题。把广告逻辑抽象成独立服务就像游戏中的音频管理器——你不需要知道背景音乐怎么播放只需调用AudioManager.PlayBGM()。广告模块也该如此其他业务代码只需说给我展示个激励视频具体实现细节由模块内部处理。2. 模块架构设计2.1 核心类结构先看这个经过实战检验的类设计public class AdManager : MonoBehaviour, IUnityAdsInitializationListener, IUnityAdsLoadListener, IUnityAdsShowListener { // 单例模式保证全局访问 public static AdManager Instance { get; private set; } // 广告配置数据 [Serializable] public class AdConfig { public string androidGameID; public string iosGameID; public bool testMode; public string interstitialUnitID; public string rewardedUnitID; public string bannerUnitID; } // 当前广告状态 public enum AdStatus { NOT_LOADED, LOADING, READY, SHOWING } private DictionaryAdType, AdStatus _adStatus; private ActionRewardResult _rewardCallback; }关键设计点单例模式通过AdManager.Instance全局访问避免频繁FindObject配置分离AdConfig结构体存储所有ID和开关方便热更新状态机用AdStatus枚举跟踪每个广告类型的状态防止冲突调用类型安全定义AdType枚举代替字符串参数避免拼写错误2.2 生命周期管理广告模块需要正确处理Unity场景加载和对象销毁void Awake() { if (Instance ! null) { Destroy(gameObject); return; } Instance this; DontDestroyOnLoad(gameObject); InitializeAds(); } void OnDestroy() { if (Instance this) { // 释放广告资源 Advertisement.Banner.Hide(); Instance null; } }这里有个坑要注意Android平台上的横幅广告在场景切换时可能不会自动隐藏。我们必须在OnDestroy中主动调用Hide()否则下次进入场景会出现双重视图。3. 广告类型实现细节3.1 激励视频的坑与解决方案激励广告看似简单但实际开发中会遇到三个典型问题问题1奖励发放时机新手常犯的错误是在Show调用后立即发奖励。正确做法应该是在OnUnityAdsShowComplete回调中处理public void ShowRewarded(ActionRewardResult callback) { if (_adStatus[AdType.REWARDED] ! AdStatus.READY) { callback?.Invoke(RewardResult.NOT_READY); return; } _rewardCallback callback; Advertisement.Show(_rewardUnitID, this); } public void OnUnityAdsShowComplete(string unitId, UnityAdsShowCompletionState state) { if (unitId _rewardUnitID) { var result state UnityAdsShowCompletionState.COMPLETED ? RewardResult.SUCCESS : RewardResult.FAILED; _rewardCallback?.Invoke(result); } }问题2按钮状态管理必须禁用按钮交互直到广告加载完成否则玩家可能在广告未准备好时点击public void OnUnityAdsAdLoaded(string unitId) { if (unitId _rewardUnitID) { _adStatus[AdType.REWARDED] AdStatus.READY; // 这里通知UI更新按钮状态 EventSystem.Notify(AdEvent.REWARDED_LOADED); } }问题3异常恢复当广告加载失败时应该自动重试而不是直接报错public void OnUnityAdsFailedToLoad(string unitId, UnityAdsLoadError error, string message) { if (unitId _rewardUnitID) { StartCoroutine(RetryLoading(AdType.REWARDED, 3)); } } IEnumerator RetryLoading(AdType type, int retryCount) { while (retryCount-- 0) { yield return new WaitForSeconds(5); LoadAd(type); } }3.2 插屏广告的最佳实践插屏广告最容易引发玩家反感需要特别注意两点展示频率控制private float _lastInterstitialTime; public bool CanShowInterstitial() { return Time.time - _lastInterstitialTime 120f _adStatus[AdType.INTERSTITIAL] AdStatus.READY; }场景白名单private HashSetstring _allowedScenes new (){MainMenu, LevelComplete}; void Update() { if (_allowedScenes.Contains(SceneManager.GetActiveScene().name)) { // 展示逻辑 } }4. 高级功能扩展4.1 A/B测试框架通过配置不同的广告单元ID实现分流测试[Serializable] public class ABTestConfig { public string groupA_ID; public string groupB_ID; public float ratio; // A:B的比例 } public string GetEffectiveUnitID(ABTestConfig config) { return Random.value config.ratio ? config.groupA_ID : config.groupB_ID; }4.2 性能监控系统记录关键指标帮助优化收益public class AdPerformance { public int impressionCount; public float loadTime; public float fillRate; public void LogImpression(AdType type) { // 上传到数据分析平台 Analytics.Log(Ad_Shown, type.ToString()); } }4.3 多平台适配方案处理iOS和Android的差异string GetPlatformUnitID(string androidID, string iosID) { #if UNITY_IOS return iosID; #elif UNITY_ANDROID return androidID; #else return androidID; // 编辑器默认用Android配置 #endif }5. 实际项目中的应用在我的休闲游戏《宝石消除》中这个模块的完整调用流程是这样的游戏启动void Start() { AdManager.Instance.Initialize(); AdManager.Instance.LoadBanner(BannerPosition.BOTTOM_CENTER); }关卡结算void OnLevelComplete() { if (AdManager.Instance.CanShowInterstitial()) { AdManager.Instance.ShowInterstitial(); } rewardButton.interactable AdManager.Instance.IsRewardedReady; } void OnRewardButtonClick() { AdManager.Instance.ShowRewarded(result { if (result RewardResult.SUCCESS) { AddCoins(500); } }); }异常处理void OnAdError(string message) { Toast.Show(广告加载失败请检查网络); Debug.LogError(message); }这套架构经过三个项目的验证广告收入平均提升40%崩溃率下降85%。最关键的是当UnityADS API从v3升级到v4时我只需要修改AdManager这一个文件就完成了迁移。

更多文章