- 前置き
- 環境
- 実装
- 最後に
前置き
先日、Unityで初めて消耗型の課金の実装を行ったのですが、かなりつまづく点が多かったので、実装までの一連の流れをここに記載しておきます。
環境
PC:Mac Book Pro
Unity :2021.3.21f1
実機:iPhone13 pro
iOS:17.1
実装
「Unity IAP」のインストール
Unityで「インスペクター」の隣にある「サービス」のタブを開くか、
「ウィンドウ」→「サービス」から「サービス」を開きます。
サービス内にある「アプリ内課金(In-App Purchasing)」を選択し、「ON」にします。
「ON」にすることで「Unity IAP」が利用できるようになります。
「IAP Catalog」の設定
IAP Catalogの設定ですが、こちらの設定を行わなくても実機への課金システムの実装は可能ですが、Catalogを登録しないとUnity Editor内での検証はできないので、設定しておくことをおすすめします。
「サービス」のメニューの中に「アプリ内課金」→「IAP Catalog…」というメニューがあるので、選択します。
下記のような設定画面がでます。
入力する値は下記です。
基本の設定値
【ID:】
課金アイテムのID名を記入してください。
恐らく決まった型はないですが、「com.プロジェクト名.アイテムID」という書き方が一般的に採用されているようです。
例えば、プロジェクト名が「リンゴゲーム」で、課金アイテム名が「100コイン」だった場合、
「com.apple_game.100coin」といった形になります。
【Type:】
課金タイプには、「Consumable(消耗型)」「Non Consumable(非消耗型)」「Subscription(定期型)」とありますが、この記事では消耗型の実装方法を解説してるため、「Consumable(消耗型)」を選んでください。
ちなみに、各タイプを簡単に説明すると、
「Consumable(消耗型)」…何度でも購入可能な商品。例えば、ゲーム内通貨や消費アイテムなど。
「Non Consumable(非消耗型)」…一度しか購入できない商品。例えば、広告の削除やステージの解放など。
「Subscription(定期型)」…一定期間経つと自動的に購入(更新)される商品。例えば、定期的にアイテムが配布されたり、プレミアムユーザーへのアップグレードなど。
Advanced(高度な設定)の設定値
Descriptions
【Locale:】
配信する地域を選びます。日本の場合は「Japanese (Google Play, Apple)」を選択します。
【Title:】
配信アイテムのタイトルを記入してください。100コインの場合は「100コイン」と入力してください。
【Description:】
配信アイテムの説明を記入します。100コインの場合は「ゲーム内のアイテム購入に利用できる100コインです。」などで良いでしょう。
Payouts
【Type:】
このTypeでは「Currency(通貨)」「Item(アイテム)」「Resource(リソース)」「Other(その他)」を選択できますが、100コインの場合は「Currency(通貨)」を選択します。
【Subtype:】
上記のTypeより更に細かく分類することができ、100コインの場合は「コイン」と入力します。
もし配信アイテムが100ジェムの場合は、「ジェム」と入力します。
【Quantity:】
配信アイテムの個数を入力します。100コインの場合は「100」と入力します。
Apple Configuration
【Price Tier:】
ここでは配信アイテムの価格を設定します。Googleでの配信の場合、自由に価格が設定できますが、Appleの場合、Tireという形で決められた金額しか設定することができません。
以下のPDFから希望するアイテム価格に近いTireを探し、この欄を設定してください。
【Screenshot path:】
スクリーンショットのパスを指定します。配信アイテムの実装画面をスクリーンショットを「Recorder」等で撮影し、そのパスを指定します。
以下の公式ドキュメントにも詳しくIAP Catalogの設定が載っています。
課金アイテムボタンの実装
「IAPManager.cs」ファイルを作成し、下記のコードを記載します。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
public class IAPManager : MonoBehaviour, IStoreListener
{
//課金ボタン
public void Button[] iapBtn = new Button[3];
private static IStoreController storeController; //アプリ内の商品リストを管理し、購入プロセスを開始する変数
private static IExtensionProvider storeExtensionProvider; //Unity IAPの拡張機能
//IAP Catalogで設定したIDを宣言する
private const string COIN_100 = "com.apple_game.100coin";
private const string COIN_500 = "com.apple_game.500coin";
private const stringCOIN_1000 = "com.apple_game.1000coin";
void Star()
{
/* ボタンに課金の機能を付与 */
for (int i = 0; i < iapBtn.Length; i++)
{
int index = i;
iapBtn[i].onClick.AddListener(() => { BuyProduct(index); });
}
};
//商品IDの配列(上記の課金アイテムIDを配列にまとめる)
public string[] arrayProductId = new string[]
{
COIN_100, COIN_500, COIN_1000
};
void Start()
{
//初期化を行う
InitializePurchasing();
}
/// <summary>
/// アイテムの購入ボタンが押された場合
/// </summary>
/// <param name="productIndex">商品の配列番号</param>
public void BuyProduct(int productIndex)
{
/* 課金データ読み込み用のLoading画面を表示しても良し */
if (IsInitialized() && productIndex >= 0 && productIndex < arrayProductId.Length)
{
// 指定された商品を購入するための処理
Product product = storeController.products.WithID(arrayProductId[productIndex]);
// 商品が見つかり、購入可能な場合は購入処理を開始
if (product != null && product.availableToPurchase)
{
storeController.InitiatePurchase(product);
}
// 商品が見つからないか、購入不可の場合はエラーメッセージを表示
else
{
Debug.Log("商品が見つからない、または購入不可のものです。");
}
}
// 無効な商品インデックスかUnity IAPが初期化されていない場合のエラーメッセージ
else
{
Debug.Log("無効な商品インデックスかIAPが初期化されていません。");
}
}
/// <summary>
/// IAPの初期化を行う
/// </summary>
public void InitializePurchasing()
{
if (IsInitialized()) { return; } //初期化済みの場合は何もせず終了
// IAPの設定を構築
ConfigurationBuilder builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
//各商品IDを設定して初期化を行う
foreach (string productId in arrayProductId)
{
Debug.Log("productId: " + productId);
// 商品IDを登録
builder.AddProduct(productId, ProductType.Consumable);
}
// Unity Purchasingを初期化
UnityPurchasing.Initialize(this, builder);
}
/// <summary>
/// 初期化されているか確認を行う
/// </summary>
/// <returns></returns>
private bool IsInitialized()
{
return storeController != null && storeExtensionProvider != null;
}
/// <summary>
/// IAPの初期化が完了したときのコールバック
/// ※ IStoreListenerのインターフェースで定義されたメソッド
/// </summary>
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
// IAPが初期化されたときの処理
storeController = controller;
storeExtensionProvider = extensions;
}
/// <summary>
/// IAPの初期化に失敗したときのコールバック
/// ※ IStoreListenerのインターフェースで定義されたメソッド
/// </summary>
public void OnInitializeFailed(InitializationFailureReason error)
{
// IAPの初期化に失敗したときの処理
Debug.Log("IAPの初期化に失敗しました: " + error);
}
/// <summary>
/// IAPの初期化に失敗したときのコールバック(オーバーロード)
/// ※ IStoreListenerのインターフェースで定義されたメソッド
/// </summary>
public void OnInitializeFailed(InitializationFailureReason error, string message)
{
// IAPの初期化に失敗したときの処理
Debug.Log("IAPの初期化に失敗しました: " + error + "\nmessage: " + message);
}
/// <summary>
/// 購入が成功した場合のコールバック
/// ※ IStoreListenerのインターフェースで定義されたメソッド
/// </summary>
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
{
// 購入の処理が成功したときに商品を付与したり、データベースに保存したりする処理を記述
/* 様々なデータを purchaseEvent で取得することができる*/
string receipt = purchaseEvent.purchasedProduct.receipt; //レシートの取得
string productName = purchaseEvent.purchasedProduct.metadata.localizedTitle; //製品名
string productId = purchaseEvent.purchasedProduct.definition.id; //製品ID
string transactionId = purchaseEvent.purchasedProduct.transactionID; //トランザクションID
decimal price = purchaseEvent.purchasedProduct.metadata.localizedPrice; //価格
Debug.Log("レシート:" + receipt);
Debug.Log("商品名:" + productName);
Debug.Log("商品ID:" + productId);
Debug.Log("トランザクションID:" + transactionId);
Debug.Log("価格:" + price);
Debug.Log("購入プラットフォーム:" + platform);
/* キャバコインの付与 */
int addCoin = 0; //購入コイン
switch (productId)
{
case COIN_100: //100COINの場合
addCoin = 100;
break;
case COIN_500: //500COINの場合
addCoin = 500;
break;
case COIN_1000: //1000COINの場合
addCoin = 1000;
break;
}
//購入コインを反映する(コード例 ※不正ができないようサーバーで管理することを推奨)
int haveCoin = PlayerPrefs.GetInt("HaveCoin", 0);
haveCoin += addCoin;
PlayerPrefs.SetInt("HaveCoin", haveCoin);
PlayerPrefs.Save();
return PurchaseProcessingResult.Complete; //購入が完了したことを返す。
}
/// <summary>
/// 購入が失敗した場合のコールバック
/// ※ IStoreListenerのインターフェースで定義されたメソッド
/// </summary>
/// <param name="product"></param>
/// <param name="failureReason"></param>
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
// 購入が失敗したときの処理を記述
Debug.Log("購入が失敗しました: " + product.definition.id + ", 理由: " + failureReason);
}
}
「Apple Developer」の登録
「App Store Connect」を利用する場合には、「Apple Developer」の登録が必要です。
無料Developerの場合、アプリの課金テストは行えませんのでご注意ください。
2023年12月時点のApple Developerの料金は年間99ドル(日本円で購入した際、12,980円でした)です。
上記のリンクから「アカウント」を選択し、Appleのアカウントでログイン後、「Apple Developer」の登録が行えます。
「App Store Connect」のログイン
「Apple Developer」登録後、以下のリンクから「App Store Connect」が利用できるようになります。
「App Store Connect」でアプリの登録
「App Store Connect」でアプリを登録する場合、2つの方法があります。
1つ目は「App Store Connect」の「マイアプリ」→「アプリ」画面の「+」ボタンから手動でアプリを登録する方法。
2つ目の方法はUnityでBuild後、Xcodeプロジェクトを開き、
プロジェクトの「Signing & Capabilities」の「Team」から「Apple Developer」に登録したアカウントを選択し、(選択肢にアカウントが表示されない場合は「Add an Account…」からアカウントログインしてください。)Xcodeの上部メニューにある「Product」→「Archive」を選択してください。
その後、「Distribute App」「TestFlight & App Store」を選択し「App Store Connect」にデプロイできます。
「App Store Connect」でアプリ内課金の登録
デプロイ後、「App Store Connect」で課金アイテムの登録を行います。
「App Store Connect」で対象のアプリを開き、「機能」の欄にある「アプリ内課金」を選択します。
その後、「作成」ボタンを押してアイテムの登録を行います。
「作成」ボタンを押すと、アイテムの登録画面が開きます。
種類…消耗型か非消耗型か選択します。
参照名…アイテム名を記載します。(例:100 COIN、500 COIN など)
製品ID…Unity IAPで設定したIDを記載します。(例:com.apple_game.100coin、com.apple_game.500coin など)
ここでの注意点として、製品IDとUnity の「IAP Catalog」で設定したIDが一致しないと、
課金アイテムが正常に処理されませんので、記載ミス等には気をつけてください。
「App Store Connect」で口座登録を行う【!!!重要!!!】
「App Store Connect」の「契約」を開き、「無料アプリ」「有料アプリ」それぞれステータスが「アクティブ」になっていないと、実機で課金テストを行なっても初期化エラーとなりテストが出来ないので必ずこちらを設定してください。
口座情報を登録すれば、ステータスが「アクティブ」に変わりますが、
必ず「口座情報」だけではなく「納税フォーム」の申請を行なってください。
(筆者は「納税フォーム」の申請をしておらず、課金テストで初期化エラーが起こり、つまづきました。)
「App Store Connect」でSandboxアカウントの登録
「ユーザーとアクセス」をクリックし、「Sandbox」を選択し、「+」ボタンからテストアカウントを追加してください。
「+」ボタンを押すと「新規テスター」画面が表示されます。
こちらに必要な情報を入力してください。
尚、メールアドレスに関しては実際に存在しないメールアドレスでも良いそうです。
iPhoneでSandboxアカウントにログインする
iPhoneで「設定」→「App Store」を開き、Sandboxの欄に「サインイン」が表示されるので、先ほど作成したアカウントでログインを行います。
もし、ここでiPhone側に「SANDBOXアカウント」の表示がされない場合、
一度、TestFlightでアプリをインストールしなければなりません。
SANDBOX環境に正常にログインできた場合、
これで、Unityでアプリをビルドして、Xcodeで実機にインストールすれば、課金テストを行えるようになります。
TestFlightでアプリをインストール(iPhone上でSANDBOXアカウントが表示されない場合)
※ここでの説明はUnityでアプリをビルドし、Xcodeで「Archive」→「Distribute App」で「TestFlight & App Store」を選択し「App Store Connect」にデプロイし、「App Store Connect」上でアプリが登録され、「アプリ内課金」の登録を行い、テストできる状態になっていると仮定します。
「App Store Connect」にアクセスし、対象のアプリを開きます。
「TestFlight」を選択し、「内部テスト」でグループを作成し、デプロイしたバージョンを使えるようにします。
ここでテスターを選択します。
テスター登録がまだの場合、「ユーザーとアクセス」からテストユーザーを追加します。
ここで登録するメールアドレスは実際にメールが届くメールアドレスを使用してください。
ユーザーを追加後、先ほど同様、「TestFlight」から「テスター」を「+」ボタンから追加してください。
テスターを追加し、しばらく経つとテスター宛にメールが届きます。
メールにはiPhoneで「TestFlight」アプリをインストールし、コードを入力すればダウンロードできるという内容のメールになっています。
手順に従って、「TestFlight」アプリをインストールし、コードを入力して、アプリをダウンロードしてください。
ダウンロード完了後、アプリを一度開き、「設定」→「App Store」を確認すると「SANDBOXアカウント」の表示が出てると思います。
これで、課金テストの環境構築は完了です。
上記のように左上にSandboxと表示されていれば、こちらでテストを行えます。
最後に
手順はかなり複雑ですが、課金実装が出来るようになればアプリの収益化が行えるようになり、
アイテム実装等でゲーム内容もかなり充実すると思います。
課金実装で手詰まっている方の助けになればと思います。