アプリ内課金実装周りで躓いたこと
アプリ内課金の実装にはUnityIAPを使用しています。
実装方法は、詳しい記事が色々とあるので、そちらをご参考下さい。
参考までに私が参考にした記事を1つ挙げておきます。
ただ、こちらの参考サイトでStart()で呼んでいるものをAwakeにしたほうが良いと思います。
理由は下記に。
UnityIAP初期化されない時がある問題
UnityIAPが、なぜか初期化されないことがありました。かと言って初期化失敗のコールバックが呼ばれているわけでもない。
色々とググッていたら、Startに書いている初期化をAwakeにしてみろ的なコメントを見つけました。
試してみたところ、それでうまくいきました。
Android版で課金しようとすると「認証が必要です。Googleのアカウントにログインしてください」っと出る。
英語版だと「Authentication is required. You need to sign into your Google Account.」
これ、色々と発生する可能性がありまして、ググると、
Google Developer Consoleにアップした直後だと駄目だから、ちょっと待て
とか
新しく追加したアカウントの場合は、端末を初期化しないと駄目だ
とか、
書かれています。
しかし、自分は、そのどれにも当てはまらず。
Google Developer Consoleではchatで相談に乗ってくれるので、質問してみました。
そうすると直ぐに解決。
購入商品が「有効」になっていなかったから
でした。
Google Developer Consoleの、「ストアでの表示」->「アプリ内サービス」にあります。
このスクショの右の方のやつですね。
こんなんに数時間ハマってしまった。
UnityでEditor実行時も30FPS
基本的にフレームレートを指定したいときはApplication.targetFrameRateに設定すれば良いです。
Application.targetFrameRate = 30;
とすると30FPSになります。
しかし、これだけだとEditorで実行していると、なんかヌルヌル動くなぁと思っていました。
調べてみるとどうもスクリプトで指定するだけじゃあかんらしく、QualityでV Sync Countを"Don't Sync"にせんとあかんそうです。
Edit -> Project Settings -> Quality
を選択し、
ここで設定できます。
こうしないとVSyncのタイミングに合わせて更新しちゃうってことなんですね。
だいたいのPCのVSyncは60FPSで行われるので60FPSになってしまうと。
知らんかった...
とあることをしたらアプリサイズが半分以下になった件
最近「ネコの絵描きさん」というアプリをアプデしたんですが、テスト中にそのアプリサイズに驚きました!
なんと400M超え!
これはあかん!と思い対策を考え始めました。
そもそも僕がゆとりプログラマーだったのでこうなったので、以下は、ゆとりじゃないプログラマーの方にとっては当たり前のことしか書いていません...
対策
一番、容量が大きいのは何か分かっていました。
めっちゃ数の増えたお弟子さんたちの画像です。
アニメーションがあるので約400枚もあります。
1枚あたり約0.5Mなので、これだけで約200M...
なんでこんなに重いんや?って、よく見るとデータフォーマットがRGBA 32bitになっています。
圧縮されとらんやないか!
どうやら、iOS版ではPVRTCという圧縮フォーマットが採用されているらしく、PVRTCが正方形の2の累乗サイズの画像しか扱えないから、圧縮されていなかったようです。
ちなみにUnityで利用するぶんには、正方形でさえあれば2の累乗サイズでなかったとしても内部的に2の累乗に変換してくれて圧縮してくれるっぽいです。
またASTCフォーマットという圧縮率が高い別のフォーマットもあるようですが、対応CPUがA8以降(iPhone6以降)らしいので、あと数年はPVRTC使おうかなって感じです。
正方形にするのか...
というわけで直接的に考えると画像1枚1枚を正方形にすれば良いのですが、それは面倒ですし、無駄も多いです。
そこで、アトラス化っていうのを試してみることにしました。
Unityをそこそこ使っている人ならみんな使っているアトラス化(たぶん...)
使っていないのは私ぐらいなものでしょう。
アトラス化っていうのは、複数のテクスチャを1枚のテクスチャにまとめることで、テクスチャの切り替えを少なくして描画負荷を下げましょうっていうものです。
このアトラス化の副次的な効果として、1枚にまとめられたアトラステクスチャは正方形の2の累乗サイズになるというのがあります。
これならPVRTCで圧縮できます。
アトラス化については、以下の記事が詳しかったです↓
kan-kikuchi.hatenablog.com
アトラス化... しかしバグる
アトラス化してみたところ...
なんか、めっちゃバグってるー。
なんか画が反転したり、他の絵が混じってきたりしてます。
自分のゲームはほぼ全てuGUIで作っているので、そのせいかも...
諦めかけたのですが、Allow Rotation と Tight Packing のチェックを外すことで解決することができました!
こんなだったのが
こんなかんじになる
アトラステクスチャのサイズ
基本的には2048ぐらいにしておくのが良いと思います。
大きいテクスチャにまとめるメリットとしては、
- テクスチャの切り替えコストが発生しにくくなる。
というのがあるのですが、デメリットとして、
- ハードウェアによってはサポートされていない。
- 1枚のテクスチャの未使用部分が多くなり無駄にメモリを圧迫する可能性がある。
というものがあります。
アトラスを細かく分けている場合、たまに1024とかにしたほうがトータルテクスチャサイズが小さくなることがあるので、そのときは1024にしても良いかも。
まとめ
というわけで、アトラス化することで400M超えから -> 150Mと半分以下にすることができました。
ちなみにAndroid版は、正方テクスチャでなくても圧縮が掛かるのでこのような問題はなかったです。
PVRTCに圧縮するとどうしても汚くなるので、そのあたりは自己責任でお願いします!
物理シミュレーションを行わずにOnCollisionEnterを使う方法
別に物理シミュレーションはしたくないのだけど、OnCollisionEnterで処理をしたいなぁと思って、とりあえずColliderだけをつけてたんですが、全然OnCollisionEnterが呼ばれない...
「なんでや????」って思ってたんですが、どうやらRigidBodyがついていないとOnCollisionEnterは呼ばれない模様。
こちらの説明によると、RigidBodyをくっつけた上でIs KinematicのチェックボックスをONにすればOKとのこと。
やってみたら、うまくいきました。
RigidBody2Dの場合はIs Kinematicのチェックボックスがないので代わりにBody TypeでKinematicを選択すれば良さそうです。
SingletonMonobehaviour再び
以前作ったSingletonMonobehaviourはシーン遷移時に破棄されるものでした。
以前のもの↓
waken.hatenablog.com
シーンをまたいだ時に破棄されないようなものも欲しくなったので、Awake内でDontDestoryにしています。その他もちょこちょこ変えています。
Awakeを上書きされるとPersistentじゃなくなっちゃうんですが、doAwakeという関数をabstractにしており、絶対実装しないと駄目なようにしているので自制しやすいんじゃないかと思います。
public abstract class PersistentSingletonMonoBehaviour<T> : MonoBehaviour where T : PersistentSingletonMonoBehaviour<T> { private static T instance; public static T Instance { get { if ( instance == null ) { instance = ( T )FindObjectOfType( typeof( T ) ); if ( instance == null ) { Debug.LogError ( typeof( T ) + " does not exist" ); } } return instance; } } //@memo //継承先でAwakeを定義しちゃ駄目 //代わりにdoAwakeを作る。 private void Awake() { if( this != Instance ) { Destroy( this.gameObject ); Debug.Log( typeof( T ) + " has already attached to \"" + Instance.gameObject.name + "\"" ); return; } DontDestroyOnLoad( this.gameObject ); doAwake(); } protected abstract void doAwake(); }
Hierarchy上のゲームオブジェクトを全て取得したい!
こちらのブログにて、Object.FindObjectsOfTypeを使う方法とResources.FindObjectsOfTypeAllを使う方法が紹介されていました。
私の場合、ActiveでないGameObjectも取得したかったのでResources.FindObjectsOfTypeAllを使っていたのですが、これですとシーン上に存在しないオブジェクトも拾ってしまいます。
ブログの方ではstring path = AssetDatabase.GetAssetOrScenePath(obj);を使って判断をしていますが、AssetDatabaseはUnityエディタ上でしか使えません。
悩んでいたところ、gameObject.scene.isLoadというプロパティを教えていただきました!
これがtrueならシーン内にある、つまりHierarchy上にあるということになります!
例えば、こんな感じに使えます。
foreach( GameObject obj in UnityEngine.Resources.FindObjectsOfTypeAll<GameObject>() ) { if( obj.scene.isLoad ) { Debug.Log( "私はヒエラルキーにいる" + obj.name + "と申します。" ); } }
SceneにはIsValid()というメソッドもあるので、未ロードのシーン内も取得したいなら、そちらを使うのも良さそうです。