この記事では、Unityを使って1人で開発したツールアプリ『リモートダイス3D』で対応したことや、使ったアセット・ライブラリなどをひたすら列挙していきます。
このアプリ特有の話はあまり出てこないので、ダイス系のアプリを触ったことがない方(が圧倒的に多いですよね)でも参考になるでしょう。いろいろな技術要素が含まれています。
「そんなアセット・ライブラリもあるんだ」「それは自分のアプリでも対応してみようかな」と知見を広げるきっかけになれば幸いです。
僕には売れるアプリの作り方は分かりませんがプロダクトを完成させる知識と技術だけはありますので、技術面を中心とした内容になっています。
各項目は詳しく説明しているものもあれば物足りない感じに留めているものも多いので「このあたりもうちょっと詳しく知りたい」というものがあればTwitterでシェアして頂くか、はてブのコメントを付けてもらえれば詳細記事が出るかもしれません。
▼目次もご活用下さい
目次
リモートダイス3Dについて
概要
iOS/Android向けのツールアプリ。以下のような流れで利用します。
- ルームを作成する
- 招待URLをシェアする
- 他の人がURLをタップするとルームに参加できる
- ダイスを振ると、ルーム内の人に結果が共有される
細かい機能はいろいろあれ、要はこれだけです。TRPGのオンラインセッション等での利用を想定しています。
もちろん他のダイスアプリのようにスマホ1台で利用することもできます。
開発期間
2021年3月 〜 2022年2月(そのうち開発に注力していたのは6ヶ月間くらい)
時間かけすぎました。反省。なぜ時間かかったのかは続きを読めば大体わかります。
Unityバージョン
Unity2020.2.7f1 で開発開始
Unity2020.3.25f1 でリリース
ダウンロード
ダウンロードしなくてもこの記事を読み進めていく上で支障はないですが、ダウンロードしてもらえると嬉しいな。
PCで読んでいる方はこちらのQRコードをご利用下さい。
Webビューア
スマホで送信 → MacのChromeで再現できた!WebGL完全に理解したで(なお謎バグが残っている模様)
unity-deterministic-physicsのやつですhttps://t.co/DqdjdCRLeF pic.twitter.com/jPJMZVPaMx
— AMAGAMI@Unityエンジニア (@ina_amagami) March 25, 2022
2022年3月29日にリリースしたアプリの補助ツールです。
招待URLをPCで開くことで、アプリ側でダイスロールしている様子をPCブラウザ上で見ることができます。
アプリではルームの人数制限を設けています。Webビューアでは人数制限を撤廃することで利用者の幅を広げられるようにしました。
アセット/ライブラリ
UI系のやつは、UIの項目にまとめています。
Jetbrains Rider
C#エディタ。Riderはいいぞ!
UniTask
Unityにおける非同期プログラミングを快適にしてくれるUniTask。もうUniTaskなしのアプリ開発は考えられません。
詳しくはYoutubeで解説しています。
UniRx
ネットワーク通信があると、他のユーザによる操作で画面に反映しなければいけないようなイベントがいつでも起こり得ます。
UniRxでイベントを監視すると対応漏れが発生しにくく、楽でした。非常に相性が良かったと思います。
UniRxについては、こちらの本がおすすめです。
DOTween
スクリプトでアニメーション付ける時の定番ですね。いろんな所にアニメーションを付けておくことで手抜き感が消えて良い感じです。
詳しくはこちらの記事で解説しています。
Pro版も持っていますが無料版の機能しか使ってないです。
EasySave
データのセーブを楽にしてくれるやつですね。PlayerPrefsも使ってますが、暗号化したいデータはEasySaveで保存しました。
WebView
アプリ内でWebページを表示します。ヘルプ・利用規約・プライバシーポリシーなどはアプリ内に文字を組み込もうとすると変更が面倒になってしまうのでWebページで管理すると楽ですが、かといってリンクを開く度にブラウザへ飛んでしまうのもイマイチなのでアプリ内で開けると良い感じです。
Odin
Inspectorを拡張するのがとても楽になりますね。でも他の拡張と競合しがちなのはネックです(例えば後述するLocalizationパッケージと競合します)。
開発中は使っていたんですが、リリース後はアプリサイズを削減するために削除しました。でも結果的に消した意味はあまりなかったです。
デバイスのシェア機能が使えます。URLをシェアする機能を入れるのに使いました。
unimgpicker
デバイスの写真を選択する機能が使えます。プロフィールアイコンのアップロード機能で使いました。
ZXing.Net
簡単にQRコードを生成できます。
デバッグ系のアセット
Unityエディタ上だったら見れるけど実機上では確認しずらいものを見やすくしてくれるアセットたち。
- InspectorとHierarchyを使えるRuntime Inspector & Hierarchy
- Consoleを表示できるIn-game Debug Console
- FPSなどのパフォーマンス表示できるGraphy
詳しくはこちらの動画で解説しています。
Unity公式パッケージ
Localization
神です。ローカライズ対応はもちろんのこと、日本語の文字しか入れない場合でも使っておいた方がいいです。
詳細については最近@harumak_11さんがLocalizationにハマっているようでブログにたくさん情報を載せてくれていますので、こちらを。
Device Simulator
いろんな画面サイズをエディタ上で確認するのに便利です。
iOS14 Advertising Support
AdMobやUnityAdsなどの広告SDKで使用するiOSのIDFA取得の許可ダイアログを表示します。
パッケージにサンプルもあるので、サンプルを参考にすれば簡単に実装できました。
Universal Render Pipeline(URP)
URPはとても便利ですね。3Dのところで解説する機能のいくつかはURPのおかげで簡単に実現できました。
公開中の自作ライブラリ
Reference Viewer
Unityって「このアセット、どこで使ったっけ?」を検索する機能がない(UE4にはある)ので、検索できるようにする拡張です。
もう使ってないアセットかと思って消したら、エラー。などという事態を防ぐことができます。
最近登場したRiderFlowでは、Prefabに関しては検索ができるみたいです。次のアプリ開発で使ってみます。
Overwriter
Unityは同じ名前のアセットをドラッグ&ドロップした時にコピー(”Texture.png” なら “Texture 1.png”)を作ります。
どちらかというとMacのFinderやWindowsのエクスプローラーのように上書きして欲しいことが多いでしょう。この拡張を入れると上書きできるようになります。
UnityC#デザインパターン集
現在は ServiceLocator / Singleton / StateMachine の3つを公開しています。
厳密に言うとGitHubで公開しているものは自分で使っているものとはちょっと違うのですが、おおよそ同じです。
詳細はYoutubeで解説しています。
UI系
Modern UI Pack
いつだったかセールで購入していたので使ってみました。ボタンデザインやアイコンなど、いろいろと揃っていて素晴らしいアセットでした。
C#スクリプトもたくさん入っているのですが、TextMeshProがベースになっていて、後述するように今回はTextMeshProを排除してしまったのであまり使っていません。
M+ FONTS
定番ですね。感謝。
UIブロッキング
@ohbashunsukeさんのツイートを引用させて頂きます。
UIを作る上でタップを無効化する「UIブロッキング」は必須。どうしても操作を受けつけたくないタイミングがある。画面遷移中、ダイアログ表示中、ローディング中など。ユーザーの動きは開発者の想像を超えてくる。想定しない場所のタップや連打。今操作されたくないときはとりあえずUIブロッキングです pic.twitter.com/1iKD1iuXEP
— オオバ@UIエンジニア (@ohbashunsuke) March 26, 2022
UniTaskに紐づけておくと簡単に使えて良い感じです。
拡張メソッドの実装はこんなイメージです。結果を受け取るUniTaskにも対応可 pic.twitter.com/LY0oP7nbWg
— AMAGAMI@Unityエンジニア (@ina_amagami) October 9, 2021
こういう入力判定だけ発生させたい時、α=0なら描画処理は実行されませんがImageコンポーネントでやるのはなんかイヤだよねって時に使える専用コンポーネントも作ってあります。
文字入力の制限
ユーザ名など自由に文字を入力できる場所がいくつかあり、標準のInputFieldだけで作ろうとすると不都合が起きやすいので拡張しました。
サロゲートペア・結合文字の排除
絵文字を入力されるとバグりやすいので、入力の段階で排除します。詳しくはこちらを。
文字数制限
例えば20文字入力できます。という時にアルファベットと日本語では長さが全然違うので、バイト数を使うことで多少は良い感じになります。
utf8の文字コードではアルファベットが1バイト、日本語はほぼ全て2バイト、一部の文字は3〜4バイトです。
3〜4バイトの文字が2バイト文字よりも横長というわけではないので、3〜4バイトの場合は2バイトとして扱いました。
アイコンなどを円形に切り抜くシェーダ
Maskでも切り抜けますが、シェーダを使うと境界のぼかし具合とか調整しやすいです。
gist作っておきました。
経過時間で表記する
履歴では時刻をそのまま表示ではなく経過時間に直した方が見やすいですよね。
SafeArea対応
iPhoneX以降のSafeAreaがある端末への対応。具体的にはCanvasを調整してSafeArea外にテキストやボタンなどが表示されないようにします。
実行確認は先ほど紹介したDeviceSimulatorでもいいのですが、重いことがあるのでGameビューのサイズをiPhoneX系統のサイズにしたら自動でiPhoneXのフレームっぽいやつが表示されるようにしています。
自前で作っていますが、SafeArea対応ができるアセットはいくつかあると思うので探してみて下さい。
スクロールビュー
見えないところまでリストの要素をすべて生成すると重いので、オブジェクトプールで実装するやつです。
これも公開されてるものがいくつかありますね。友達の@setchiさんが作ったFancyScrollViewとか凄いです。
Unity UI Playables
Timelineで細かく調整できるので、UIをまとめて動かしたい時にとても便利です。
チュートリアル
UniTaskで書いています。
指定した範囲以外を押させないようにするのは、@heppokoさんのGuardLayerImageを使わせて頂きました。
SpriteAtlas
アトラスを作ることで描画パフォーマンスが大幅に向上します。
2022年現在、Unity上で2D用のアトラスを作るなら標準のSpriteAtlasですね。AssetBundle(やAddressable)を使っていない状態ではトラブルも起きにくいです。
UIで使っているテクスチャは次の項目で解説するダイスアイコンを除くと、この1024×1024のアトラスに収まっています。
3Dモデルのアイコン化
3DモデルをUI上にそのまま表示しようとすると問題が起きがちなので、事前にアイコン化しておきます。
もちろん1つ1つチマチマと作成するわけではなく、全て自動生成するツールを用意しました。
ダイスアイコンについても、現在使用できる全ダイスの種類、カラーが2048×2048のアトラスに収まっています。
影を入れる
これをやるとき、ちょっと面倒なのが透明な背景に対して影を入れることです(別に入れなくてもいいんですけど、謎のこだわりです)。
3Dレンダリングにおける影って、普通は影を受ける側(例えば地面)が色を暗くすることによって表現しています(丸影みたいに板ポリで表示するやつは例外です)。
背景が透明ということは影を受ける側が存在しないということなので、普通のシェーダでは影の落としようがありません。
ということで、普通の表示とは真逆の「ベースが透明(何も表示しない)で、影を受ける部分だけ色を表示する」というシェーダを用意しておきます。
このシェーダを板ポリに適用して地面に置いておくことで、背景が透明でも影が表示されます。
ステータスバーを表示
時間やバッテリー表示など。ゲームでは非表示にすることが多いですが、ツールでは出ていた方がいいですよね。
iOSは設定で表示できて、Androidはこちらを参考にしました。
Escape対応
Androidのみ付いている戻るボタンを使えるようにします。
あのボタンを押した時の判定はInput.GetKeyDown(KeyCode.Escape)
で判定できるので、エディタでもEscapeキーを押せばテストできます。
実装方法はどんな形でもいいですが、このアプリではUIを開く度にStackに積んでいき、Escapeが押される度にStackから取り出してUIを閉じます。
UIを1つも開いていない時の挙動は何もしない。でもいいですが、Androidアプリの一般的な挙動としてはアプリ終了なので、Application.Quit()
するようにしました。
といってもUnityの場合、一度アプリを終了すると再起動するのに時間がかかるので確認ダイアログを入れています。
レビュー要求
ルームから退室した時など、アプリの利用を邪魔しないタイミングで出すようにしてみました。実装はこちらを参考に。
後からアプデで入れましたが、まだ誰も使ってくれてないです。
課金してくれている人はルームの時間制限がなく退室するタイミングがないので、ヘビーユーザーほどレビューする機会がないという…。
3D系
Dice Creator Pack
このアセットを導入するだけで3Dモデルから出目の判定スクリプトまで全て揃ったので最高でした。5.5ドルは安すぎます。
ただしURPには対応していないので、変換は必要でした。
D3を追加
1〜3の数字が出るダイスのことです。6面ダイスのテクスチャを加工して1〜3だけ出るようにしました。カイジに出てくる「456サイ」の123バージョンですね。
他のダイスアプリのレビューを見ていたら「D3を追加して欲しい」という要望がたくさんあったため、これはチャンスと思い入れておきました。
利用状況をチェックすると、D3を使っている方もいる様子なので入れて良かったと思います。
Texture2DArray
同じサイズ、フォーマットのテクスチャを1ファイルにまとめることができます。
今回のアプリでは、全てのダイス用テクスチャを1ファイルにまとめました。
こうしておくと、アトラスと同じ要領で描画パフォーマンスを向上させることができます。
なお、今回は文字部分の色があるかどうかさえ判定できればOKなのでRチャンネルだけ残しています。
事前につなげた画像であればインポート設定でTexture2DArrayにすることができますが、個別のテクスチャを1つにまとめる場合はスクリプトで生成します。
使用するときは専用のシェーダを書く必要があります。詳細はこちらを。
SRP Batcher
従来のUnityではマテリアルを増やすごとにSetPassCallが増えて描画負荷が増えていく。というのが常識でしたが、SRP登場以降はこれのおかげでマテリアルが増えることもあまり気にしなくてよくなりました。
URPを含むSRP環境におけるベースの考え方は「シェーダをなるべく増やさない方が良い」で、これにはシェーダバリアントも含みます。
このアプリでは同じテクスチャを使いまわしつつ「色を反転するかどうか」「色を加算するかどうか」のオプションを設けてダイスのカラーバリエーションを作っています。
このケースでもシェーダバリアントで処理を切り分けるのではなく 0 or 1 の値としておき、以下のようにlerpを駆使した計算で処理の切り分けを実現しています。
表現パターンごとに計算量が多すぎるなら引き続きシェーダバリアントでも良いですが、このようにしておくことでSRP Batcherを有効活用できます。
ただし対応していないプラットフォーム(AndroidのOpenGLES3.0以下とかWebGL)もあるので、サポート対象とする場合は引き続きマテリアルも増やしすぎないように注意しなければいけません。
SRP Batcher について詳しくはこちらで解説しています。
DOTSおよび決定論的な物理演算
こちらのunity-deterministic-physicsのおかげでなんとか限りなくゼロに近い通信コストで実現できたもの😊
右がEditor、左がAndroid
どの程度の需要あるか不明だしアプリの機能として必須でもなかったですが、せっかくの個人開発なので実現できたら面白いな〜と感じるものにはチャレンジしていく https://t.co/Y1Lmz8u4SP pic.twitter.com/X9XniR0iz6
— AMAGAMI@Unityエンジニア (@ina_amagami) January 24, 2022
ダイスの結果を共有するだけじゃなくて、通信相手のデバイス上でダイスが動いている様子も見れたら面白いんじゃないかなと思いました。
正確には僕がそう思ったわけではなくて、他の3Dダイスアプリのレビューを見ていたらそんな感じのことを書いている人がいました。
最初は「いやーそれはキツいで…」と思いました。
技術的にムリとかではなく、やろうとするとPhoton的なリアルタイム通信で動きを同期しないといけないので、サーバコストが高く付いてしまいダイスアプリの予算感とまったく合わないため。
しかし物理演算の結果が完全に一致するのであれば、物理演算に必要なパラメータ(Position・Rotation・Velocityなど)を1回だけ送信すれば再現できるんじゃない?ということに気づきました。
そこで試してみるわけですが、まあズレます。同じデバイス上なら標準のRigidbodyでもそこそこ一致するのですが、別のデバイスでは全く違う動きになってしまいます。
deterministic-physics
そしてこれについて知りました。
Unity公式でもDOTSに用意されているPhysicsなら結果が一定になりますが、違うデバイスだとfloat誤差でズレます。
deterministic-physicsでは、内部でfloatの代わりにuintを使用します。これによって完全一致する物理演算を実現し『LIVEモード』としてアプリに入れておきました。
あとは「Positionは固定の値を使う」「RotationやVelocityは送信せず、乱数のシード値を送って受信側で同じ計算をする」等の方法で通信量を削減しました。
しかしDOTSベースでコードを書かなければいけないのと、いろいろと制約があり実装には苦労しました。
詳しくはこちらで解説しています。
そんな苦労をして作ったLIVEモードですが、LIVEモード中は自分のダイスロールができないというイマイチな仕様のため、あまり意味はなかったかもしれません…。
→ (追記)ここは仕様を見直して、LIVEモードはデフォルトでONにし、自分のダイスロールもできるようにしたのでユーザーの皆さんに使ってもらえています。
後から思いついたWebビューアを作ったことで、ようやく意義が出てきたかもしれません。でも公開したばかりなので実際に使われるかどうかはまだ不明です。
deterministic-physicsではマルチスレッド厳禁なので、マルチスレッドが使えないWebGL環境とある意味、相性がいいかもしれませんね。
画面全体を対象にしたモーションブラー
PostProcessingStackに入っている「MotionBlur」は、いわゆるカメラモーションブラーというやつです。
リアルのカメラは動かした時に被写体がブレますがそれを再現するもので、VR酔いを軽減する効果なんかもあります。
しかしながら、このアプリにおいてカメラは動きませんので、カメラを動かさずにモーションブラーをかけたいと思いました。
Standard Assets
その昔、Unityには無料でダウンロードできる「Standard Assets」というものが存在していました。
これは現在サポートされていないのですが、この中に画面全体を対象にしたモーションブラーがあって、以前よく使っていました。
今回はそれをURPに移植して使いました。
3Dだけ解像度を落とす
最近のスマートフォンやタブレットはめちゃくちゃ高解像度です。
UIは高解像度で表示しないとボケてしまってイマイチなのですが、3Dは解像度を落としてもそんなに気になりません。
少なくとも、FullHD(1920×1080)よりも大きな解像度はまず必要ないと思います。
ポストエフェクトを利用していると解像度がダイレクトに描画負荷に影響してきますので、落としておくメリットは大きいです。
SRP登場以前はこの対応をするのがとても大変でしたが、URPならRenderScaleの設定をいじるだけです。
ただし注意しなければいけないのは、これは元の解像度を1としたスケールであるということ。
元の解像度が2000のものを0.5にしたら1000ですが、1000のものだと500まで落ちてしまい、とても残念な見た目になります。
手動でRenderScaleをいじるのではなく、スクリプト上で計算して設定するのが安全です。
グラフィックス品質の自動設定
クリエイターにとってはなるべく良い画を見せたいものです。
とはいえ最高品質を基準にしてしまうとショボイAndroidでカクカクする事態になりかねないので、今回は4段階のグラフィックス品質(Lowest – Low – Medium – High)を用意しています。
内容の違いとしては「3D解像度」「影の品質」「ポストエフェクトの有無」など、影響の大きいものを中心に。
しかしながら4段階もあるものをユーザ側に選ばせると「どれ選んだらええねん!!」となることが想定されるので、デバイス情報から推測される性能を使ってこちらで勝手に決めてしまいます。
デバイスの性能判定
iOSでは UnityEngine.iOS.Device.generation
でデバイスの種類を取得できるので「iPhone8以降ならHigh」みたいな感じで判定します。
AndroidはグラフィックスAPIのバージョン(例えばSRPBatcherが有効になるOpenGLES3.1以上かどうか?)と、
GPUの名前などを使って判定します。「Adrenoのバージョンxxx以下だからショボイ」みたいな感じです。
でもxxx以下だからショボイかと思いきや下二桁の番号によってはハイエンド。みたいなケースもあってこの対応は非常に面倒なのでおすすめしません。でもこれくらいしか方法がないの。
省エネモード
そんな感じでデバイスの性能判定はなかなか難しいところがあるので、自動判定では最低品質のLowestを返さないようにして、設定で1段階下げられるようにしておきました。省エネモードです。
ついでに省エネモードをONにした時はFPSが60ではなく30になります。これでバッテリー消費は大幅に抑えられるはず。
Firebase
部分的にFirebaseを使うことも多いと思いますが、今回はバックエンドすべてFirebaseで実装しました。
Unityでゲームを作る場合はゲームに特化したmBaaSが良いかと思いますが、ツールを作るならFirebaseはかなり使い勝手が良い印象です(もちろんゲームにも使えます)。
ここで紹介するものはすべてUnity向けのSDKが用意されています。
Authentication
ログイン機能。今回は Twitter / Facebook / Google / Apple のログインに対応しました。
これらのログインを使わずにユーザ登録した場合、アプリ内部でランダムに生成したメールアドレスとパスワードを使って認証します。
認証するだけなら外部アカウントのログインを実装する必要はなかったのですが、今回はプロフィールアイコンを取得するために入れました。
インターネット上に住んでいる人々にとって、名前よりもアイコンで人を判別するよね?という想定のもと、どうしても入れたかったのです。
でも、これは大失敗でした。理由はいくつかあります。
画面が1つ増える
アプリインストールからユーザ登録まで到達しないケースが割と多いのですが、アカウントを選択する画面で「うっ」となっている可能性が高そうです。
(追記)アップデートで外部認証あり / なしどちらも1画面で選べるように修正したので、現在は解消しました
実装コスト高すぎ
外部アカウントのログインに対応することで認証フローが複雑化し、実装もデバッグも大変でした。
認証まわりでトータル2ヶ月くらい使った気がします。今後も認証フローを実装するなら使い回せるのでいいのですが、果たして使うかどうか…。
意外とアイコンアップロードしてくれる
手間かと思ったんですが、意外とみなさんアイコンのアップロード機能を使ってくれます。むしろアカウントにログインしてもらう方が手間だったかもしれない。
認証フローに時間をかけるくらいなら、アップロードする時にアプリ上で正方形に切り抜くやつを実装すれば良かったかも。今は正方形の画像だけアップロードできるようにしています。
Appleログイン使う人おおすぎ
AppleIDは仕様上プロフィールアイコンを取得できなかった(そもそもAppleIDにアイコン設定してる人おる?)ので認証ファミリーに入れたくなかったのですが、入れなかったらリジェクトだったので入れました。
iOSではAppleログインを使う人が大多数。意味ない…。ちなみにFacebookは利用者0人です。→ (追記)その後もFacebookは利用があまりなかったため、削除しました
逆に言えば、認証フローを用意するならAppleログインとGoogleログインだけ作るのは利用してもらいやすくてコスパが良さそうです。
Realtime Database
データをJSONで管理するNoSQLデータベースです。ユーザのデータ管理はコレで完結しています。
素晴らしいポイントがいくつかあります。
端末から直接アクセスできる
アプリが動作している端末から読み書きできるので、サーバを経由する必要がありません。
それで安全なの?という話なのですが、何も考えずに作ると当然ながら危険極まりないです。
「ルール」というシステムがあるので、これでユーザのデータはそのユーザ本人しか読み書きできないようにしたり、書き込めるデータを制限したりします。
もっとセキュアに利用したい箇所については、後述するFunctionsを経由してアクセスします。
リアルタイム通信できる
今回のアプリと非常に相性が良かったのがこちらの機能です。
「ダイス結果がデータベースに追加されたら自動で受信してね」と指定しておくことによって、追加された瞬間に送信されてきます。
最初はタイムラグがあるのかな〜と思って使ってみたら、とても高速です。
これのおかげでダイス結果の共有はもちろん、ユーザが名前やプロフィールアイコンを変更した時に他の人の画面にも即時反映するなど、いろんな機能を楽に作ることができました。
料金体系
進化版の新しいデータベースシステムで Cloud Firestore もありますが、今回はアプリ内容と料金体系の相性が良かったのでこちらを選択しました。
Realtime Databaseでは「ストレージの使用量」と「データのダウンロード量」で料金が決まります。
データを送受信する回数についてはあまり関係ないので、今回のように少量のデータを頻繁に送受信するようなアプリと相性が良いと思います。
無料枠も1GBストレージ&月間10GBダウンロードと、かなり充実しています。
データの管理はちょっと大変
現時点ではユーザ数も多くないのでFirebaseのコンソールで管理できています。
ユーザが増えるとデータを探してくるのも大変になりそうなので、別途Adminツールを作成するのがいいかなと思っています。一度作れば他のアプリでも流用できそうですね。
解散されたあとのルームのデータとか、今はそのまま残していますが肥大化すると料金に影響するので、統計とバックアップを取ってから削除するようなバッチ処理も作りたいですね。
Storage
プロフィールアイコンのアップロード先として使用しています。
こちらもデータベースと同じく無法地帯にならないようにセキュリティルールをちゃんと設定しておけば非常に便利です。
Dynamic Links
ルーム招待URLに使用しました。以下の優先順で動作するURLを生成できるシステムです。
- アプリがインストールされていたら、アプリを開く
- インストールされていなかったらiOSではAppStore・AndroidではGooglePlayを開く
- PCなど、その他のデバイスでは公式Webサイトなどの指定したURLを開く
Unityでは正常に動作しないケースもあってちょっと大変でしたが、なんとか使えています。
当初はTwitterとFacebookログインをブラウザで行い、アプリに戻る時に使用していましたが、iOSの審査時にうまくアプリに戻れない問題が起きてリジェクトになったのでWebViewでの認証に変更しました。
RemoteConfig
名前の通り、リモートで変更できる設定データ。
- お知らせの表示
- 利用規約・プライバシーポリシーの変更通知
- メンテナンスモード
- 必須アプリバージョンの指定
等に使用しました。でも挙動にクセがあるのでUnity公式のRemoteConfigを使った方がいいかもしれません。
Functions
JavaScriptでサーバコードを書いて手軽にAPIを公開できます(js以外も一応使えるようです)。
Twitter・Facebookの認証フローと、サーバタイムの取得に使っています。
サーバタイムの取得?
デバイスの時間は設定で簡単に変更できてしまいます。時間を変更してスタミナ回復する裏技とかありましたよね。
このアプリではルームに時間制限を設けていたり、ダイスロールした日時を履歴から確認できるようにしています。
時間がらみの処理がおかしなことにならないよう、DateTime.Now()
などではなくサーバの時間を取得して利用します。
サーバタイムはアプリ起動時とバックグラウンドから戻ってきた時に取得し、最後に取得してから経過した時間を足し合わせることで現在時刻とします。
もちろんサーバタイムを取得する通信をしている間は、時間関係の判定をしないよう注意が必要です。
Cloud Messaging
プッシュ通知を送るやつ。
Crashlytics
実機のクラッシュログを見れる。必須ですね。
すべてのデバイスを自分でテストするのは到底不可能なので、ユーザさんのクラッシュログを参考にさせてもらいます。
開発環境と本番環境に分ける
リリース後でもテストしやすいように、Firebaseプロジェクトをdevとprodの2つに分けました。
エディタ上とDevelopmentBuildの場合はdevに、リリースビルドではprodに接続します。
間違ってprodを触らないための機能として、Firebaseの設定で本番環境であることを指定できます。
大規模なチーム開発ではdev、stg、prodのように分けますが、1人での開発なら2つだけで十分ですね。
アプリサイズの削減
2022年現在、iOSのアプリサイズは150MB以内に収まっていればWi-Fiなしでダウンロードできますが、数字の印象から100MB未満に収めておきたいと思いました。
TextMeshProを排除
正確には覚えていないですが、3MBくらい削れたと思います。
今回はアセットの追加ダウンロードに対応しなかったので、TextMeshProの多言語対応が面倒でした。次のアプリ開発ではちゃんと使います。
ちなみにTextMeshProじゃないと困る問題の中にアウトラインの話があると思うのですが、こちらのMultiOutlineを使えば結構なんとかなります。
StripEngineCode
ProjectSettingsで設定でき、IL2CPPビルド時に不要なコードを削減してくれます。iOSリリースビルドのサイズに大きな影響を与えていました。
Managed Stripping Level を Middle以上にすると効果が大きかった(Lowよりも10MB以上の削減になった)のですが、アプリ起動でコケるようになったのでLowにしておきました。
どうしてもアプリサイズを削減したい時は、エラーが出る箇所を「link.xml」で地道に潰していくことも可能です。
テクスチャの圧縮設定
iOSは「ASTC」でAndroidは「ETC2」にしておけば対応端末を広く取れます。
AudioClipの設定
今回はSEのみなので素材から無音部分を切り取ったのと、モノラル化のチェックだけ入れておきました。
設定のポイントはこちらで解説しています。
使ってないPackageやプラグインを消す
プラグインごと消せるようなものはそのまま削除。消せないものについても使っていないスクリプトに絞って消すなどの対応をしたら、5MBくらいは削減できました。
ビルド周り
Git & GitHub
バージョン管理はGit、リポジトリの格納先はGitHubのprivateリポジトリです。無料で使えるようになって本当に感謝ですね。
Cloud Build
手動ビルドはあまりにも大変です。自動ビルド環境があると捗りますね。
CloudBuildは、Unity公式というだけあって使いやすいです。こんな感じのビルドフローを設定だけで作ることができました。
- GitHubにプッシュしたらビルドが走る
- 終わったらSlackに通知が来る
- Slackにインストールリンクが投稿されるので、そこから端末にインストールする
ビルドフローの構築について、詳しくはこちらを。
こちらの記事では配布用にDeployGateへデプロイするところまで対応していますが、今回は1人開発なので配布対応まではしていません。
CloudBuildの利用はUnity Teams Advancedへの登録が必要なので月額1000円くらい料金が発生します。
無料で自動ビルド環境を構築したい場合は、GitHub Actionsの無料枠で頑張る方法も。
もしくは使っていないMacがあれば、Jenkinsで頑張る方法などがあると思います。
CloudBuildのデメリットとしてビルド時間が長い(最近ちょっと改善された気がします)ので、高速化したい場合はJenkinsの利用もアリかと思います。
僕は1人で開発するのにビルド環境のメンテナンスまでしたくないので、CloudBuildにおまかせすることにしました。アプリ開発でどれくらいの収益を狙っているかにもよりますが、コスパは良いと思います。
マネタイズ
広告(AdMob / UnityAds)
バナー広告・リワード広告・インターステシャル広告を使用。
iOSのAdMobはリリース直後に広告を表示できないので、UnityAdsのメディエーションも設定しておくとiOSの審査が楽です。こちらを参考にしました。
サブスクリプション(IAP)
複数人でアプリを利用する場合も、1人だけ課金していれば恩恵を受けられるようにしています。
そのため仲間内なら割り勘できるはず。もしくは課金した人が英雄として扱われるであろうということで、そこそこ強気の値段設定にしています。
価格のローカライズ
AppStore/GooglePlayが自動計算してくれる海外向けの価格は為替レートしか考慮されていないので、物価も考慮した価格に設定しました。
といっても国ごとの物価について詳しく調べたわけではなくて、デジタルコンテンツで全世界配信されているNetflixの価格を参考にしました。
2日間かけて、AppStoreの175カ国、GooglePlayの177カ国すべて対応しました。
今のところ日本とアメリカ以外ではほとんどダウンロードされていませんので、効果の程は不明です。
次からはもうちょっと工夫します
正直マネタイズの設計については失敗でした。このアプリに関しては現状このままにして、次からもうちょっと工夫していきます。
改善案はあるので、ユーザ数が増加するようなことがあれば対応を考えたいと思います。
具体的な内容は、まだ伏せておきます。
Webサイト
ゲームアプリはどうか分かりませんが、ツールアプリだとWebアプリでも代替できることが多いこともあってAppStoreやGooglePlayではなく、Google検索する人も多いみたいです。
ちゃんとSEO対策したWebサイトを用意しておくと、そこからダウンロードしてくれる人もいて良い感じですね。
レンタル済みのサーバを使用
このブログなどと同じサーバに置いています。いくつ置いても追加料金かからないのでいいですね。ちなみにエックスサーバーです。
ドメインも同じ(正確にはサブドメイン)なので、Google検索にも引っかかりやすいです。Google検索では、ドメインの運用歴が長いほど評価されやすい仕組みです。
ローカル環境で構築してからデプロイ…ではなく、誰かが見に来るわけでもないし仮サイトの状態からサーバ上で作成、公開していました。その方がドメインの運用歴が長くなっていいと思います。
WordPress
世界中のウェブサイトのうちWordPressで作られているものが40%を超えているとかなんとか。
ブログ用に購入した有料テーマのJINをすべてのWebサイトで使いまわしています。
ただ、ブログ形式にはとても相性が良いですがトップページ作成に弱い気がするので、今後は他のテーマも検討してみようと思います。
英語版Webサイト
一部を除いて、すべてのページに英語版も用意しました。
JINで英語サイトを作る方法についても記事を書きました。
この記事はWordPressならでは
余談ですが、この記事のようなアホみたいに長くて画像もベタベタ貼っている記事を作れるのはWordPressの強みです。
大抵の無料ブログでは、こんな記事を書こうものなら読み込みが遅すぎて戻るボタンを押したくなります。読み込みが遅いとGoogle検索の評価も落ちます。
Qiitaとかはこのあたり上手くやっている方だと思いますが、WordPressなら画面に映る直前に画像を読み込むようなプラグインとか、各種最適化ができるので良い感じです。
お問い合わせフォーム
アプリ内のお問い合わせフォームはメーラーを立ち上げるような方法ではなく、Webサイト側に用意しておき、WebViewで表示するようにしてみました。
Webサイトからアクセスできるものとアプリ内のお問い合わせフォームは分けています。バグ報告などは端末情報を取得するためにアプリ内から送信して欲しいため。
実装はWordPressのプラグイン Contact Form 7 を使っています。
このプラグインではURLに「your-name=ユーザ名」みたいにクエリを付けてページを開くと自動入力ができるので、それを使って端末情報が自動入力されるようにしています。
Webページなのでcssで非表示にしておくこともできますが、ユーザさん側で「端末情報が送信されてるなんて知らなかったよ!」などとならないように編集不可の状態で表示だけしています。
ちなみにお問い合わせは今のところ、営業メールしか来ていません。
WebGL版(Webビューア)で使用したライブラリ
Firebase WebGL
WebGLでFirebaseを利用するには、JavaScriptのSDKを利用しなければいけません。
こちらのライブラリではFirebaseの一部の機能について、jsのSDK側からUnity側への橋渡しをしてくれます。
今回は Realtime Database のみ使用し、バグを発見したので修正してプルリクを送っておきました。マージ済みです。
用意されていないものについては結局jsを書くことになりましたが、SDKの使い方が参考になって非常に助かりました。
WebGLInput
WebGLではInputFieldに対してコピペができませんが、できるようになるプラグインです。
MiniJSON
Realtime Database で2件以上のデータが入ったJSON(Dictionary
に変換したいやつ)をパースするのに丁度よかったです。
iOS/Android用のSDKでは、データを1件ずつ取り出すことができるので標準のJsonUtilityだけで大丈夫でした。
その他
AppとLibraryにフォルダを分ける
自分で作ったコードやアセットは「Assets/App」以下と「Assets/Library」以下に分けて置いています。(自分で作ってないやつは「Assets/Plugins」以下に入れます)
Appにはアプリ固有のC#スクリプトやアセットを、Libraryには他のアプリでも使い回せるものを入れます。
こうしておくと、次のアプリを作る時にLibraryをまるっとコピーして必要ないものを消せばスムーズに開発を始められます。
開発サイクルがやたらと早い人達は、似たようなことをしてるんじゃないかなと思います。
AssemblyDefinition
Libraryフォルダ内にはAssemblyDefinitionを作っておくと良い感じです。
App側だけを変更した時に、Libraryに関してはコンパイルが走らなくなるので時間短縮できます。
Library側のコードからApp側のコードへアクセスできなくなるので、設計もキレイになります。
AssemblyDefinitionについては、こちらの動画でも解説しました。
Enter Play Mode
Reload Domain をオフにするとスクリプトのリセットを省略してくれるので、エディタの再生開始が爆速になります。
static変数の初期化を忘れるとバグりますが、意外と問題が起こることは少なかったです。パッケージによってはこれが原因でエラーになることがあるので、その時だけ1回 Reload Domain をオンにして再生していました。
UniTaskを使用するときはキャンセルを忘れると残り続けるので注意です。
Reload Scene はオフにすると動かなくなったので、オンにしています。Domainだけでも十分に早くなります。
法人アカウントでのリリース
今回のアプリは初めて法人アカウントでリリースしました。
会社の英語名を任意のものにしたい場合、ちょっと対応に時間がかかったので法人アカウントを作る場合は早めに対応しておいた方が良いと思います。
アカウントを作る時にAppleさんから電話がかかってくること以外では、個人アカウントとの違いはあまりないように思います。
AppStoreのプライマリ言語について
最初は日本のみでリリースしたので、ストアの説明文などの言語も日本語だけでした。
日本のみでリリースする場合でも、AppStoreで設定する「プライマリ言語」は英語にしておけば良かったです。後から変更するのはアプリ更新&審査が必要で大変でした。
韓国語/中国語(繁体)の翻訳
TRPGは韓国でも流行ってるらしいよ。ということで、アプリ内は英語になっちゃいますが、ストア情報は韓国語/中国語も対応しておきました。
翻訳は基本DeepLでいいと思うのですが、韓国語と中国語(繁体)に対応していません。
韓国語についてはPapagoの評判が良く、中国語(繁体)にも対応していたので、この2つについてはPapagoを使っています。
読めるものになっているかは、全くわかりません。ただ、明らかにおかしいと気づいたものについては修正しています。
アプリ内まで対応したり翻訳を外注するのは、その国のユーザー層の厚さを確認できてからでいいかなと思っています。
まとめ
久々にとんでもないボリュームの記事を書いちゃいました。
こんな記事を書いておきながらアレですが、1人で開発する時にアレコレと対応しすぎない方が良いと思います。
1ヶ月とかからずアプリをリリースできちゃうスゲー人もたまにいますが、本当に対応すべきところを見極める能力に長けているのでしょう。
1度でも学習・対応したら次から使い回せるものについては積極的に取り入れても良いと思いますので、ぜひご活用下さい。
参考になったことがあれば、ぜひアプリをダウンロードして☆5評価を頂けると嬉しいです!
大事なことなのでもう一度言います。☆5評価お願いします!!