fixed
This commit is contained in:
@@ -19,27 +19,40 @@ namespace Darkmatter.Services.Ads
|
||||
public class AdMobAdService : MonoBehaviour, IAdService
|
||||
{
|
||||
[SerializeField] private AdUnitCatalogSO catalog;
|
||||
[Tooltip("Auto-reload after dismiss/failure for non-banner formats.")]
|
||||
[SerializeField] private bool autoReload = true;
|
||||
[Tooltip("Seconds between auto-reload retries on failure.")]
|
||||
[SerializeField, Min(1f)] private float reloadDelaySeconds = 5f;
|
||||
[Tooltip("Max reload attempts before giving up.")]
|
||||
[SerializeField, Min(1)] private int reloadMaxAttempts = 6;
|
||||
[Tooltip("Hard fallback (seconds) to recover a full-screen show if AdMob never raises its close callback. Android focus-return recovery usually fires far sooner; this cap covers iOS/edge cases. Must exceed max plausible ad length so a real ad is never cut short.")]
|
||||
[SerializeField, Min(15f)] private float showWatchdogSeconds = 60f;
|
||||
[Tooltip("Max interstitials shown per app session (run). Once reached, ShowAsync(Interstitial) no-ops until the app restarts. Counts only ads actually shown, not failed/skipped attempts. 0 disables interstitials.")]
|
||||
[SerializeField, Min(0)] private int maxInterstitialsPerSession = 8;
|
||||
|
||||
[Tooltip("Auto-reload after dismiss/failure for non-banner formats.")] [SerializeField]
|
||||
private bool autoReload = true;
|
||||
|
||||
[Tooltip("Seconds between auto-reload retries on failure.")] [SerializeField, Min(1f)]
|
||||
private float reloadDelaySeconds = 5f;
|
||||
|
||||
[Tooltip("Max reload attempts before giving up.")] [SerializeField, Min(1)]
|
||||
private int reloadMaxAttempts = 6;
|
||||
|
||||
[Tooltip(
|
||||
"Hard fallback (seconds) to recover a full-screen show if AdMob never raises its close callback. Android focus-return recovery usually fires far sooner; this cap covers iOS/edge cases. Must exceed max plausible ad length so a real ad is never cut short.")]
|
||||
[SerializeField, Min(15f)]
|
||||
private float showWatchdogSeconds = 60f;
|
||||
|
||||
[Tooltip(
|
||||
"Max interstitials shown per app session (run). Once reached, ShowAsync(Interstitial) no-ops until the app restarts. Counts only ads actually shown, not failed/skipped attempts. 0 disables interstitials.")]
|
||||
[SerializeField, Min(0)]
|
||||
private int maxInterstitialsPerSession = 8;
|
||||
|
||||
public bool IsInitialized => _initialized;
|
||||
public event Action<AdFormat, AdLoadState> LoadStateChanged;
|
||||
|
||||
private IAnalyticsService _analytics;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
// Per-session interstitial counter. The service is the app-lifetime singleton (it retains
|
||||
// loaded ads across scene swaps), so this survives Colorbook<->Gameplay transitions and only
|
||||
// resets on app restart — i.e. a true per-session cap.
|
||||
private int _interstitialsShownThisSession;
|
||||
|
||||
private bool _hasUserConsent = true;
|
||||
|
||||
// Coloring book is a child-directed app, so default to true. SetConsent can still
|
||||
// override if a consent flow later supplies a different value.
|
||||
private bool _isChildDirected = true;
|
||||
@@ -165,6 +178,7 @@ namespace Darkmatter.Services.Ads
|
||||
SetState(format, AdLoadState.Failed);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
#else
|
||||
await UniTask.CompletedTask;
|
||||
@@ -198,7 +212,7 @@ namespace Darkmatter.Services.Ads
|
||||
// Per-session interstitial cap. Check before load so a capped show wastes no fill. Skip
|
||||
// is silent (Failure, Shown=false); the fire-and-forget caller just keeps playing.
|
||||
if (format == AdFormat.Interstitial && _interstitialsShownThisSession >= maxInterstitialsPerSession)
|
||||
return AdShowResult.Failure("Session interstitial cap reached.");
|
||||
return AdShowResult.Failure("[AdMobAdService] Session interstitial cap reached.");
|
||||
|
||||
if (!IsReady(format))
|
||||
{
|
||||
@@ -206,6 +220,7 @@ namespace Darkmatter.Services.Ads
|
||||
if (!loaded) return AdShowResult.Failure($"{format} failed to load.");
|
||||
}
|
||||
|
||||
Debug.Log($"[AdMobAdService] Showing {format} ad.");
|
||||
switch (format)
|
||||
{
|
||||
case AdFormat.Interstitial:
|
||||
@@ -218,6 +233,7 @@ namespace Darkmatter.Services.Ads
|
||||
case AdFormat.RewardedInterstitial: return await ShowRewardedInterstitialAsync(cancellationToken);
|
||||
case AdFormat.AppOpen: return await ShowAppOpenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return AdShowResult.Failure($"{format} not supported by ShowAsync.");
|
||||
#else
|
||||
await UniTask.CompletedTask;
|
||||
@@ -225,7 +241,8 @@ namespace Darkmatter.Services.Ads
|
||||
#endif
|
||||
}
|
||||
|
||||
public async UniTask<bool> ShowBannerAsync(BannerSize size, BannerPosition position, CancellationToken cancellationToken)
|
||||
public async UniTask<bool> ShowBannerAsync(BannerSize size, BannerPosition position,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_initialized) return false;
|
||||
|
||||
@@ -309,7 +326,8 @@ namespace Darkmatter.Services.Ads
|
||||
MobileAds.SetRequestConfiguration(config);
|
||||
}
|
||||
|
||||
private async UniTask<bool> LoadInterstitialAsync(string unitId, AdRequest request, CancellationToken cancellationToken)
|
||||
private async UniTask<bool> LoadInterstitialAsync(string unitId, AdRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tcs = new UniTaskCompletionSource<bool>();
|
||||
InterstitialAd.Load(unitId, request, (ad, error) =>
|
||||
@@ -320,6 +338,7 @@ namespace Darkmatter.Services.Ads
|
||||
tcs.TrySetResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_interstitial?.Destroy();
|
||||
_interstitial = ad;
|
||||
WireFullScreenEvents(ad, AdFormat.Interstitial);
|
||||
@@ -334,7 +353,8 @@ namespace Darkmatter.Services.Ads
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask<bool> LoadRewardedAsync(string unitId, AdRequest request, CancellationToken cancellationToken)
|
||||
private async UniTask<bool> LoadRewardedAsync(string unitId, AdRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tcs = new UniTaskCompletionSource<bool>();
|
||||
RewardedAd.Load(unitId, request, (ad, error) =>
|
||||
@@ -345,6 +365,7 @@ namespace Darkmatter.Services.Ads
|
||||
tcs.TrySetResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_rewarded?.Destroy();
|
||||
_rewarded = ad;
|
||||
WireFullScreenEvents(ad, AdFormat.Rewarded);
|
||||
@@ -359,7 +380,8 @@ namespace Darkmatter.Services.Ads
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask<bool> LoadRewardedInterstitialAsync(string unitId, AdRequest request, CancellationToken cancellationToken)
|
||||
private async UniTask<bool> LoadRewardedInterstitialAsync(string unitId, AdRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tcs = new UniTaskCompletionSource<bool>();
|
||||
RewardedInterstitialAd.Load(unitId, request, (ad, error) =>
|
||||
@@ -370,6 +392,7 @@ namespace Darkmatter.Services.Ads
|
||||
tcs.TrySetResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_rewardedInterstitial?.Destroy();
|
||||
_rewardedInterstitial = ad;
|
||||
WireFullScreenEvents(ad, AdFormat.RewardedInterstitial);
|
||||
@@ -384,7 +407,8 @@ namespace Darkmatter.Services.Ads
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask<bool> LoadAppOpenAsync(string unitId, AdRequest request, CancellationToken cancellationToken)
|
||||
private async UniTask<bool> LoadAppOpenAsync(string unitId, AdRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tcs = new UniTaskCompletionSource<bool>();
|
||||
AppOpenAd.Load(unitId, request, (ad, error) =>
|
||||
@@ -395,6 +419,7 @@ namespace Darkmatter.Services.Ads
|
||||
tcs.TrySetResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_appOpen?.Destroy();
|
||||
_appOpen = ad;
|
||||
WireFullScreenEvents(ad, AdFormat.AppOpen);
|
||||
@@ -513,8 +538,16 @@ namespace Darkmatter.Services.Ads
|
||||
var tcs = new UniTaskCompletionSource<AdShowResult>();
|
||||
bool resolved = false;
|
||||
|
||||
Action onClosed = () => { resolved = true; tcs.TrySetResult(buildResult()); };
|
||||
Action<AdError> onFailed = err => { resolved = true; tcs.TrySetResult(AdShowResult.Failure(err?.GetMessage())); };
|
||||
Action onClosed = () =>
|
||||
{
|
||||
resolved = true;
|
||||
tcs.TrySetResult(buildResult());
|
||||
};
|
||||
Action<AdError> onFailed = err =>
|
||||
{
|
||||
resolved = true;
|
||||
tcs.TrySetResult(AdShowResult.Failure(err?.GetMessage()));
|
||||
};
|
||||
|
||||
subscribe(onClosed, onFailed);
|
||||
show();
|
||||
@@ -570,21 +603,26 @@ namespace Darkmatter.Services.Ads
|
||||
{
|
||||
// App returned to foreground after the ad held it => ad was dismissed but
|
||||
// the close callback was dropped. Brief grace for the real event, then force.
|
||||
await UniTask.Delay(ForegroundGraceMs, DelayType.Realtime, PlayerLoopTiming.Update, cancellationToken);
|
||||
await UniTask.Delay(ForegroundGraceMs, DelayType.Realtime, PlayerLoopTiming.Update,
|
||||
cancellationToken);
|
||||
if (!isResolved() && tcs.TrySetResult(buildResult()))
|
||||
Debug.LogWarning("[AdMobAdService] Close callback missed; recovered via foreground watchdog.");
|
||||
Debug.LogWarning(
|
||||
"[AdMobAdService] Close callback missed; recovered via foreground watchdog.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (elapsed >= showWatchdogSeconds)
|
||||
{
|
||||
if (!isResolved() && tcs.TrySetResult(buildResult()))
|
||||
Debug.LogWarning($"[AdMobAdService] Close callback missed; recovered via {showWatchdogSeconds:0}s watchdog cap.");
|
||||
Debug.LogWarning(
|
||||
$"[AdMobAdService] Close callback missed; recovered via {showWatchdogSeconds:0}s watchdog cap.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void WireFullScreenEvents(InterstitialAd ad, AdFormat format)
|
||||
@@ -658,9 +696,12 @@ namespace Darkmatter.Services.Ads
|
||||
if (IsReady(format)) return;
|
||||
if (await LoadAsync(format, cancellationToken)) return;
|
||||
}
|
||||
|
||||
Debug.LogWarning($"[AdMobAdService] {format} reload gave up after {reloadMaxAttempts} attempts.");
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static AdSize MapBannerSize(BannerSize size) => size switch
|
||||
@@ -671,7 +712,8 @@ namespace Darkmatter.Services.Ads
|
||||
BannerSize.FullBanner => AdSize.IABBanner,
|
||||
BannerSize.Leaderboard => AdSize.Leaderboard,
|
||||
BannerSize.SmartBanner => AdSize.SmartBanner,
|
||||
BannerSize.AnchoredAdaptive => AdSize.GetCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(AdSize.FullWidth),
|
||||
BannerSize.AnchoredAdaptive => AdSize.GetCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(
|
||||
AdSize.FullWidth),
|
||||
_ => AdSize.Banner
|
||||
};
|
||||
|
||||
@@ -694,4 +736,4 @@ namespace Darkmatter.Services.Ads
|
||||
LoadStateChanged?.Invoke(format, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user