【制作】アプリ内課金の実装、その3

いろいろまとめます。ちょっと長い記事になるかも。

今日はほぼ一日バグと戦ってました。

先日の記事で報告したバグなんだけれども、実機一台のほうはUnityで簡単なサンプル作っても、もうUnityエンジン自体が起動しなくなってた。

SDKをアップデートする前の環境(ノートPC)でも起きた。

その間にしたことといえば、Unityのバージョンアップくらいしか思い当たらないので、ビルド関連で何か重要な変更があったのかもしれない。

もう一台の実機は動くけれども、途中でアプリが落ちるというバグ。原因は俺でした。

DLCで落とした画像をUnity上でバイナリから画像ファイルに変換する際、もとの画像サイズ(約100K前後)とは関係なく、メモリ上にはUnity独自の形式で展開されるのだった。

ショック。がんばって超軽量のアトラスに圧縮したのに。まぁ、ダウンロード要領が小さくなるので必要な手間ではあるが。

その画像がシナリオ用の立ち絵のアトラスなんだけれども、一枚約8Mでメモリ上に展開されていた。(透過画像でRGBA4444、いわゆるRGBA16bitというやつ)

加えて、いったん読込んだ画像を再利用するコードにバグがあって、同じテクスチャを何枚もメモリ上に展開していたためリソースを圧迫していた模様。

同じ画像は読込まないようにリソース管理のバグを修正して、画像アトラスも半分のサイズ(4分の一)にすることで正常動作に戻った。

バグの検出で役立ったのが、DDMS(すでに非推奨らしいけど)と自分で入れたデバッグログ。

特に自作スクリプトの実行時に利用メモリをログっていたので見つけることが出来た。

ちなみにメモリは以下のコードで参照できる。

Debug.Log("Memory: " + Profiler.usedHeapSize / 1048576 + " / " + SystemInfo.systemMemorySize + " MB");

あと、PCでは起きないなかった現象でアンドロイド実機で発生するという厄介な問題を対処。

備忘録に概要だけ述べると、スクリプトからダウンロードした画像をマテリアルにテクスチャとしてセットした際、実機では画像が真っ黒に潰れるというもの。

原因はわからなかったけれども、スプライトに置き換えることで解決したのでよしとする。

とまぁ、こんな感じでこの段階で出るバグはリリースに響くのでほんとに注意が必要。

教訓として、これからは一日の終わりに必ず実機での動作確認を日課とすることにした。

さて、長い前置きだったけれども、ここからが本題のアプリ内課金についてのまとめ。

といっても、サンプルの実行手順と、サンプル解析は、前々回の記事で紹介した下記のサイトで詳しくやっていたのでそちらを熟読させていただいた、わはは。

UnityとSoomlaで手軽にアプリ内課金: Android編

ただ、Soomlaはこの記事以降にバージョンアップでインターフェースを変更しているところがあって、それについて補足と、結局Soomlaで何をすればいいのかを簡潔にまとめてみたい。

まず、記事のバージョンでは、非消費型アイテムを表現するクラスNonConsumableItemというものがあったんだけれども、これが廃止(remove)されLifetimeVGというクラスに統合された。

したがって、これを使用していたインターフェースは軒並み変更が必要となる。

といっても、旧版からの修正箇所はあまり多くない。

たとえば、上記記事の非消費型アプリのサンプル、バナナ購入アプリを例にすると変更点は下記となる。

【BananaStoreAssets.cs】

//下記メソッドは廃止
//public NonConsumableItem[] GetNonConsumableItems() {
//	return new NonConsumableItem[]{BANANA_NONCONS};
//}

//変更前(NonConsumableItemをLifetimeVGに変更)
//public static NonConsumableItem BANANA_NONCONS  = new NonConsumableItem(
//	"Banana"
//	, "Banana not eatable"
//	, BANANA_ITEM_ID
//	, new PurchaseWithMarket(new MarketItem(BANANA_ITEM_ID, MarketItem.Consumable.NONCONSUMABLE , 0.99))
//);
//変更後(NonConsumableItemをLifetimeVGに変更)
public static LifetimeVG BANANA_NONCONS  = new LifetimeVG(
	"Banana"
	, "Banana not eatable"
	, BANANA_ITEM_ID
	, new PurchaseWithMarket(new MarketItem(BANANA_ITEM_ID, MarketItem.Consumable.NONCONSUMABLE , 0.99))
);

//変更前
//public VirtualGood[] GetGoods() {
//	return new VirtualGood[] {};
//}
//変更後(BANANA_NONCONSを追加)
public VirtualGood[] GetGoods() {
	// LifetimeVG
	return new VirtualGood[] {BANANA_NONCONS};
}

【BananaStoreScript.cs】

// 変更前
//OnMarketPurchase(PurchasableVirtualItem pvi, string purchaseToken, string payload)
// 変更後
OnMarketPurchase(PurchasableVirtualItem pvi, string purchaseToken, Dictionary<string, string> payload) {
…以下略

// 変更前
//if (StoreInventory.NonConsumableItemExists(BananaStoreAssets.BANANA_ITEM_ID)) {
// 変更後
if (StoreInventory.GetItemBalance(BananaStoreAssets.BANANA_ITEM_ID) >= 1) {
…以下略

とまあ、こんな感じで修正して、あとは記事の手順に従ってグーグルにベータ版としてアップ。

Unityからの実行で、無事非消費型アイテムの課金に成功しました。

この時注意しないといけないのが、APKのアップロードが反映されるまで、および、登録したアイテムが有効になるまでに、数時間かかるかもしれない。ということ。

はじめ、何度やっても「グーグルの認証が必要です」みたいなエラーが出たり、「このアプリでは購入ができない」的なエラーが出たりしたけれども、数時間後にふつうに出来るようになってたなんて事があった。

で、結局Soomlaで何をするのか、概要をまとめてみる。

【概要】
・SoomlaはちょうどiTweenのようなスクリプトアセットで、アセットストアから無料でダウンロード可能。

・サンプルも入っているが、必要なのはSoomlaフォルダとPluginsフォルダだけ。

・Soomla/Prefabs内にCoreEvents.prefabとStoreEvents.prefabがあって、これがSoomlaの肝。
 スマホに入っているGooglePlayとのメッセージ交換は、スタティックメソッドを介して全部この人がやってくれる。
 メッセージ交換の各フェーズで発生するイベントには、デリゲートメソッド(メソッドのポインタ)を渡して、コールバックしてもらう、というのが基本的な考え方。

・つまり、CoreEvents.prefabとStoreEvents.prefabをシーンに配置する。

・そして、商品を定義する。
 IStoreAssetsを継承して、必要なインターフェースを実装する。

・イベントのデリゲートメソッドを指定して、Soomlaをセットアップする。

//必要最小限のイベントハンドラ
//他にもイベントはたくさんあるけど、それらは必要に応じて。
StoreEvents.OnSoomlaStoreInitialized += OnSoomlaStoreInitialized;
StoreEvents.OnMarketPurchase += OnMarketPurchase;
StoreEvents.OnRestoreTransactionsFinished += OnRestoreTransactionsFinished;

//Soomlaのセットアップ
SoomlaStore.Initialize(<上述した定義済み商品>);

・Soomlaを介したアイテムの購入は、スタティックメソッドをぶったたくだけ。あとは発生するイベントを上述したデリゲートメソッドで拾う。

・アイテムの購入前に、グーグルプレイを活性化してやる。
 OnSoomlaStoreInitialized内で、SoomlaStore.StartIabServiceInBg()を呼ぶ。

・非消費型アイテムの購入済チェック。
 OnRestoreTransactionsFinished のイベントで購入済みの有無を判定。

・消費型・非消費型のアイテム購入。
 OnMarketPurchase のイベントで購入後処理。

・課金が必要なくなったらグーグルプレイを非活性にしてやる。
 OnDestroyなど課金機能を抜けるタイミングで、SoomlaStore.StopIabServiceInBg();を呼ぶ。
 
以上、概要を理解してしまえば、Soomlaは非常に簡単に使用できるように思う。

4年前

2 コメント

  1. はじめまして、高山と申します
    売れるゲームが作れるわけでもないのに、いつか売れることを夢見てブログを読ませていただいています
    この記事の内容について少しわからない点があり、教えていただきたいのでしょうか

    わからないのはサンプルコード内のGetItemBalanceはどこにある情報を参照しているかの点です
    >if (StoreInventory.GetItemBalance(BananaStoreAssets.BANANA_ITEM_ID) >= 1)
    これが仮にGooglePlayに記録された情報を参照するのであれば、オフライン時にモザイクバナナになったりしてはしまわないのでしょうか
    はたまたsoomla内部で管理されている情報を参照しているのであれば、端末の買い替え等に対応できないような気がしてしまいます
    それとも、それらの問題が起きないようにsoomla内部で工夫されていて、心配するような問題は生じないのでしょうか

    1. 高山さん、はじめまして。
      かなり前の記事なので記憶をたどりながらの回答です。参考程度にお読みください。

      StoreInventory.GetItemBalance(BananaStoreAssets.BANANA_ITEM_ID) で得られる情報は、グーグルプレイのアカウントに紐づいた購入情報を参照していると思います。オフライン時の処理はアプリ側で処理する必要があると思っています。

      たとえば、まずはPlayerPrefsなどセーブデータ内でアイテム番号をすでに保持しているかオフラインでチェックします。セーブデータ内になければ、上記の仕組みを利用してグーグルプレイから情報を取得します。

      取得できればPlayerPrefsに保存し次回からはこちらを参照する。もしネットワーク接続に失敗したら、ユーザーには接続エラー情報を提示するなどの処置を行います。

      このような仕組みにすれば、端末の買い替え時でもアイテムは引き継がれ、一度接続に成功してさえいれば、オフライン時でもアイテムを使用することができると思います。

コメントを残す

メールアドレスが公開されることはありません。