
【第 2 回 cocone tech talk・後編】Singletonを使わないUnityを用いたApplication開発【イベントレポート】
-
2021年6月23日
こんにちは!cocone tech blog 新人編集者のYです!
今回は2021年5月27日に行われた「cocone TECH TALK VOL.2」でお話した内容をまとめたものを前編・後編に分けてご紹介します!
本記事は後編として「Singletonを使わないUnityを用いたApplication開発」というテーマについてのお話になります。
前編はこちら!
【第 2 回 cocone tech talk・前編】クソコードをシンプルにする【イベントレポート】
登壇者紹介
堀尾 大地さん
ポケコロツイン clientエンジニア
職務経歴
- 株式会社Aiming(約5年)
- 株式会社インディゴゲームスタジオ(約1年)
- cocone株式会社 ⇦イマココ
趣味
- ゲーム
- YouTubeでhololiveの配信を見る
- 欅坂46/櫻坂46
目次
Singletonを使いたくない理由
Unityを用いてApplicationを作る時にSIngletonを使うに至る流れは以下のような場合が多いと思います。
- UnityがSceneを起点としている
- アプリやチームの規模が大きくなってくると複数のSceneができる
- Scene間でデータのやり取りをする仕組みが欲しくなる
- Singletonを使おう!
Singletonは手っ取り早く実装も簡単ですが、しばらく使っているとこんなことが起きませんか?
- Scene間のやり取りのために作ったのに、気付いたらどこからでも呼べる便利クラスになっていた
- ボタンを押したら、ボタンの制御クラスが急にSingletonを呼び出してどうやって動いているのか分からなくなる
- Singletonを1つ作るとどんどんSingletonが作られる(割れ窓理論みたいな感じ)
- Singletonが依存しあって処理を追えなくなる
こうしてみると、管理コスト高くないですか?
結局のところ使う人による部分なので、使用ルールを作り必要最低限だけをSingletonでカバーするというのは良いと思います。しかし、人の入れ替わりが起こる運営型のProjectでずっと保守し続けられますか?
話を戻しましょう。今回の話はScene間のデータのやり取りをスムーズに行いたいだけです。つまり、SceneLoad時に必要な情報をScene側に良い感じに渡せれば良いのです。
ポケコロツインの実装紹介
こちらがざっくりとしたイメージになります。
まずApplicationManagerの役割です。一番大きな役割としてはApplication全体のエントリポイントとなることです。
どういうことかと言うと
- 起点となるSceneに設置されたGameObjectにAttachしApplicationのエントリポイントとなる
- 初期化時にこのGameObjectはDontDestroyし常に存在する状態にする
- ApplicationOperatorを初期化し、インスタンスを保持する
ということです。
次にApplicationOperatorの役割です。
- Sceneが共通して使うインスタンスを初期化し、保持する
- インスタンス同士の依存を解決する
Sceneが共通して使うインスタンスの例としてポケコロツインでは以下のようなものがあります。
Scene、UI、通信、AssetBundle、Sound、Localデータ、設定データ、ユーザーデータなど
また、2のインスタンス同士の依存と言われてもよく分からないと思います。
例えばサーバーからの返答を待っている時はユーザーに操作をさせたくなかったり、ユーザーに通信中であることを伝えるために通信中であることを示すUIを表示することが多いと思います。つまりUIを出すためのUIの処理に依存している訳です。そのため、ApplicationOperator側で通信中はこのUIを出す、といった依存を解決することでApplication層側でUI処理を意識する必要がなくなるという状態になります。
次に実際にどうやってScene側にApplicationOperatorを渡すのかについて説明します。
SceneClassの定義はこのような形になります。
MonoBehaviorを継承したAbstractSceneを作り、ApplicationOperatorをセットします。このようなClassを継承したClassを各Sceneに配置したGameObjectにAttachします。
次にSceneのLoadです。ApplicationOperator側が持っているSceneを管理するインスタンスにこのような関数があると思ってください。
LoadSceneをする時にSceneNameを用いて呼び出します。
Unityの機能でSceneをLoadし、UnityEngineのSceneのObjectを取得します。次に取得したSceneのRootObjectからAbstractSceneを取得し、そのComponentに対してApplicationOperatorを渡しています。
最後にSceneをLoadすることで、Sceneで使いたいインスタンスをScene側が持っている状態でLoad処理をすることができます。
まとめ
Singletonを使わずにScene間のやり取りを実現して良かった点としては以下のことがありました。
- インスタンスの使用スコープを絞ることができる
- エントリポイントが明確なので処理が追いやすい
- 再利用性が高い
逆に悪い点としては以下のことがありました。
- 学習コストが高め
- 雑にApplicationManager側に処理を作られる
結局のところ、どうやって教育していくかが重要になります。
Q&A
Q1. 新しいスクリプトを作る際にOperatorの機能を使いたい場合はどのような形でOperatorにアクセスする形になりますか?
A1. 例として出したものはできるだけ簡潔になるように、ApplicationOperatorを渡す、といった表現をしたが実際にはもう少し色々やっています。ApplicationOperatorをそのまま渡したくはないので、Application層が使うであろう関数などだけに絞ったinterfaceを定義し、そのインターフェースを渡して利用しています。
Q2. Operatorの実装イメージを簡単に共有して欲しいです。
A2. OperatorにはApplication層で触って欲しくないデータも含まれているため、interfaceを噛ませることで参照のみに制限するなどをしています。Operator側に書く処理の1つの基準として、他のProjectでも使い回すことができるかどうか、があります。
Q3. このフレームワークの使い方を新メンバーに伝える、教育する際の工夫などありますか?
A3. 中途採用などある程度経験がある人は思想を伝え、コードを触ってもらえればだいたい把握してもらえる物になっている気がします。新卒の方やあまり経験がない人については、理解しきれない部分が多いのでタスクを振る時の順番を考えながらタスクを通して学んでいってもらいます。
Q4. 例示されたAbstractSceneクラスはその内部からabstractメソッドを呼んでいないため、interfaceでも良い気がしました。
A4. interfaceで問題ありません。実際のコードでは例示した物以外にも色々としているのですが、基本的にはinterfaceでいいと思っています。
Q5. Sceneスクリプト側からinterfaceを実装したインスタンスをどう取得するのか、という部分のイメージが湧きませんでした。
A5. SceneのLoad時にSceneスクリプトに対して必要なinterfaceを渡し、インスタンスを初期化しているだけで、依存注入などの難しい事はしていません。
スライド
いかがでしたか?
便利だからと雑に扱うのではなく、将来的な管理コストを考えて工夫をすることがサービスを長く続ける上で大切だということですね。
以上でcocone TECH TALK VOL.2のイベントレポートは終わりとなります。
前編をまだ読んでいない方はこちらから!
【第 2 回 cocone tech talk・前編】クソコードをシンプルにする【イベントレポート】
cocone TECH TALK VOL.3の開催も予定しています。開催日時等決まりましたら告知致しますので、ぜひともご参加ください!
また、ココネでは一緒に働く仲間を募集中です。
ご興味のある方は、以下のリンクからぜひご応募ください。