RのWeb制作

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

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

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

投稿日:

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

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

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

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

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

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

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

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

以下解説します。

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

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

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
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内で初期化すると問題が起こります…。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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.");
        }
    }

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

1
IAP.Instance.PurchaseProduct(dataId);

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

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

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

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

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

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
// 購入プロセス開始
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を、デバイス再起動時に復活させる処理が初期化後に必要です。

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
// 初期化
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初期化を行ってください。

1
IAP.Instance.InitializePurchasing()

まとめ

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

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

執筆者:


comment

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

関連記事

Unity スタンドアロンアプリ(PC版)でFirebaseが動かない不具合の対応

「原因がわからなくて詰んでる」みたいな状態になったバグがありましたので共有します。 参考:[Crash] Firebase crashes on some Windows machines #1284 …

C#(Unity) 2D障害物ゲームのマップをインスペクタから作成・設定する例

知りたい方がいらっしゃったので書いてみました。 インスペクタから設定できると、データベース不要で簡単に複数マップを作成・管理できるところが良いところだと思います。 設定ファイルの作成 MapSetti …

【Unity(C#)】メインカメラの挙動に合わせて、サブカメラもアスペクト比に合わせて伸縮させる

メインカメラの挙動は以下参考にしました。ありがとうございました! Unityの画面のアスペクト比と解像度を自動変換 全スマホ・複数解像度に対応させる 使い方 メインカメラとサブカメラを作成。 メインカ …

ゲームアプリ運営の分析ノウハウ vol.3 この状態のアプリはやべえ編

はじめに 皆さんお久しぶりです。れいです。 近しい友人(アプリ運営を長年経験)が転職することになり、色々話してみましたがやべえ状態ってあるんやなと思ったので共有します。 これは…他山の石としてください …

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

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

S