リワード広告はパズルゲームのヒントなどの報酬を受け取ることと引き換えにユーザーが明示的に視聴することを選択できるタイプの広告です。

インタースティシャル広告との違い
インタースティシャル広告と似ていますが違いがあります。
| 比較 | インタースティシャル | リワード |
| 広告表示 | 表示のタイミングはアプリ側が決定 | ユーザーが希望したとき |
| 報酬 | なし | ヒント・答えなど |
| 繰り返し利用 | 不満につながりやすい | 納得して利用しやすい |
Googleも、インタースティシャル広告はゲームのステージ間など、自然な区切りでの表示を推奨しています。また、操作のたびに表示するような実装は認められないケースがあります。これに対してリワード広告は、ヒントなどの報酬を受け取ることと引き換えに、ユーザーが明示的に視聴することを選択できるので、こちらのほうが適しているといえます。
特にパズルゲームでは、広告を見ることで行き詰まりを解消できること自体が、広告を見る動機になるので広告も見られやすいです。
広告の在庫がない場合はどうなる?
広告の在庫がない場合はどうなるのでしょうか? リワード広告も必ず広告が表示されるわけではありません。
例えば、次のようなケースがあります。
広告を配信できる広告主がいない
ユーザーの地域や時間帯によって広告在庫が少ない
広告の読み込みに失敗した
通信環境が悪い
AdMob側で一時的な問題が発生している
このような原因で広告が表示されない場合があります。
GoogleのAdMobでは、広告の読み込み失敗をコールバックで検知できるので適切に切り分ける必要があります。
実装すべき処理
[ヒントを見る]ボタンが押下されるまえに広告をあらかじめ読み込んでおき、ボタンが押下されたら
| 広告の読み込み成功 | 広告を表示 |
| 広告の在庫なし | 「現在広告を表示できません」と表示 |
| 通信エラー | 「通信環境を確認してください」と表示 |
そして
| 広告を最後まで視聴 | ヒントを表示 |
| 広告を途中で閉じる | ヒントを表示しない |
という処理が必要になります。
※ 広告がない場合でもゲーム自体は普通に続けられるようにしておく
※ Googleの標準的なリワード広告では、読み込んだ広告の有効期限は1時間。期限切れの広告は表示に使用できないため、新しい広告を読み込む必要がある。
※ 今回のサンプルでは、広告の読み込みに失敗したことを検知するところまで実装している。エラーの詳細な分類については、必要に応じてイベント引数から取得する。
Plugin.AdMob 3.0.2 のインストールと AndroidManifest.xml の編集
NuGet で Plugin.AdMob をインストールします。.NET 9.0 の場合は最新版をインストールすることができないので Plugin.AdMob 3.0.2 をインストールします。
そのあと AndroidManifest.xml を編集します。これは以下と同じです。
アプリIDをManifestに設定し忘れると、Google Mobile Ads SDKの初期化時にアプリがクラッシュすることがあります。Plugin.AdMob をインストールしたら忘れずにしておきましょう。
RewardedAdManagerクラスの定義
RewardedAdManagerクラスを定義します。プロジェクトフォルダ内に RewardedAdManager.cs を作成し、以下のコードを書きます。
Load() で広告を読み込んで Show() で表示させ、IsLoaded で読み込まれているか確認するのですが、読み込み終わったとき、閉じられたとき、最後まで表示され終わったとき、エラーが発生したときがわかるようにイベントハンドラを追加しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
using Plugin.AdMob; using Plugin.AdMob.Services; public class RewardedAdManager { // リワード広告のテスト広告 private const string AdUnitId = "ca-app-pub-3940256099942544/5224354917"; private readonly IRewardedAdService _adService; private IRewardedAd? _rewardedAd = null; public bool IsReady => _rewardedAd?.IsLoaded == true; public event Action? OnAdLoaded; public event Action? OnRewardEarned; public event Action? OnAdClosed; public event Action<string>? OnAdError; public RewardedAdManager(IRewardedAdService adService) { _adService = adService; } // 広告を読み込む public void LoadAd() { if (_rewardedAd != null) return; _rewardedAd = _adService.CreateAd(AdUnitId); // 広告読み込み成功時 _rewardedAd.OnAdLoaded += (s, e) => { OnAdLoaded?.Invoke(); }; // 広告読み込み失敗時 _rewardedAd.OnAdFailedToLoad += async(s, e) => { OnAdError?.Invoke("広告を読み込めませんでした。"); _rewardedAd = null; await Task.Delay(1000 * 10); LoadAd(); }; // 広告読み込み失敗時 _rewardedAd.OnUserEarnedReward += (s, reward) => { OnRewardEarned?.Invoke(); }; _rewardedAd.OnAdDismissed += (s, e) => { _rewardedAd = null; OnAdClosed?.Invoke(); // 次の広告を読み込む LoadAd(); }; _rewardedAd.OnAdFailedToShow += (s, e) => { _rewardedAd = null; OnAdError?.Invoke("広告の表示に失敗しました。\n" + "エラーメッセージ:" + e.Message); // 次の広告を読み込む LoadAd(); }; _rewardedAd.Load(); } // 広告を表示する public void ShowAd() { if (_rewardedAd?.IsLoaded != true) { OnAdError?.Invoke("現在広告を表示できません。"); return; } _rewardedAd.Show(); } } |
MauiProgram.cs の編集
プロジェクトフォルダ内にある MauiProgram.cs を編集します。前回までとちがって編集する部分が増えます。依存性注入機能 (DI) への登録の処理が必要です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using Microsoft.Extensions.Logging; using Plugin.AdMob; // ← これを追加 namespace RewardedMauiApp1 { public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .UseAdMob() // ← これを追加 .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }); builder.Services.AddMauiBlazorWebView(); builder.Services.AddSingleton<RewardedAdManager>(); // ← これを追加(依存性注入機能 (DI) への登録) #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); builder.Logging.AddDebug(); #endif return builder.Build(); } } } |
Home.razor の編集
Home.razor を編集します。ボタンをふたつ表示し、片方は C# 側からもうひとつは JavaScript 側からリワード広告を見るという条件でヒントを得ることができるようにします。また得られたヒントも C# 側と JavaScript 側からみることができるようにします。
広告がないときにヒントの要求があったときはどうすればよいのでしょうか?
存在しない広告は表示できないし、いつ訪れるかわからない広告を読み込める時期までヒントを見ることができないというのは不合理なので、この場合は広告なしでヒントをみせることにします。
Components\Pages\Home.razor
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
@page "/" @using Plugin.AdMob @inject RewardedAdManager AdManager @inject IJSRuntime JS @implements IDisposable <style> .message { white-space: pre-line; } .btn { width: 200px; height: 50px; margin: 10px; display: block; } </style> <p>パズルゲーム</p> <button class = "btn" @onclick="ShowHintAd" disabled="@(!EnableHintButton)"> 広告を見てヒントを獲得 </button> <button id = "request-hint" class = "btn" disabled="@(!EnableHintButton)"> 広告を見てヒントを獲得 (JavaScript側) </button> <p class ="message">@Message</p> <p>@Hint</p> <p id = "hint"></p> <script src = "./app.js" type="module"></script> |
Home.razor.cs の作成と編集
Home.razor と同じフォルダに Home.razor.cs を作成して以下のコードを書きます。
このコードでは広告に関する処理に失敗したときに AdError が呼び出されます。広告が
AdError が呼び出される場合として考えられるのが
1. 広告の読み込みに失敗したとき
2. 広告の読み込みが完了していない状態で広告を表示させようとしたとき
です。
2. の場合はヒントを表示しますが、1. の場合はヒントを要求していないのに勝手にヒントが表示されるという問題がおきます。なので IsHintRequested フラグで両者を区別できるようにしています。
ヒントはサンプルプログラムなので「これがヒントです」と表示されるだけですが、複数回ヒントをみたときどのタイミングのヒントなのかわからないのでヒント生成時の時刻もあわせて表示させています。
広告が表示されたあとは次の広告が読み込まれるのですが、そのあいだの 2 ~ 3 秒間広告が存在しない状態になります。この状態でヒントを要求するボタンを何度も押されるとヒントのタダ食い(?)をされてしまいます。なので、ボタンを短い間隔で連打できないように、広告が表示されたら 5 秒間はヒントを要求するボタンを無効にしています。
また PC だけでデバッグしようとすると広告を読み込む処理でエラーが発生するので Android 以外では広告処理を行わないようにしています。
Components\Pages\Home.razor.cs
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
using Microsoft.JSInterop; namespace RewardedMauiApp1.Components.Pages; public partial class Home { private string Message = ""; private string Hint = ""; bool EnableHintButton = true; private bool IsHintRequested = false; private DotNetObjectReference<Home>? DotNetRef = null; protected override void OnInitialized() { DotNetRef = DotNetObjectReference.Create(this); if (DeviceInfo.Platform != DevicePlatform.Android) return; AdManager.OnAdLoaded += AdLoaded; AdManager.OnRewardEarned += RewardEarned; AdManager.OnAdError += AdError; AdManager.LoadAd(); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await JS.InvokeVoidAsync("setDotNetReference", DotNetRef); } } private void AdLoaded() { MainThread.BeginInvokeOnMainThread(async () => { await InvokeAsync(() => { Message = "広告読み込み成功!"; StateHasChanged(); }); }); } private void RewardEarned() { MainThread.BeginInvokeOnMainThread(async () => { await InvokeAsync(async () => { Message = "広告が表示されたのでヒントを表示します"; await ShowHint(); StateHasChanged(); }); }); } private void AdError(string error) { if (!IsHintRequested) return; IsHintRequested = false; MainThread.BeginInvokeOnMainThread(async () => { await InvokeAsync(async () => { Message = "エラーで広告が表示されませんでしたがヒントを表示します"; Message += "\nエラーメッセージ:" + error; await ShowHint(); StateHasChanged(); }); }); } [JSInvokable] public void ShowHintAd() { IsHintRequested = true; AdManager.ShowAd(); } private async Task ShowHint() { IsHintRequested = false; Hint = $"これが {DateTime.Now.ToString()} のヒントです。"; // 5 秒間はヒントボタンを無効にする EnableHintButton = false; StateHasChanged(); await JS.InvokeVoidAsync("onSentHint", "これがヒントです。(JavaScript側)"); await Task.Delay(5000); EnableHintButton = true; StateHasChanged(); } public void Dispose() { AdManager.OnAdLoaded -= AdLoaded; AdManager.OnRewardEarned -= RewardEarned; AdManager.OnAdError -= AdError; DotNetRef?.Dispose(); } } |
app.js の作成と編集
wwwrootフォルダ内に app.js を作成し、以下のコードを書きます。
wwwroot\app.js
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
let dotNetRef = null; // 準備 window.setDotNetReference = (ref) => { dotNetRef = ref; }; document.getElementById('request-hint').addEventListener('click', () => requestHint()); // ヒントを要求する処理 async function requestHint() { if (!dotNetRef) return; await dotNetRef.invokeMethodAsync("ShowHintAd"); } // ヒントが送信されたときの処理 window.onSentHint = (text) => { document.getElementById('hint').innerText = text; }; |
