Unity開発におけるアンチパターン

2022/05/06に公開

TL;DL

Unity開発を5年以上、中・大規模アプリの開発に関わってきました。
そのなかで、これは...というものを集めてみました。

あくまで、中・大規模なアプリ開発で多人数で開発する際のアンチパターンです。
個人/小規模、またはサンプルコード的なものには当てはまりませんのであしからず。

Inspectorから設定する項目が全部 public で定義されている

Unityのサンプルなどでよく見ます。ただ、実害がないのであれば問題ないのですが、
通常のクラス設計として考えた場合は悪手です。本当に必要なものだけpublicで公開しましょう
[SerializeField] prviate で同様なことは出来ますので、こちらのほうが望ましいのではと思います

ただ、Unityのサンプルはpublicフィールドが使われることが多いですし、Unity開発の場合はどうなんでしょうね。
これに関しては未だに答えは無いです。

ただし、Serializerなどの制限でpublicにしなければならない場合などもあり、プロジェクト内で合意が取れているのであれば問題ありません。

全クラスがMonoBehaviour

Unityを始めたばかりの初心者がやってしまいがちのことですが、MonoBehaviourである必要のないクラスに継承させてはいけません。
MVCでいうModel層がまさしくそれで、MonoBehaviourである必要がまったくありません。
このあたりをうまく設計できるようになるとUnity初心者を脱せたという指標の一つでもあります。

うまく設計されたUnityプロジェクトではMonoBehaviourのクラスが非常に少ないです

コーディング規約がない

多人数で開発する際に、コードの品質をある程度保つためにコーディング規約はあったほうが良いです。
まあ、無くても開発はできますけどね。ソースコード全体を見て、書き方がバラバラというのは保守するのに苦労します。

以下のようにMicrosoftが用意しているものもあるので、こういうものをまるっと採用するのも悪くないかと思います。
https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/inside-a-program/coding-conventions

Visual StudioやRiderなどを使える場合はIDE上でチェックもできるので設定をチーム内で共有するのが良いかと思います。
(ライセンス料が高くて使えない職場も多くあり...)

Unityプロジェクト+CLIでコードスタイルチェックを行うためのツールを筆者は自作してもいます。
https://github.com/rookxx/StyleCopAnalyzers.CLI
※純粋なC#プロジェクトの場合は dotnet-format を使用するのを推奨します。

また、実際のチェックはJenkinsなどのCIで行い機械的にチェックするのが良いかと思います。
コードレビューで規約違反の指摘をするのは不毛です。

テストが全く書かれていない

ある程度の規模になると、様々な条件が絡み合うことになります。
そこで、テストを書き動作を保証するということが必要になりますが、テストが一件も書かれていないプロジェクトが結構あります。
複雑な条件が絡むModelをControllerやViewに繋ぎこんで動作確認する場合、実際にプレイして確認するよりもTestRunner上で動作保証が出来てから繋ぎこむと実装が楽にもなったりしますので、
テストをかけそうなところは積極的に書いたほうが開発が楽にもなります。

ビルドが手動

規模が大きくなってくると、一回のビルドにかかる時間的なコストは無視できません。
JenkinsやUnity Cloud Buildなどを積極的に使っていきましょう。
このようなビルド環境を整えないと開発終盤のリリース直前ではビルド地獄に陥ります。

Managerクラスがたくさんある

クラス設計をする際、そのクラスに何をさせるのかというのをじっくり検討する必要があります。
何らかの機能を管理するという意味で、Managerと名付けることが多々ありますが、
Managerと名付けてしまったがゆえに、あらゆることを行う巨大クラスになることをよく見る気がします。
(あくまで個人的な感想)

例えば、GameManager と名付けてしまったが故に、ゲームの何を管理するのかふわっとしたクラスとなり、
ありとあらゆる責務を追う超巨大クラスになってしまっていたものも見たことがあります。

C#の古いバージョンの書き方に固執する

Unityで使えるバージョンは純粋な.NETプロジェクトで使用できるC#のバージョンより古いです
最近では大分新しいC#のバージョン(9.0)の構文も使えて古さをあまり感じませんが、
少し前までUnityでは async/await なども使えませんでした

未だにその当時の名残で非同期は Coroutine のみしか使わないプロジェクトがあります

追加された新しい構文は理由があって追加されているので、積極的にC#の新しい書き方を使用していきましょう

Unityのアップデートに追従できていない

アンチパターンではないですが、追従できてないプロジェクトは非常に多いです
これは工数やコストの関係で出来ないプロジェクトが多数な気がします。
ですが、できる限りアップデートには追従する努力を続けたほうが良いです。

リリースされているアプリだとすると、それは本当に難しいのですが、最新のLTSでは使える機能が使えないというのは
ストレスにもなります

といいつつ、リリースされ運用されているものは、バグもなく正しく動くことが正義です。
Unityをアップデートしたおかげで致命的なバグを産んで顧客に迷惑をかけると目も当てられません。
なのでこれは、慎重にやるしかありません。

ですが、こまめにアップデートをしておけばアップデートのコストは最小限で済みますので、
月イチぐらいで最新版のUnityでビルドし、挙動に問題がないかチェックすることをおすすめします。

また、アップデートした際にバグが起き、それがUnity起因であるのであれば、
Unityに報告しましょう。そういうことでUnity Communityに貢献することも出来ます。

タイトル画面からしかプレイできない

ある程度の規模のアプリになると、クライアントのみでクローズすることは殆無く、サーバーと連携することが必須となります。その際、ユーザーの新規登録/ログイン/様々なデータのダウンロードなど最初に表示されるタイトル画面で行われることになります。

その結果、複数シーンで構成されているアプリの場合、タイトルから遷移しない限り正常に動作しないというものをたまに見かけますが、これは開発する上で非常に辛いです。

そのため以下のようなデバッグ用の処理を入れる事が多いです。

  • シーン開始直後にゲームプレイに必要な最低限の処理を実行させる(ログインなど)
  • それらの処理が終わってから各シーンの初期化処理を実行させる

これらの仕組みを入れることでデバッグが大分しやすくなります。
ただ、このような仕組みがなくタイトルシーンからのみしか実行できないアプリが多々あるのです。

シーン階層の深いところのPrefabなどを調整したときに、タイトルから潜り続けないといけないというのは非常に非効率です...

IDL(Interface Description Language)が無い

サーバー/クライアント間の通信仕様を決めるIDLが無いプロジェクトをたまに見かけます。
IDLは様々な言語で書かれており、json/php/ruby/markdownなど開発会社によって大分違います。
Protocol BuffergRPCでは、.proto というファイルによって通信内容が決められており、
送受信を行うコードの生成まで行ってくれます。
※違うように書いていますが、gRPCがProtocol Bufferを内包しているような形です。

また、Swaggerを利用するプロジェクトなどもあります。

IDLはあるが、コードを生成する機構がない

人は間違う生き物です。APIのパラメータが変わった/新規にAPIを追加したとなるたびに、人の手によってAPI接続用のコードを書いていては必ず間違えます。何よりそこに工数を割きたくないためAPI Call用のメソッドは自動生成するのが良いです。
そのため、IDLは何らかのコードからパースできる形のものを選び、テンプレートファイルなどを使用してコードを生成できるように構成するようにします。
gRPCを選ぶとコードの生成処理自体が含まれているので自前で用意する必要はありません。
また、Swaggerにもその機構があります。

jsonやmarkdownの場合はちゃんと書かれていればパースすることはそれほど難しくないため、それらを生成するツールを自作することも簡単です

サーバーと正しく通信できないと一切動かない

リリースされているアプリはそれが正かもしれません。
ですが、開発中に正しく通信できる環境が無いとデバッグ出来ないというのは危険です。
APIが変わりその修正が開発環境にデプロイされるまでデバッグが止まる可能性もあります。
そのため、通信環境が無くてもデバッグできるようにするためにモックを用意することが多いです。
また、モックの通信用コードは上記に上げた自動生成する際に、同時に生成してしまうという手法もよく使われます。

この機能の進捗どうなんですかー?っと聞かれて、
サーバー側の実装が出来てからじゃないと、こちらの実装も出来ないので待ちです! というのは無しの方向で...

チュートリアルの処理の入れ方が煩雑

ゲームのチュートリアルというものは、開発の中盤から後半に行われます。
開発中ではチュートリアルのことなんか眼中になく進められています。
そのため、チュートリアルをどうやって差し込もうか、ユーザーの入力をどうやってブロックし、対象のボタンしか押せないようにするかなど開発チームの腕の見せ所であったりもします。
ですが、そこに時間をかけられなかった、実装担当者が未熟だったという場合には悲しい結果が待っていたりします。

TutorialManager.IsTutorialというフラグによる分岐が数百以上ある

このManager名/フラグ名は仮ですが、ある程度ゲーム開発をしている人は見覚えがあるのではないでしょうか。
そして、このフラグ管理の分岐によってあらゆる機能の制限を行っているのだとしたらもう取り返しがつかなくなっています。

あらゆるクラスに紛れ込み、ここの処理を修正したらどうなるのか・影響範囲はいかほどなのかよくわからなくなってしまいます。

TutorialManagerと名付けられているが、お前はチュートリアルの何を管理しているんだとぱっと想像がつかないところも怖いです。どのクラスを見ても、TutorialManager というstaticクラスに依存しているという状態であったり、Tutorialという文言でgrepした際に数百を超えていた場合は、手遅れに近いです。
保守する人間が地獄を見ます。

uGUI/NGUIなどのUIシステムでゲーム自体が作られてしまっている

これも初心者がやりがちなことなんですが、UIシステムそのものを用いてゲームそのものが作られている場合があります。
(ここでいうゲームとは、パズドラでいうパズル/バトル部分だと思ってください。インゲームとも呼ばれています)
UIシステムは、あくまでUIを作るためのものであってゲームそのものを作るためのものではありません。

そのため、UIシステムで作られてしまった場合、

  • 余計なオーバーヘッドのおかげでパフォーマンスが出ない
  • 最適化しようにも、そのオーバーヘッドが邪魔して大したことが出来ない

ということに陥ります。ある程度の人数で開発してレビューする文化があるのであれば誰かが気づくでしょう。
ですが、開発後半になりよくよく見てみるとこんな作られ方をしていると気づいたときは凍りつきます。

ですが、これはゲームの種別によります。
アクションやパズルなどオブジェクトが動き回るようなものには向かないということです。

あとがき

書いてみると、Unityのことというよりも開発というお仕事に関する内容も多いようにも思います。
また、ここに書いてあることは、実際にストアにもリリースされているUnityプロジェクトで経験したことです。

Discussion