RのWeb制作

Webサービス制作のための技術情報を。データ分析(Python、機械学習コンペ他)や自作野球ゲームMeisyoのこと中心。

モバイル制作 Unity(C#) Meisyo4(監督たちの甲子園)

Unity IAP(In App Purchase):消耗型編の覚え書き

投稿日:

この記事はUnity IAP完全攻略への道:消耗型編(+コンビニ決済)を掘り下げた記事です

この記事では、基本的な処理は書かれているものの初心者にはわかりづらい点があります。

問題1・IAPクラスをどこに置くべきかわからない

「多分、UIやアイテム反映処理と別だよなあ…」←その通りです

問題2・「PurchaseProcessingResult.Pending」した購入プロセスがデバイスを終了させると無効化する

「Androidアプリのテストで遅延購入すると、反映されずに消費だけ行われる」ぞい…困った
※実は、購入ID(product.definition.id)の永続化が必要

問題3・遅延購入を有効化したら、思わぬ挙動が出た

初期化タイミングが重要です。
pendingしていた購入を有効化すると、その後すぐ購入処理が走ります。
ゲームデータが読み込まれていないのに購入処理をすると・・・どうなる?

以下解説します。

問題1・IAPクラスをどこに置くべきかわからない

常時利用できる場所に置く必要があります。
例えば、ゲーム開始画面にGameObjectを置きIAPをアタッチします。スクリプトは以下のようにシングルトンにできるようにしておくといいでしょう。

public class IAP : MonoBehaviour, IStoreListener
{
    // -----------------------------------------//
    // 設定
    // -----------------------------------------//
    // インスタンス
    public static PurchaseManager Instance { get; private set; }

    // コントローラー
    IStoreController storeController;
    IGooglePlayStoreExtensions googlePlayStoreExtensions;

    // -----------------------------------------//
    // 初期化
    // -----------------------------------------//
    void Awake()
    {
        if(Instance == null)
        {
            Instance = this;
            Debug.Log("Purchase Manager Awake.");
        }
        else
        {
            Destroy(gameObject);
        }
    }

    // 以下その他処理
}

初期化は問題3で実装するため、とりあえずなしにしておきます。
※Start内で初期化すると問題が起こります…。

そして外部スクリプトから、呼び出せるような関数を作っておきます。

// IAP内
    // 購入
    public void PurchaseProduct(string dataId)
    {
        // ID設定
        string productId = dataId; // 必要に応じてIDを変更

        // チェック
        if(storeController != null && storeController.products.WithID(productId) != null)
        {
            storeController.InitiatePurchase(productId); // 購入処理を実行
            Debug.Log($"Purchase Add:{productId}");
        }
        else
        {
            Debug.LogError("Purchase failed: Product not found or not initialized.");
        }
    }

そして、アイテムショップから以下のコードで呼び出します。

IAP.Instance.PurchaseProduct(dataId);

IAPが初期化されていれば、上記のコードが実行可能となります。

上記のように、どこからでもアクセスできるようにしておきましょう!
問題2・3への対応も、この配置が良いようです。

問題2・「PurchaseProcessingResult.Pending」した購入プロセスがデバイスを終了させると無効化する

起動中はデバイスに購入待ちデータが保管されているんですが、永続化されていないんですよね。
そのため、起動中に購入待ち処理が完了すると問題なく購入が完了します。
(なお、読んでみても詳しく書いてないという…)

データの永続化には、以下のコードのように「購入したもののIDを、PendingまたはComplete時に保存(永続化)」が必要となります。
IDをリスト形式(List)で保存しておくのが一番簡単かもしれません。
保存するものはPlayFabでも、EasySaveでも何でも可能です。

    // 購入プロセス開始
    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    {
        // プロダクト呼び出し
        var product = args.purchasedProduct;
        Debug.Log($"購入レシート処理開始: {product.definition.id}");

        // コンビニ決済未完了
        if(googlePlayStoreExtensions.IsPurchasedProductDeferred(product))
        {
            Debug.Log("コンビニ決済未完了のためPendingとします");
            // ここでproduct.definition.idを保存
            return PurchaseProcessingResult.Pending;
        }

        // レシート送信開始
        StartCoroutine(SendReceipt(product));

        // レシート未送信時点ではPendingを返しトランザクションを張る
        Debug.Log("レシート未送信のためPendingとします");
        // ここでproduct.definition.idを保存
        return PurchaseProcessingResult.Pending;
    }

    // レシート送信
    private IEnumerator SendReceipt(Product product)
    {
        Debug.Log($"レシート送信 Product:'{product.definition.id}'");

        // サーバーに決済データを送信
        var addPurchase = xxxxx; // 非同期処理を実装し、完了を待つ(例:Firebaseでデータを送る等) // ToDo
        yield return WaitForTask(addPurchase);
        if(addPurchase.IsCompletedSuccessfully) // 成功
        {
            Debug.Log("レシート処理完了");
            // ここでproduct.definition.idを削除
            storeController.ConfirmPendingPurchase(product);
        }
        else // 失敗
        {
            Debug.Log($"エラー: {addPurchase.Exception?.Message}");
        }
    }

そして保存したIDを、デバイス再起動時に復活させる処理が初期化後に必要です。

    // 初期化
    public void InitializePurchasing()
    {
        // 初期化済み確認
        if(storeController != null) return;
        Debug.Log("Initialize Purchasing");

        // ビルダー立ち上げ
        var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
        builder.Configure<IGooglePlayConfiguration>().SetDeferredPurchaseListener(
            product => Debug.Log("コンビニ決済開始")
        );

        // 商品追加
        foreach(XXXXX)
        {
            builder.AddProduct(dataId, ProductType.Consumable);
        }

        // 初期化
        UnityPurchasing.Initialize(this, builder);

        // 保留の内容を確認
        CheckPurchasePendings();
    }

    // 保留中の内容を確認
    void CheckPurchasePendings()
    {
        Debug.Log("Check Purchase Pendings:");
        List<string> listId = xxx; // 読み込み

        // Pendingになっていたものリストを取得し、再確認
        if(listId.Count > 0)
        {
            foreach(string productId in listId)
            {
                PurchaseProduct(productId); // 定義したPurchaseProductを実行し、復活
                Debug.Log($"ProductId: {productId}");
            }
        }
        else // else
        {
            Debug.Log("No Pendings.");
        }
    }

上記のように設定すると、遅延購入が可能となります!

問題3・遅延購入を有効化したら、思わぬ挙動が出た

ゲームデータの読み込みを少し遅延する方法(EasySaveやFirebase)で行うと、IAP初期化してすぐに購入処理が走るため困ったことが起こる場合があります。
かんたんに言うと、ゲームデータが消えます。

【思ってた読み込みフロー】
ゲームデータ読み込み

IAP初期化

遅延購入実行

購入対応:ゲームデータ書き込み

【設定をミスったら起こる読み込みフロー】
IAP初期化

遅延購入実行

購入対応:ゲームデータ書き込み ← アイテムデータ等を読み取っていない段階で上書きをしてしまう

ゲームデータ読み込み ← 遅延する

あっ・・・これはデータが消えますねえ…
そのため、ゲーム立ち上げ時にはIAPは初期化せずゲームデータの読み込みを行ってください。
その後、以下のコードでIAP初期化を行ってください。

IAP.Instance.InitializePurchasing()

まとめ

以上のように、IAPには思わぬ落とし穴があります。
注意して開発を進めましょう!
それでは~( ^^)/

-モバイル制作, Unity(C#), Meisyo4(監督たちの甲子園)

執筆者:


comment

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

関連記事

ゲーム 監督たちの甲子園(仮) 制作開始

弱小高校の監督となり、「数多の強敵を倒す」「逆境を跳ね返す」指揮(戦略・戦術)を楽しむ3Dストラテジーゲームの開発を開始します。 これまで同様の背景を持った名将と呼ばれた者達(Meisyo)シリーズを …

【Flutter】ToDoアプリの作成

StatefullWidgetの状態管理、データの受け渡し、デバイスへのToDoリスト保存の解説がなかったので例となるアプリを作成しました。 こちらのアプリを応用すれば、大体のアプリが作れるのではない …

【Flutter】Googleアカウント認証SHA-1キーのためにkeytoolを使えるようにする

FlutterのGoogleアカウントでの認証(Authentication)のためにSHA-1キーが必要です。ただし、簡単に取得ができません。そのため、下記を参考にして進めます。 Google Au …

【Flutter】動画配信アプリの作成(2022年版・無料)

はじめに とあるきっかけから動画撮影・個別配信アプリを作りたいなということでいろいろなサイトで調べていました。 が・・・配信アプリ作成がうまく行かない。 例) ローカルネットワークでライブ配信する(2 …

【Flutter】(初心者向け)カメラ・動画アプリを理解しながら作る

今回の記事はモバイルのカメラ&動画撮影アプリをまず動かしてみて、何を実装しているのか解説・理解しながら、作成を進めるコンセプトで進めていきます。 なぜなら、この記事の著者はとあるきっかけからモバイル( …