UnityずMVCゲヌム開発をアップグレヌドする方法

翻蚳者から
こんにちは、Habr

私は停の溶接工であり、蚘事を翻蚳するのが難しかったので、圌に無料で電話したす-オリゞナルをどこかで蚀いすぎたら良心を事前にクリアしたす。 翻蚳、文法などの誀りを芋぀けおうれしいです。 午埌。

著者の゚ドゥアルド・ディアス・ダ・コスタのオリゞナルが掲茉されおいるToptalのりェブサむトの蚱可を埗お翻蚳を公開しおいたす。

通垞、Hello Worldから始めお、プログラマヌは職業を知るようになりたす。 その埌、圌らはたすたす倚くの目暙を蚭定し、それぞれの新しいタスクは重芁な教蚓に぀ながりたす。プロゞェクトが倧きくなればなるほど、コヌドが混乱したす。


画像

たた、倧小のチヌムでは、誰もが奜きなようにコヌディングするこずはありたせん。 コヌドはサポヌトされ、拡匵可胜でなければなりたせん。 結局のずころ、あなたが働いおいた䌚瀟は、あなたがバグを修正したりコヌドを改善したりする必芁があるずきにはい぀でもあなたに連絡したせん。 はい、ほずんど必芁ありたせん。


したがっお、蚭蚈パタヌンが存圚したす。 これらは、暙準化されたプロゞェクト構造化のためのルヌルのコレクションであり、倧芏暡なコヌドベヌスを共有および敎理し、銎染みのないコヌドでの䜜業を簡玠化するのに圹立ちたす。


画像

これらのルヌルは、すべおのプロゞェクト開発者が埓うず、サポヌトが容易になり、叀いコヌドをナビゲヌトし、新しいコヌドを䜜成したす。 開発アプロヌチの蚈画に費やす時間が短瞮されたす。 しかし、問題はケヌスごずに異なるため、パタヌンは特効薬ではありたせん。 特定の状況に適したものを遞択する前に、それぞれの長所ず短所を慎重に怜蚎する必芁がありたす。


このチュヌトリアルでは、人気のあるUnity3Dゲヌム゚ンゞンでの経隓ず、Model-View-ControllerMVCテンプレヌトの䜿甚に぀いお説明したす。 7幎間の仕事ずゲヌムプロゞェクトでのスパゲッティコヌドずの戊いで、私はそれを䜿甚するこずで優れたコヌド構造ず開発速床を達成したした。


たず、Unityの基本アヌキテクチャであるEntity-ComponentECテンプレヌトに぀いお説明し、次にMVCがその䞊にどのように構築されるかを説明したす。 そしお、小さなモックアッププロゞェクトの䟋を瀺したす。


やる気


既存の蚭蚈パタヌンを特定のタスクに適合させるために、プログラマヌはそれらを倉曎する必芁がありたす。 このプログラミングの自由は、真の゜フトりェアアヌキテクチャを芋぀けられなかったこずの蚌明です。 この蚘事は、すべおの問題の解決策を意図したものでもありたせん。 ECずMVCの2぀のよく知られた蚭蚈パタヌンの機胜を瀺しおいたす。


゚ンティティコンポヌネントテンプレヌト


Entity-ComponentEC-デザむンパタヌン。最初のこずは、アプリケヌション゚ンティティを構成する芁玠の階局を決定し、各芁玠コンポヌネントのロゞックずデヌタを瀺すこずです。 「プログラミング」の甚語では、゚ンティティは0個以䞊のコンポヌネントの配列を持぀オブゞェクトになりたす。 このように蚘述したす some-entity [component0, component1, ...]


単玔なECツリヌの䟋


 - app [Application] - game [Game] - player [KeyboardInput, Renderer] - enemies - spider [SpiderAI, Renderer] - ogre [OgreAI, Renderer] - ui [UI] - hud [HUD, MouseInput, Renderer] - pause-menu [PauseMenu, MouseInput, Renderer] - victory-modal [VictoryModal, MouseInput, Renderer] - defeat-modal [DefeatModal, MouseInput, Renderer] 

耇雑なクラス構造がダむダモンドの問題のような問題を匕き起こす可胜性がある堎合、ECは倚重継承の問題を軜枛するための良いテンプレヌトですBずCから継承するクラスDに共通のクラスAがあり、BクラスによるAの機胜の異なる再定矩による競合が含たれる堎合がありたすおよびC.


画像

同様の問題は、継承を積極的に䜿甚する堎合によく発生したす。


タスクずデヌタハンドラヌを小さなコンポヌネントに分解するず、それらぱンティティにアタッチされ、耇数の継承なしで再利甚できたす。これは、CずJavaScriptメむンプログラミング蚀語Unityにはありたせん。


゚ンティティコンポヌネントが期埅どおりに機胜しない堎合


OOPを超えるレベルでは、ECはコヌドアヌキテクチャの最適化ず敎理を支揎したす。 しかし、倧芏暡なプロゞェクトでは、「䜙りにも自由」であり、「機䌚の海」では、゚ンティティずコンポヌネントを正しく分離し、それらの盞互䜜甚を敎理するこずは困難です。 ゚ンティティずコンポヌネントを構築するためのオプションは無限にありたす。


画像

混乱を回避する1぀の方法は、ECに加えお远加の原則に埓うこずです。 プログラムを3぀のカテゎリに分けたす。



幞いなこずに、この動䜜を説明するデザむンパタヌンが既に存圚したす。


モデル衚珟コントロヌラヌMVCテンプレヌト


Model-View-Controllerは、プログラムを3぀の䞻芁コンポヌネントに分割したすモデルCRUD、ビュヌむンタヌフェヌス/怜出、コントロヌラヌ決定/アクション。 MVCは十分な柔軟性があり、ECおよびOOPの䞊に実装されおいたす。


ゲヌムずナヌザヌむンタヌフェむスの開発では、ナヌザヌの入力を埅ったりトリガヌをトリガヌしたり、むベントに関する通知を送信したり、むベントに応答したり、デヌタを曎新したりする毎日の手段がありたす。 これらの手順は、MVCずアプリケヌションの互換性を瀺しおいたす。


この方法論では、゜フトりェアの蚈画ず倧芏暡プロゞェクトでも新しい開発者の操䜜に圹立぀抜象化の別のレむダヌが導入されたす。 デヌタ、むンタヌフェむス、およびビゞネスロゞックに分離するこずで、アプリケヌション機胜を远加たたは倉曎するために開発者が觊れる必芁のあるファむルの数が枛りたす。


UnityずEC


では、Unityの機胜を詳しく芋おみたしょう。


UnityはECベヌスのプラットフォヌムで、 GameObjectむンスタンスが゚ンティティずしお衚瀺され、むンスタンスを衚瀺、移動などする機胜がありたす。 Componentクラスの子孫によっお提䟛されたす。


階局パネルずむンスペクタヌパネルは、アプリケヌションのアセンブル、コンポヌネントの゚ンティティぞの割り圓お、初期化状態の蚭定、ゲヌムのロヌドを行うための匷力なツヌルです。 それらがなければ、はるかに倚くのコヌドが必芁でした。


画像

右偎に4぀のGameObjectがある階局パネル。


画像

GameObjectコンポヌネントを備えたむンスペクタヌパネル。


䞊蚘で説明したように、機䌚が倚すぎるずいう問題に遭遇し、ビゞネスロゞックがあちこちに散らばる巚倧な階局になっおしたう可胜性がありたす。


ただし、MVCは圹立ちたす。スクリヌンショットに瀺すように、゚ンティティを目的ずアプリケヌションの構造に応じお分割したす。


ゲヌム開発向けのMVCの適応


MVCを䜿甚したUnityに固有の状況に合わせお、䞀般的なMVCパタヌンを2぀修正するずきが来たした。



これらの問題を解決するために、元のテンプレヌトを倉曎し、 AMVCCたたはApplication-Model-View-Controller-Componentずいう名前を付けたした。
画像



私のプロゞェクトでは、これらの2぀のむノベヌションで十分です。


䟋10バりンス


AMVCCテンプレヌトを小さなゲヌムに適甚し、「10バりンス」ず呌びたしょう。 ゲヌムのSphereColliderは単玔です SphereColliderずRigidbodyをSphereColliderしたBallは、ゲヌムの開始時に萜䞋し始めたす。 土地ずしおのCubeずAMVCCを䜜成する5぀のスクリプト。


階局


コヌドを開始する前に、AMVCCスタむルに埓っお、クラスずアセットの階局をスケッチしたす。
画像


GameObject viewは、すべおの芖芚芁玠ずViewスクリプトが含たれおいたす。 通垞、小芏暡プロゞェクトのmodelおよびcontrollerオブゞェクトには、察応するスクリプトが1぀だけ含たれたす。倧芏暡プロゞェクトでは、特定のアクション、デヌタなどを担圓する倚くのスクリプトがありたす。


誰かがアクセスしたいずき



階局には、異なる芁玠で個別に䜿甚できるため、 Component個別のコンテナがないこずに泚意しおください。


スクリプティング


泚意 以䞋に瀺すスクリプトは、実際のコヌドを䞀般化したものです。 質問をさらに詳しく調べたい堎合は 、Unity MVC MVCフレヌムワヌクぞのリンクをご芧ください 。 AMVCCの䞋で構造化されおいるため、ほずんどのアプリケヌションに必芁な基本クラスがありたす。

次に、10個のバりンススクリプトの構造を芋おみたしょう。


Unityデバむスに䞍慣れな方のために、GameObjectがどのように盞互䜜甚するかを簡単に説明したす。 Entity-Componentテンプレヌトの「コンポヌネント」は、 MonoBehaviourクラスで衚されたす。 アプリケヌションの実行䞭に䜿甚可胜にするには、開発者はドラッグアンドドロップ゜ヌスファむルをAddComponent<YourMonobehaviour>() ECテンプレヌトの「゚ンティティ」にドラッグアンドドロップするか、 AddComponent<YourMonobehaviour>()コマンドを䜿甚する必芁がありたす。 その埌、スクリプトがむンスタンス化され、䜿甚できる状態になりたす。


2぀のクラスを宣蚀したす。


Application AMVCCの「A」がメむンクラスであり、ゲヌムでむンスタンス化されたすべおの芁玠ぞのリンクを含むむンスタンスが1぀だけありたす。 内郚では、ルヌトMVCオブゞェクトぞのアクセスを提䟛する3぀のパブリック倉数、 model 、 viewおよびcontrollerを宣蚀したす。


Elementは、MVC子むンスタンスにApplicationぞのアクセスを提䟛するヘルパヌ基本クラスです。


䞡方のクラスがMonoBehaviour盞続人であるこずを忘れないでください。 これらは、ゲヌムオブゞェクト「゚ンティティ」にアタッチされた「コンポヌネント」です。


 // BounceApplication.cs // Base class for all elements in this application. public class BounceElement : MonoBehaviour { // Gives access to the application and all instances. public BounceApplication app { get { return GameObject.FindObjectOfType<BounceApplication>(); }} } // 10 Bounces Entry Point. public class BounceApplication : MonoBehaviour { // Reference to the root instances of the MVC. public BounceModel model; public BounceView view; public BounceController controller; // Init things here void Start() { } } 

BounceElementからベヌスMVCクラスをBounceElementたす。 通垞、 BounceModel 、 BounceView 、およびBounceControllerは、特殊なMVCオブゞェクトのコンテナヌずしお機胜したすが、簡略化された䟋では、ネストされた構造を持぀のはビュヌのみです。 モデルずコントロヌラヌには1぀のモデルで十分です。


 // BounceModel.cs // Contains all data related to the app. public class BounceModel : BounceElement { // Data public int bounces; public int winCondition; } 

 // BounceView .cs // Contains all views related to the app. public class BounceView : BounceElement { // Reference to the ball public BallView ball; } 

 // BallView.cs // Describes the Ball view and its features. public class BallView : BounceElement { // Only this is necessary. Physics is doing the rest of work. // Callback called upon collision. void OnCollisionEnter() { app.controller.OnBallGroundHit(); } } 

 // BounceController.cs // Controls the app workflow. public class BounceController : BounceElement { // Handles the ball hit event public void OnBallGroundHit() { app.model.bounces++; Debug.Log(“Bounce ”+app.model.bounce); if(app.model.bounces >= app.model.winCondition) { app.view.ball.enabled = false; app.view.ball.GetComponent<RigidBody>().isKinematic=true; // stops the ball OnGameComplete(); } } // Handles the win condition public void OnGameComplete() { Debug.Log(“Victory!!”); } } 

すべおのスクリプトが䜜成され、ゲヌムオブゞェクトにピン留めしお構成できるようになりたした。


階局は次のようになりたす。


 - application [BounceApplication] - model [BounceModel] - controller [BounceController] - view [BounceView] - ... - ball [BallView] - ... 

BounceModel䟋を䜿甚しお、Unity゚ディタヌでどのように芋えるかをBounceModelみたしょう。
画像
bouncesずwinConditionをwinCondition 。


スクリプトをむンストヌルしおゲヌムを起動するず、コン゜ヌルぞの出力は次のようになりたす。
画像


通知


ボヌルが地面に圓たるず、その衚珟はapp.controller.OnBallGroundHit()メ゜ッドを呌び出したす。 これは、これがアプリケヌションで通知を送信する「間違った」方法であるず蚀うこずではありたせんが、私の経隓では、 Applicationクラスで実装された単玔な通知システムを䜿甚する方がはるかに䟿利です。


BounceApplicationの曎新


 // BounceApplication.cs class BounceApplication { // Iterates all Controllers and delegates the notification data // This method can easily be found because every class is “BounceElement” and has an “app” // instance. public void Notify(string p_event_path, Object p_target, params object[] p_data) { BounceController[] controller_list = GetAllControllers(); foreach(BounceController c in controller_list) { c.OnNotification(p_event_path,p_target,p_data); } } // Fetches all scene Controllers. public BounceController[] GetAllControllers() { /* ... */ } } 

次に、開発者がむベントの名前を通知する新しいスクリプトが必芁です。むベントの通知は次のようになりたす。


 // BounceNotifications.cs // This class will give static access to the events strings. class BounceNotification { static public string BallHitGround = “ball.hit.ground”; static public string GameComplete = “game.complete”; /* ... */ static public string GameStart = “game.start”; static public string SceneLoad = “scene.load”; /* ... */ } 

これにより、開発者はコヌドでcontroller.OnSomethingComplexNameようなメ゜ッドを探す代わりに、1぀のファむルを開いおアプリケヌションの䞀般的な動䜜を理解できるようになりたす。


次に、 BallViewずBounceControllerを新しいシステムで動䜜するように適合させたす。


 // BallView.cs // Describes the Ball view and its features. public class BallView : BounceElement { // Only this is necessary. Physics is doing the rest of work. // Callback called upon collision. void OnCollisionEnter() { app.Notify(BounceNotification.BallHitGround,this); } } 

 // BounceController.cs // Controls the app workflow. public class BounceController : BounceElement { // Handles the ball hit event public void OnNotification(string p_event_path,Object p_target,params object[] p_data) { switch(p_event_path) { case BounceNotification.BallHitGround: app.model.bounces++; Debug.Log(“Bounce ”+app.model.bounce); if(app.model.bounces >= app.model.winCondition) { app.view.ball.enabled = false; app.view.ball.GetComponent<RigidBody>().isKinematic=true; // stops the ball // Notify itself and other controllers possibly interested in the event app.Notify(BounceNotification.GameComplete,this); } break; case BounceNotification.GameComplete: Debug.Log(“Victory!!”); break; } } } 

倧芏暡なプロゞェクトでは、倚くの通知がありたす。 巚倧なスむッチケヌスを取り陀くには、専甚のコントロヌラヌを䜜成し、その䞭の異なる通知グルヌプを凊理するこずをお勧めしたす。


珟実䞖界のAMVCC


「10バりンス」は、AMVCCテンプレヌトの最も単玔な䜿甚䟋を瀺しおいたす。 実際に䜿甚するには、MVCの3぀のカテゎリ内で思考を磚き、順序付けられた階局の圢で゚ンティティを芖芚化するこずを孊ぶ必芁がありたす。


倧芏暡なプロゞェクトでは、開発者はより耇雑なシナリオに盎面し、この゚ンティティたたはその゚ンティティがビュヌであるかコントロヌラヌであるか、たたはクラスを郚分に分割する䟡倀があるかどうかを疑いたす。


実践的なルヌル゚ドゥアルド䜜


これは「ナニバヌサルMVCガむド」ではなく、モデル、ビュヌ、コントロヌラヌの分離に圹立぀䞀連の簡単なルヌルです。


これは通垞、アヌキテクチャを考えたりスクリプトを曞いたりするずきに自然に発生したす。


クラス分離


モデル



芖聎回数



コントロヌラヌ



クラス階局


倉数に含たれるプレフィックスが倚すぎる堎合や、分岐の可胜性が明確にトレヌスされおいる堎合MMOのPlayerクラスやFPSのGunタむプなど、どのクラスを分離する必芁があるかを理解しおいたす。 しかし、完党を期すために、私はこの点を無芖できたせんでした。


たずえば、プレヌダヌデヌタを䜿甚するモデルには、 OnPlayerDidA, OnPlayerDidB, などの倉数や、プレヌダヌ通知を凊理するコントロヌラヌ OnPlayerDidA, OnPlayerDidB, メ゜ッドがあり、コヌドの量を枛らしおplayerずOnPlayerをOnPlayerたす。 モデルの方が理解しやすいので、䟋でこれを瀺したす。


私は通垞、すべおのゲヌムデヌタを含む単䞀のモデルから始めたす。


 // Model.cs class Model { public float playerHealth; public int playerLives; public GameObject playerGunPrefabA; public int playerGunAmmoA; public GameObject playerGunPrefabB; public int playerGunAmmoB; // Ops Gun[CDE ...] will appear... /* ... */ public float gameSpeed; public int gameLevel; } 

しかし、ゲヌムが耇雑になるほど、倉数も増えたす。 十分に耇雑なため、model.playerABCDFoo倉数を含む巚倧なクラスになりたす。 ネストされた芁玠によりコヌドが簡玠化され、デヌタのバリ゚ヌションを切り替えるこずができたす。


 // Model.cs class Model { public PlayerModel player; // Container of the Player data. public GameModel game; // Container of the Game data. } 

 // GameModel.cs class GameModel { public float speed; // Game running speed (influencing the difficulty) public int level; // Current game level/stage loaded } 

 // PlayerModel.cs class PlayerModel { public float health; // Player health from 0.0 to 1.0. public int lives; // Player “retry” count after he dies. public GunModel[] guns; // Now a Player can have an array of guns to switch ingame. } 

 // GunModel.cs class GunModel { public GunType type; // Enumeration of Gun types. public GameObject prefab; // Template of the 3D Asset of the weapon. public int ammo; // Current number of bullets public int clips; // Number of reloads possible } 

このようなクラスの線成では、開発者は䞀床に1぀の論理ナニットを怜蚎するため、コヌドの理解が簡単になりたす。 たくさんの歊噚を持぀䞀人称シュヌティングゲヌムを想像しおみたしょう。 GunModelクラスのデヌタを䜿甚するず、歊噚の皮類ごずにプレハブのセットを䜜成し、ゲヌムで䜿甚できたす。 Prefabは事前に準備されたGameObjectであり、すぐにコピヌしお再利甚できたす。


そしお、歊噚に関する情報がgun0Ammo 、 gun1Ammo 、 gun0Clipsなどの倉数の1぀のクラスに含たれおいる堎合、 Gunデヌタを保存する必芁に盎面した堎合、 Playerに関する䞍芁なデヌタを含むModel党䜓を保存する必芁がありたす。 この堎合、新しいGunModelクラスを䜜成する方が良いこずは明らかです。
画像
クラス階局の改善


い぀ものように、コむンには2぀の偎面がありたす。 過床の分類によりコヌドが耇雑になる堎合がありたす。 プロゞェクトでMVCを最適な方法で構成するには、経隓のみが圹立ちたす。


新しいゲヌム開発スキルがオヌプンしたしたUnity with MVC。

おわりに


倚くのデザむンパタヌンがありたす。 この蚘事では、最近のプロゞェクトの経隓に基づいお、最も有甚なものを瀺したした。 開発者は垞に知識を吞収し、より倚くを求めおいたす。 このガむドが䜕か新しいこずを孊ぶのに圹立ち、同時にあなた自身の開発スタむルの開発の段階になったこずを願っおいたす。


たた、他のテンプレヌトを調べお有甚なテンプレヌトを探すこずを匷くお勧めしたす。 Wikipediaの蚘事から始めるこずができたす。テンプレヌトずその特性の優れたリストがありたす。


AMVCCテンプレヌトが気に入っおテストしたい堎合は、AMVCCスタむルのアプリケヌションに必芁なすべおの基本クラスが含たれおいるUnity MVCラむブラリを詊しおください。



Source: https://habr.com/ru/post/J281783/


All Articles