日本で唯一のMicrosoft MVP for Developer TechnologiesとUnity Ambassadorのダブルホルダー

森哲哉氏:それでは「Unity Package ManagerとAddressable Assets Systemを用いたプロジェクト分割について」というタイトルで、キッズスター CTOの森哲哉がお話ししたいと思います。よろしくお願いします。

今日はこんな感じのアジェンダを用意しました。まずどんなことを話すかというところをザッと話して、本題をザッと話して、これをやるにあたっての注意点があるので、その辺も話してみようと思います。

さっそくLT(ライトニングトーク)の概要ですね。私は株式会社キッズスターでCTOをやっています、森と申します。美人の妻と、とってもかわいい娘5歳と、最近生まれたばかりの娘0歳9ヶ月に囲まれて暮らしています。Unity歴はだいたい8年目だと思います。TwitterやGitHubはこんな感じのアカウント(@monry)でやっていて、もんりぃ先生という名前で活動しています。

これは毎回言っているんですが、日本で唯一のMicrosoft MVP for Developer TechnologiesとUnity Ambassadorのダブルホルダーです。日本で唯一です。仕事は先ほども言いましたが、キッズスターというところで「ごっこランド」という、主に未就学児向のアプリをかれこれ7年くらい、けっこう長いことやっています。

このアプリがかなり好調で、今絶賛人を募集中です。このQRコードを読んで、ぜひ応募していただければなと思います。よろしくお願いします。今日のLTはUnity Learning Materialsで公開予定で、このあとアップロードしますのでお待ちください。

今日はAddressableについて、ちょっとマニアックな使い方を知りたいよという人、あるいはちょっとプロジェクトのサイズが膨れてきちゃってインポート時間がかかってしんどいんですという人、Addressableの深淵をチラッと覗いてみたい人などをターゲットに考えてスライドを作ってみました。

UPMとAASを用いたプロジェクト分割という、どこに需要があるんだかちょっと微妙なんですが、ひょっとしたら需要があるかもしれないネタについて話をして、基本的には、別のプロジェクトで作ったAddressableのCatalogを読み込んで、実際にアプリをビルドして動くようにするという話をします。

まだリリースはしていないんですが、実際に今これをやっていて、これをやるにあたっていろいろと課題も見えてきたので、その辺もちょっとお話ししようと思います。UPMやAASの細かい使い方は話しませんので、そのあたりはGoogleや過去に僕がしゃべったことなどを見てもらうといいかなと思います。

あとは、AASのCatalogの動的更新みたいなところは今回は触れないので、興味がある人はTwitterでメンションを飛ばしてもらえれば答えるかもしれません。ほかにも異常系の話も今回はしないので、そこはご注意ください。前置きは以上です。

「ごっこランド」はスマホゲームにしては大きめの53ギガバイト

では本題に入っていきたいなと思うんですが、プロジェクト分割についてです。実際に私が関わっている「ごっこランド」というプロダクトは、身も蓋もない言い方で言うと「キッザニア」のアプリ版と毎回呼んでいるんですが、今は確か41社、42社ぐらいの実在企業さんが、パビリオンというかたちで1つのアプリの中に出展しています。

各社、平均するとだいたい2個ぐらいミニゲームを展開しているので、60個以上のミニゲームが入っている、なかなかアグレッシブなアプリになっているんですね。そうすると、こんな感じのサイズになります。プロジェクトをduする(ファイル容量を確認する)と53ギガバイトという、スマホゲームにしてはわりと大きめのサイズです。

プロジェクトのサイズが上がるとどうなるのかというと、コンパイルやインポートの時間がメチャクチャ伸びるんですね。マジで長いです。その結果、生産性がめっちゃ下がります。実際に、1個のソースコードを1行直して、Unityにフォーカスを戻した場合、下手したら1分や2分かかることもありします。正直、マジで仕事にならないレベルなので、ちょっとこれはなんとかしなきゃいけないです。

継ぎはぎ状態のAssetBundleのManagerはしんどいのが現状

じゃあどうやるんですか、何をやるんですかというと、うちではパビリオンごとにUnityのプロジェクトを分割して、40個のプロジェクトのスクリプトだけ母艦にマージして、リソースはAssetBundleでダウンロードしています。

その辺のプロジェクト分割をやっているんですが、これはもともと今日お話をする技術を使う前からやっていて、GitHubからプロジェクトを引っ張ってきて、そのプロジェクトを母艦で全部yarnでガチャンコして、ガチャンコしたやつをビルドサーバーががんばって1時間ぐらいかけてビルドしています。

AssetBundleはオレオレなAssetBundleManagerでダウンロードしていて、まだリリースはしていないので、現時点ではそういうかたちで動いています。AssetBundleのManagerもかなり継ぎはぎで作っていて、秘伝のタレがいい感じに煮詰められていて、ちょっとしんどい状態でもあるのが現状です。

CatalogのURLの決定ロジックはきちんと考える必要がある

それをどうやって使っていくのかですが、当然Addressable Assets Systemを使います。キーになるのはAddressableクラスの.LoadContentCatalogAsync()です。あとはUnity Package Managerでプロジェクトを同期する技術を使うことで、今まで弊社がやっていたことをそのままプロジェクト分割できるんじゃないかなと思います。

もう少し深掘りをしていくと、まずはAddressable Assets Systemですね。これは単純にそれぞれのミニゲームごとにプロジェクトを作り、AASをビルドします。ビルドすると、catalogというJSONファイルが作られるので、そのJSONファイルを母艦側で読み込むという、ただそれだけの話です。

今日このあとお話されると思いますが、neuecc(@neuecc)先生が作られたUniTaskが役に立っています。ありがとうございます。助かっています。これはUniTaskを入れておいて、UniTask.Addressablesを参照しておくと、そのままawaitできてとても便利です。

.LoadContentCatalogAsync()を叩くとresourceLocatorが返ってきます。resourceLocatorから何をどう引っ張ってくるかは自由ですが、resourceLocatorに含まれる全ロケーションをダウンロードするなど、こんな感じのコードを書くと一括ダウンロードできます。

AASの話はそんなに難しい話ではなくて、こう書けばその通り動くよねという感じですが、CatalogのURLですね。これはただの文字列なので、決定ロジックは若干気を遣う必要があります。ほかにも、実際に今どのバージョンを参照しようとしているのかを母艦側に何とかして伝えないと、アップデートがうまくいかないなどが起こり得ると思うので、その辺はちょっと注意が必要です。

ハッシュ値は、ビルド時に.hashというファイルで吐いてくれるので、この辺も使うといいと思います。AASの話はこんなところです。

Unity Package Managerではクラスやコンストラクタが落とされないように注意

もう1個のUnity Package Managerですね。こっちでC#のスクリプトを同期しないとダメです。ご存知のとおり、Unityの場合はビルドするプレイヤーにスクリプトを全部突っ込んでおかないと、基本的には動かないので、AASを作ったプロジェクトのそのソースコードだけはなんとかして母艦に持ってくる必要があります。

あとはIL2CPPのコードストリッピングによって、クラスやコンストラクタが落とされちゃったりするので、「これを落とさないでね」というlink.xmlの情報などを同時にもっておく必要があります。これをするにあたって、UPMのGit参照という機能がとても便利なんだろうなと思っています。

これはUniTaskとUniRxも一緒に書いてあるんですが、こんな感じのJSONファイルを書くと、Gitからプライベートでも取得できます。詳しくはスライドをご覧いただければなと思います。

ドキュメントには、パッケージ名をcom.始まりにしないとダメと書いてあって、嘘やんという感じなんですが、jp.co.でもいけたので大丈夫です。きちんとSSHなどの鍵を設定しておけば、プライベートリポジトリも参照できるのでそこは問題ないですね。

もう1つここを気をつけておくといいよというポイントは、母艦で必要ないものは.npmignoreを使って除外すると、チェックアウトにちょっと時間がかかっちゃうんですが、少なくともプロジェクトにはいらないファイルがコピーされないので、ちょっと軽くなって便利です。さっきも言ったんですが、link.xmlは除外しちゃダメです。

Unity Package Managerの課題

課題はシーンですね。AddressableのグループがLocalLoadPathから引っ張る場合に、プレイモードがFastModeになっているとうまく開けないことがあります。

EditorSceneManager.OpenScene()、要は編集モードで開こうとすると、パッケージに入ってくるソースコードは全部ReadOnlyになってしまうので、それが開けないとエラーで怒られるんですね。なので、UPMの仕様によってはLocalLoadPathで開こうとする場合にちょっと注意が必要になります。

SceneManagerの.LoadScene()や.LoadSceneAsync()で開く場合には問題ないのですが、たぶんZenjectなどを使っているとこれにハマるんじゃないかな思います。

ほかにも、Catalog URLの動的更新もRemote Settingsか何かを使えばいけるんじゃないかなと思っています。依存の依存にはSemVerしか使えないので、パッケージのURL、GitのURLを指定するのができないのも課題かなと思っています。

ご視聴ありがとうございました。