Bard は楽して暮らしたい

ゲームを作って思うこと、ゲームを遊んで思うこと

Unity

【Unity】課金関連の補助機能 Unity In-App Purchase の処理の流れまとめ

投稿日:

Unity が用意してくれている、スマートフォンアプリ内課金まわりに関する便利機能、『Unity In-App Purchase』。

最近また触れる機会があったので、少しまとめて記事にしてみようと思いました。

いわゆるゼロからの導入ガイド的なものではなく、スクリプトの中身に関する簡単な解説になります。

 

試行環境:Unity 5.5.6f1

Unity IAP バージョン:1.20.0

(試行環境が少し古い Unity5系 なので、最新版では一部内容が異なるかも知れません、ご注意ください)

 

導入については、元 Unity の中の人 伊藤 周 氏 による、こちらの資料も参考になりますので併せてどうぞ。

 

IStoreListener:課金処理の管理用インターフェース

OnInitialized:課金管理クラスの初期化処理

Unity – Scripting API: Purchasing.IStoreListener

Unity – Scripting API: Purchasing.IStoreListener.OnInitialized

インターフェース『IStoreListener』を継承したクラスが『課金管理クラス』のような形になります。

何は無くとも、この管理クラスを作成するところから始まります。

課金が行われた際に、このクラスの特定の関数たちが呼び出され、中に記述された処理が実行されていきます。

コールバック処理を外から渡せるようにしたい場合は、Singleton クラスにしておくと便利かと思います。

 

初期化関数 OnInitialized の中で、課金コンテンツの登録と UnityPurchasing クラスの初期化を実行しておきます。



// あらかじめ using UnityEngine.Purchasing を宣言しているものとします.

private IStoreController mStoreController;
private IExtensionProvider mExtensionProvider;

void Start()
{
        LogPurchasing("PurchasingManager Start");

        // Apple / Google ストア間で, 課金コンテンツの ID が同一である前提の書き方.
        // 同一でない場合はプラットフォームごとに分岐させる.
        var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance())
                .AddProduct("jp.bardaxel.hogehogegame.item_unlock_01", ProductType.NonConsumable) // 課金コンテンツ:アイテム解放01.
                .AddProduct("jp.bardaxel.hogehogegame.character_unlock_01", ProductType.NonConsumable) // 課金コンテンツ:キャラクター解放01.
                .AddProduct("jp.bardaxel.hogehogegame.story_unlock_01", ProductType.NonConsumable) // 課金コンテンツ:ストーリー解放01.
                .AddProduct("jp.bardaxel.hogehogegame.item_unlock_02", ProductType.NonConsumable); // 課金コンテンツ:アイテム解放02.
 
        // この後, OnInitialized が呼ばれる.
        UnityPurchasing.Initialize(this, builder);
}

public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
        mStoreController = controller;
        mExtensionProvider = extensions;

        LogPurchasing("PurchasingManager Initialized");
}

 

ConfigurationBuilder.Instance.AddProduct によって、『iTunesConnect』や『Google Play Console』で登録している課金コンテンツを追加していきます。

『OnInitialized』が走る際に渡される controllerextensions は、後にいろいろ使用するため、メンバ変数として保持しておくと便利です。

 

余談ですが、課金コンテンツの IDストア内で唯一性が要求されるため、『アプリのバンドル ID + 課金コンテンツを表す文字列』にしておくと無難です。

また、Apple では ID に大文字が使用できますが、Google Play の方はすべて小文字になってしまうため、iOS/Android 両ストア同じ ID にしたい場合は要注意です。

 

以下では、『IPurchaseListener』の中で、『OnInitializedの他に定義されている主な関数について取り上げていきます。

 

OnInitializeFailed:課金管理クラスの初期化失敗時に走る処理

Unity – Scripting API: Purchasing.IStoreListener.OnInitializeFailed

初期化失敗時に呼び出される関数です。

インターネットが利用できない環境の場合については、これが呼び出されるわけではなく、オンラインになるまで継続的に初期化を試みるようです。


public void OnInitializeFailed(InitializationFailureReason error)
{
    // 初期化失敗時に実行する処理.
    if (mFuncInitializeFailed != null)
    {
        mFuncInitializeFailed();
    }
    
    LogPurchasing("PurchasingManager OnInitializeFailed : " + error.ToString());
}

 

IStoreController.InitiatePurchase:各プラットフォームの購入画面を呼び出す処理

Unity – Scripting API: Purchasing.IStoreController.InitiatePurchase

こちらは『IPurchaseListener』ではなく、『OnInitialized』で渡された『IStoreController』が持つ関数です。

実際にプレイヤーが「購入します」とボタン押下などで意思決定をした際に、走らせるべき関数となります。

これが実行されると、iOS/Android それぞれで用意された課金用のダイアログが表示されます。

IPurchaseListener に、これを走らせるための public 関数を作成し、ゲーム内から呼び出せるようにしておくと良いかと思います。


public void OnPurchase(string product_id)
{
    LogPurchasing("PurchasingManager OnPurchase");
    mPurchasedProductId = product_id; 
    mStoreController.InitiatePurchase(mPurchasedProductId);
}

 

話を広げ過ぎないために、ここでは string で ID を直接渡していますが、実際は enum 定義を用意しておいて、Dictionary<enum, string> のような形で ID 文字列を参照できるようにしておくと便利かと思います。

 

ProcessPurchase:購入成功時に走る処理

(なぜか『ProcessPurchase』の公式ドキュメントが無かった…)

購入が成功したときに呼び出される関数です。

『PurchaseProcessingResult』という型で戻り値を返す必要があり、返す値は

  • PurchaseProcessingResult.Complete:課金処理の流れが完了済み
  • PurchaseProcessingResult.Pending:課金処理の流れが未完了

のどちらかになります。

Unity – Scripting API: Purchasing.PurchaseProcessingResult

 

購入処理の完了は、IStoreController.ConfirmPendingPurchase の呼び出しを持って判定します。

購入処理 – Unity マニュアル

OnInitialized において、StoreController を保持しておくと便利、と述べていたのはこの辺りにかかってきます。

 

課金情報をローカルに保存するだけであれば、即座に IStoreController.ConfirmPendingPurchase を呼び出しComplete を返して良いかと思います。

(少し話が逸れますが、課金情報の保存やレシート検証をローカルで実行する場合、当然ながらチートの危険性について意識する必要があります)


public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
{
    LogPurchasing("PurchasingManager ProcessPurchase");

    // レシート情報やトランザクション ID を保持する場合はこんな感じ.
    string receipt = e.purchasedProduct.receipt;
    string transaction_id = e.purchasedProduct.transactionID;

    // 購入後に実行する処理.
    // ここでコンテンツのアンロックや課金情報の保存を実行しておく.
    if (mFuncPurchaseComplete != null)
    {
        LogPurchasing("PurchaseComplete!!!");
        mFuncPurchaseComplete();
    }

    // この時点で, 一連の購入処理が完了したものとする.
    // 購入したコンテンツ ID は, enum を Key とした Dictionary などで参照しやすくしておくと便利.
    mStoreController.ConfirmPendingPurchase(mStoreController.products.WithID(mPurchasedProductId));

    return PurchaseProcessingResult.Complete;
}

 

(この例では、レシート検証を行わない形となっています。レシート検証についても、まとめられそうであれば別途記事にしようかと思います。)

レシート検証 – Unity マニュアル

 

一方、課金情報をサーバに送って、サーバ側でレシート検証などを行う場合、サーバの応答を待っている間は Pending を返すこととなります。

Pending が返され続けて、IStoreController.ConfirmPendingPurchase が実行されずにゲーム終了などした場合、ゲーム再起動時に未完了のままだった処理が走り直す、とされています。


public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
{
    LogPurchasing("PurchasingManager ProcessPurchase");

    // レシート情報やトランザクション ID を保持する場合はこんな感じ.
    string receipt = e.purchasedProduct.receipt;
    string transaction_id = e.purchasedProduct.transactionID;

    // ストアでの決済が完了したら, サーバへ購入情報を送信する.
    // NetworkRequest.PurchaseContent を, サーバへ購入情報を送る関数とする.
    // 引数は, 送信の完了が確認できた際に実行されるコールバック処理を渡しているものとする.
    NetworkRequest.PurchaseContent(() =>
    {
        // 購入後に実行する処理.
        if (mFuncPurchaseComplete != null)
        {
            LogPurchasing("PurchaseComplete!!!");
            mFuncPurchaseComplete();
        }
        // 送信完了したら, 一連の購入処理が完了したものとする.
    // 購入したコンテンツ ID は, enum を Key とした Dictionary などで参照しやすくしておくと便利.
        mStoreController.ConfirmPendingPurchase(mStoreController.products.WithID(mPurchasedProductId));

        return PurchaseProcessingResult.Complete;

    });

    LogPurchasing("PurchasePending...");

    // 送信が完了するまでは, Pending を返す.
    return PurchaseProcessingResult.Pending;
}

 

OnPurchaseFailed:購入失敗時に走る処理

購入が失敗したときに呼び出される関数です。


public void OnPurchaseFailed(Product i, PurchaseFailureReason p)
{
    // 課金失敗時に実行する処理.
    if (mFuncPurchaseFailed != null)
    {
        mFuncPurchaseFailed();
    }

    LogPurchasing("PurchasingManager OnPurchaseFailed : " + p.ToString());
}

 

 

課金管理クラス』での、実際の処理の流れはこのような感じになります。

どこにどのような処理が書かれるべきなのか、コードを見て1つ1つ取り上げていくことで、少し理解しやすくなるのではないでしょうか。

 

レシート検証などの後編へ続く…かも…。

 

© Unity Technologies Japan/UCL

-Unity

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

関連記事

【Unity】Unity 2017 で TextMesh Pro を使って作成したプロジェクトを Unity 2018 へ移行する際のエラー対策リンクまとめ

Unity 2017 で作成したプロジェクトを Unity 2018 へ移行すると、TextMesh Pro まわりでいろいろとエラーが出たので、そのときに参考にさせていただいた記事のリンク集です。 …

【Unity】WebGL 出力から RPG アツマール へアップロードする際の解像度指定

Unity で作成したゲームを、ブラウザゲームとして出力しアップロードする場合、『RPGアツマール』は有力なプラットフォームの選択肢の一つとなります。 RPGアツマール: トップページ 自分が現在制作 …

BitSummit Vol.6

【ゲーム雑記】BitSummit Vol.6 を経て、期待のインディーゲーム一覧 前編

少し間が空いてしまいましたが、先月、京都で開催された BitSummit Vol.6 へ行ってきました。 その中で、プレイしたタイトルの感想や、未プレイながら目を引いたタイトルの紹介をしていきたいと思 …

no image

【Unity】講演紹介:いかにして個人制作ゲームで生きていくか 〜スマホゲームレッドオーシャンの泳ぎ方〜 by いたのくまんぼう さん

Unite 2018 の開催も迫ってきているので、改めて昨年 Unite 2017 の講演の中で、おすすめのものでも紹介していこうかなと思います。 Unite 2018 公式サイトはこちら 目次講演動 …

UnityChanLicenseLogo

【Unity】Unite Tokyo 2018 見てきた講演の簡単なまとめ 3日目

間が空いてしまいましたが、Unite Tokyo 2018 3日目 のまとめです。 前回(2日目のまとめ)はこちら 目次さては非同期だなオメー!async/await完全に理解しようスクリプトによるT …