こんにちは、みんな。私は経験豊富な Unity 開発者です。 HackernoonとTatum Gamesによるコンテストに参加することにしました。最初の記事では、Unity ゲーム開発プロジェクトのアーキテクチャについて説明し、私がこれまでに遭遇した最も一般的なアプローチについて説明します。そしてもちろん、なぜ私がそんなにマゾヒストなのか、そしてなぜ私の愛するHMVС(HMVP)に来たのかについてもお話します。 PS ここに記載されている内容はすべて主観的な意見です。誰もが開発の詳細とプロジェクト全体を考慮する必要がありますが、一般に、最良のアーキテクチャとは、存在しないアーキテクチャであり、チームにとって便利で効率的なスタイルでさまざまなアプローチを組み合わせたものです :D MonoBehavior とコンポーネント指向プログラミング (COP) 主に初心者が使用する最も基本的なアプローチから始めましょう。このアプローチが悪いと言っているのではなく、ほとんどの開発者が OOP (オブジェクト指向プログラミング) の観点から考えることに慣れているというだけです。 COP (コンポーネント指向プログラミング) を正しく使用するには、多少異なる考え方が必要です。同時に、Unity による MonoBehaviour ベースの COP の実装は理想的とは言えません。 したがって、基本的なアプローチは、ゲーム全体が MonoBehaviour コンポーネントを備えた GameObject 上に構築されることを意味します。これにより、さまざまなサブシステムを小さな部分に分割し、そこからゲームを構築できます。 { private Awake() { } } しかし、悪魔は細部に宿ります。このアプローチは、特に大規模なプロジェクトでのスケーリングの問題、不必要なリンク、Unity の内部での反射的な問題、Unity API との強い結びつきにつながり、特にクライアント上でコードを複製する場合に後で問題を引き起こす可能性があります。サーバ。 シングルトン 管理用のシングルトンが最初に思い浮かぶかもしれない、そして最も恐ろしいものです。本質的に、シングルトンは単一インスタンス内のシーンまたはプロジェクト全体に含まれるオブジェクトであり、ゲーム内で分離されたシステムをバインドして管理するために必要です。 シングルトンの簡単な例は、ジュニア開発者によく見られます。 public sealed class MySingleton { private static MySingleton _instance = null; private MySingleton() { } public static MySingleton Instance { get { if (_instance == null) { _instance = new MySingleton(); } return _instance; } } public void OperationX() { } } 小規模なプロジェクトでは、これによって不必要な問題が発生することはありません。プロジェクトが大規模になればなるほど、管理は難しくなります。メモリ リークは言うまでもなく、シングルトンはテストの構築、マルチスレッドの編成、および長すぎるスクリプト (初心者の開発者によく見られます) の問題を引き起こします。 シングルトン組織が通常どのように見えるかについて少し説明します (これは最も正しいとは言えません)。 では、シングルトンが悪かどうかはどうやってわかるのでしょうか? ゲーム内のすべてのロジックをバインドし、すべてを制御する場合 大量のリンクが直接そこに投げ込まれた場合 サイズが巨大化すると デバッグやメモリ管理にすでに慣れている場合、特にシングルトンがゲームオブジェクトの場合 シングルトンを使用する方が良いのはどのような場合ですか? 小規模なプロジェクト メモリ管理に問題がなく、直接リンクを転送する代わりにイベントを管理する場合 小規模システムの場合、たとえばオーディオの管理や分析システムのエンドポイント取得として 次に、DI コンテナについて説明します。 DIコンテナとクソZenject ああ、多くの人が Zenject を推進し、DI コンテナを使用して依存関係を実装しています。実際、多くの人がこの巨大なフレームワークを通常のシングルトンとして使用しています。 いつものように、私はこれをプロジェクトで見てきました。 本質的に、DI コンテナは、参照を配置し、最終オブジェクトの依存関係を解決するために必要です。同じ Zenject の最も単純な例: public class TestInstaller : MonoInstaller { public override void InstallBindings() { Container.Bind<string>().FromInstance("Hello World!"); Container.Bind<Greeter>().AsSingle().NonLazy(); } } public class Greeter { public Greeter(string message) { Debug.Log(message); } } このアプローチは良いですが、それは物事が複雑になり始めるまでに限られます。 DI コンテナは基本的にシングルトンと同じですが、コンテナ自体へのバインディングを作成するように改良されています。 非常に多くの場合、依存関係のバインディングを行う「キロメートル長」のインストーラー クラスが作成されます。 後から拡張性の高いアプローチになるものの、責任の分離がさらに進むため、新人にとっては理解が難しくなります。 コンテナとユビキタスなバインディングによりデバッグが困難 通常の DI コンテナをサービス ロケーターに変えるのは非常に簡単です もちろん、DI コンテナーはコードを適切に整理するための優れた方法ですが、物事がバカな状態に陥らないようにするには高度なトレーニングが必要です。 純粋な形式の MVC なぜ純粋な形で?十分に理解できるからです。プロジェクトを構成するためのコントローラー、モデル、ビューがあります。しかし、MVC がある限り、MVP、MVVM などと同じくらい多くのサブタイプが存在します。 ただし、今は、基本的ですべての例にアクセスできる例に焦点を当てます。 利点は明らかです。コントロール (ユーザー入力) をデータおよびビュー (ユーザーが画面上で見るもの) から分離します。通信は通常、イベント駆動型であり、アプリケーション コンテナーで初期化されます。これにより、多くの結束力が必要なくなります。私たちはイベントとのみ通信します。 ただし、ここにはいくつかの欠点があります。 プロジェクトが拡大するにつれて、インストール クラス アプリケーション (同じコンテナー) も大きくなります MVC の水平配置により、相互に緩やかに接続された膨大な数の異なるクラスが作成されます。 次に、別のアプローチについて説明します。 コンテナ内の MVC もう 1 つの考えられるシナリオは、MVC トライアドを DI コンテナーにリンクすることです。このようにして、アプリケーション間の接続をより適切に制御できますが、すべてを Service Locator に変えるのは非常に簡単です。 コントローラーをイベントにリンクするのではなく、コンテナーを通じてコントローラーを解決してからイベントを操作するため、アプローチが異なります。ただし、やはり、通常の DI コンテナと同様に、ここでも問題が発生しますが、発生の複雑さが増し、より多くのクラスが作成されます。ただし、表現、モデル、およびコントローラーを分離します。 HMVC/HMVP マゾヒストの私はこのアプローチがとても気に入っているので、ここでもう少し詳しくお話したいと思います。これにより、MVC のツリー状の分割が作成され、コード ベースが大幅に増加したにもかかわらず、いくつかの利点が得られます。 それでは、私が最も頻繁に使用する対話スキームを見てみましょう。 どのように機能するのでしょうか? 最初に、GameInstaller を使用して空のシーンを作成し、各シーンのコンテナーを個別にロードします。 GameInstaller クラス自体は、通常、大規模なシステム (オーディオ処理など) を担当するグローバル (トップレベル) トライアドを格納し、ゲームのライフサイクル全体の一般的なイベントを格納します。 次に、GameInstaller は必要なシーン コンテナをロードします。これにより、GameInstaller 内部のトップレベルのトライアド (たとえば、汎用プレーヤー コントローラー) が初期化され、次に、GameInstaller 内部の子コントローラー (たとえば、大砲コントローラー) が初期化されます。そしてさらに下降していきます。すべてのブランチの通信は、イベントと動的フィールドのみを介して行われます。 複雑そうに聞こえますが、実際はもっと簡単です。このアプローチにより、子要素間の適切な文脈上のつながりを維持しながら、すべてのトライアドを簡単に分離できます。各プレゼンターの初期化は、親からイベントのコンテキストを取得することから始まります。 このアプローチにはいくつかの利点があると思います。 プロジェクト シーンはほぼ即座にロードでき、ビューを含むオブジェクトはツリーがロードされるときにオンデマンドで初期化できます。イベントを送信する前に設定やゲーム ストアを含むビューをロードする必要がない場合は、イベント以外は何も保存しません。 個々のトライアドの緊密な構造化と分離 イベントによる結束力の低下 イベントを犠牲にしてダイナミックに コンテナ経由ではなくトライアドブランチでのデバッグがかなり簡単 いくつかの欠点があります。 20 個のトライアドのツリーにイベントを通す必要がある場合、かなり長い作業になりますが、このアプローチには適切な初期設計が必要です プロジェクトのコードベースは大規模ですが、適切に構造化されています ブランチを結合する必要がある場合、12 クラスにわたるイベントをスローするのは大きな課題になる可能性があります。 一般に、HMVC/HMVP は、サブシステムの分離性が高く、メモリ要件が高く、ゲーム リソースが必要な、よく組織化されたプロジェクトに必要です。ただし、他のアプローチよりも慣れるまでに時間がかかる可能性があります。 結論 プロジェクトを組織するためのそれぞれのアプローチには、それぞれの役割があります。すべては設計目標によって異なります。厳密なアーキテクチャと高速なメモリ処理が必要で、高速で動的なリソースが必要な場合は、HMVC を使用してください。手間をかけずにプロジェクトのプロトタイプを迅速に作成する必要がある場合は、すべてをシングルトンで記述してください。