Einheit mit MVC: Wie Sie Ihre Spieleentwicklung verbessern

Veröffentlicht: 2022-03-11

Programmierer, die zum ersten Mal programmieren, beginnen normalerweise mit dem klassischen Hello World Programm. Von da an werden immer größere Aufgaben folgen. Jede neue Herausforderung führt zu einer wichtigen Lektion:

Je größer das Projekt, desto größer die Spaghetti.

Dass man in großen oder kleinen Teams nicht leichtsinnig machen kann, was einem gefällt, ist schnell klar. Der Code muss gepflegt werden und kann lange Bestand haben. Unternehmen, für die Sie gearbeitet haben, können nicht einfach Ihre Kontaktinformationen nachschlagen und Sie jedes Mal fragen, wenn sie die Codebasis reparieren oder verbessern möchten (und Sie das auch nicht wollen).

Aus diesem Grund gibt es Softwaredesignmuster; Sie legen einfache Regeln fest, um die Gesamtstruktur eines Softwareprojekts zu diktieren. Sie helfen einem oder mehreren Programmierern, Kernstücke eines großen Projekts zu trennen und auf standardisierte Weise zu organisieren, wodurch Verwirrung vermieden wird, wenn ein unbekannter Teil der Codebasis angetroffen wird.

Wenn diese Regeln von allen befolgt werden, kann Legacy-Code besser gewartet und navigiert und neuer Code schneller hinzugefügt werden. Es wird weniger Zeit für die Planung der Entwicklungsmethodik aufgewendet. Da es Probleme nicht nur in einer Richtung gibt, gibt es kein Patentrezept für das Design. Man muss die Stärken und Schwächen jedes Musters sorgfältig abwägen und die beste Lösung für die jeweilige Herausforderung finden.

In diesem Tutorial werde ich meine Erfahrungen mit der beliebten Spieleentwicklungsplattform Unity und dem Model-View-Controller (MVC)-Muster für die Spieleentwicklung erzählen. In meinen sieben Jahren der Entwicklung, nachdem ich mit meinem fairen Anteil an Spieleentwickler-Spaghetti gerungen habe, habe ich mit diesem Entwurfsmuster eine großartige Codestruktur und Entwicklungsgeschwindigkeit erreicht.

Ich beginne mit der Erläuterung der Basisarchitektur von Unity, dem Entity-Component-Muster. Dann erkläre ich weiter, wie MVC darauf passt, und verwende ein kleines Scheinprojekt als Beispiel.

Motivation

In der Softwareliteratur finden wir eine große Anzahl von Entwurfsmustern. Obwohl sie eine Reihe von Regeln haben, werden Entwickler in der Regel ein wenig die Regeln biegen, um das Muster besser an ihr spezifisches Problem anzupassen.

Diese „Freiheit des Programmierens“ beweist, dass wir noch keine einzige, endgültige Methode zum Entwerfen von Software gefunden haben. Dieser Artikel soll also nicht die ultimative Lösung für Ihr Problem sein, sondern vielmehr die Vorteile und Möglichkeiten zweier bekannter Muster aufzeigen: Entity-Component und Model-View-Controller.

Das Entity-Component-Pattern

Entity-Component (EC) ist ein Entwurfsmuster, bei dem wir zuerst die Hierarchie der Elemente definieren, aus denen die Anwendung besteht (Entities), und später definieren wir die Features und Daten, die jedes enthalten wird (Components). Etwas „programmiererischer“ ausgedrückt kann eine Entität ein Objekt mit einem Array von 0 oder mehr Komponenten sein. Lassen Sie uns eine Entität wie folgt darstellen:

 some-entity [component0, component1, ...]

Hier ist ein einfaches Beispiel für einen EC-Baum.

 - 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 ist ein gutes Muster, um die Probleme der Mehrfachvererbung zu lindern, bei denen eine komplexe Klassenstruktur Probleme wie das Rautenproblem einführen kann, bei dem eine Klasse D, die zwei Klassen B und C mit derselben Basisklasse A erbt, Konflikte verursachen kann, weil wie B und C modifizieren die Merkmale von A unterschiedlich.

BILD: DIAMANTPROBLEM

Diese Art von Problemen kann in der Spieleentwicklung üblich sein, wo Vererbung oft extensiv verwendet wird.

Indem die Funktionen und Datenhandler in kleinere Komponenten zerlegt werden, können sie in verschiedenen Entitäten angehängt und wiederverwendet werden, ohne auf mehrere Vererbungen angewiesen zu sein (was übrigens nicht einmal eine Funktion von C# oder Javascript ist, den Hauptsprachen, die von Unity verwendet werden ).

Wo die Entitätskomponente zu kurz kommt

EC liegt eine Ebene über OOP und hilft bei der Defragmentierung und besseren Organisation Ihrer Codearchitektur. In großen Projekten sind wir jedoch immer noch „zu frei“ und können uns in einem „Feature-Ozean“ wiederfinden, in dem wir Schwierigkeiten haben, die richtigen Entitäten und Komponenten zu finden oder herauszufinden, wie sie interagieren sollten. Es gibt unendlich viele Möglichkeiten, Entitäten und Komponenten für eine bestimmte Aufgabe zusammenzustellen.

BILD: EC FEATURE OZEAN

Eine Möglichkeit, ein Durcheinander zu vermeiden, besteht darin, einige zusätzliche Richtlinien über Entity-Component aufzuerlegen. Eine Art, wie ich zum Beispiel über Software nachdenke, ist, sie in drei verschiedene Kategorien zu unterteilen:

  • Einige handhaben die Rohdaten, sodass sie erstellt, gelesen, aktualisiert, gelöscht oder durchsucht werden können (dh das CRUD-Konzept).
  • Andere implementieren die Schnittstelle, mit der andere Elemente interagieren können, um Ereignisse zu erkennen, die sich auf ihren Bereich beziehen, und Benachrichtigungen auszulösen, wenn sie auftreten.
  • Schließlich sind einige Elemente dafür verantwortlich, diese Benachrichtigungen zu erhalten, Entscheidungen zur Geschäftslogik zu treffen und zu entscheiden, wie die Daten manipuliert werden sollen.

Glücklicherweise haben wir bereits ein Muster, das sich genau so verhält.

Das Model-View-Controller (MVC)-Muster

Das Model-View-Controller-Muster (MVC) teilt die Software in drei Hauptkomponenten auf: Modelle (Data CRUD), Ansichten (Schnittstelle/Erkennung) und Controller (Entscheidung/Aktion). MVC ist flexibel genug, um sogar auf ECS oder OOP implementiert zu werden.

Die Spiel- und UI-Entwicklung hat den üblichen Arbeitsablauf, bei dem auf eine Benutzereingabe oder eine andere auslösende Bedingung gewartet wird, an geeigneter Stelle eine Benachrichtigung über diese Ereignisse gesendet wird, entschieden wird, was als Reaktion zu tun ist, und die Daten entsprechend aktualisiert werden. Diese Aktionen zeigen deutlich die Kompatibilität dieser Anwendungen mit MVC.

Diese Methodik führt eine weitere Abstraktionsschicht ein, die bei der Softwareplanung hilft und es auch neuen Programmierern ermöglicht, selbst in einer größeren Codebasis zu navigieren. Durch die Aufteilung des Denkprozesses in Daten, Schnittstellen und Entscheidungen können Entwickler die Anzahl der Quelldateien reduzieren, die durchsucht werden müssen, um Funktionen hinzuzufügen oder zu korrigieren.

Einheit und EG

Schauen wir uns zunächst einmal genauer an, was Unity uns im Voraus bietet.

Unity ist eine EC-basierte Entwicklungsplattform, bei der alle Entitäten Instanzen von GameObject sind und die Funktionen, die sie „sichtbar“, „beweglich“, „interagierbar“ usw. machen, von Klassen bereitgestellt werden, die Component erweitern.

Das Hierarchie-Panel und das Inspektor-Panel des Unity-Editors bieten eine leistungsstarke Möglichkeit, Ihre Anwendung zusammenzustellen, Komponenten anzuhängen, ihren Anfangszustand zu konfigurieren und Ihr Spiel mit viel weniger Quellcode als normalerweise zu booten.

SCREENSHOT: HIERARCHIE-PANEL
Hierarchiebereich mit vier GameObjects auf der rechten Seite

SCREENSHOT: INSPEKTOR-PANEL
Inspector Panel mit den Komponenten eines GameObjects

Wie wir bereits besprochen haben, können wir jedoch auf das Problem „zu viele Funktionen“ stoßen und uns in einer gigantischen Hierarchie mit überall verstreuten Funktionen wiederfinden, was das Leben eines Entwicklers erheblich erschwert.

Wenn wir nach MVC denken, können wir stattdessen damit beginnen, die Dinge nach ihrer Funktion zu unterteilen und unsere Anwendung wie im folgenden Beispiel zu strukturieren:

SCREENSHOT: UNITY MVC-BEISPIELSTRUKTUR

Anpassen von MVC an eine Spielentwicklungsumgebung

Nun möchte ich zwei kleine Modifikationen am generischen MVC-Muster einführen, die helfen, es an einzigartige Situationen anzupassen, auf die ich beim Erstellen von Unity-Projekten mit MVC gestoßen bin:

  1. Die MVC-Klassenreferenzen werden leicht im Code verstreut. - Innerhalb von Unity müssen Entwickler Instanzen normalerweise per Drag-and-Drop verschieben, um sie zugänglich zu machen, oder sie durch umständliche Suchanweisungen wie GetComponent( ... ) erreichen. - Die Hölle mit verlorenen Referenzen entsteht, wenn Unity abstürzt oder ein Fehler alle gezogenen Referenzen verschwinden lässt. - Dies macht es erforderlich, ein einziges Root-Referenzobjekt zu haben, über das alle Instanzen in der Anwendung erreicht und wiederhergestellt werden können.
  2. Einige Elemente kapseln allgemeine Funktionen, die in hohem Maße wiederverwendbar sein sollten und die natürlich nicht in eine der drei Hauptkategorien Model, View oder Controller fallen. Diese nenne ich gerne einfach Komponenten . Sie sind ebenfalls „Komponenten“ im Sinne von Entity-Component, fungieren jedoch lediglich als Helfer im MVC-Framework. - Zum Beispiel eine Rotator Komponente, die Dinge nur um eine bestimmte Winkelgeschwindigkeit dreht und nichts benachrichtigt, speichert oder entscheidet.

Um diese beiden Probleme zu lösen, habe ich mir ein modifiziertes Muster ausgedacht, das ich AMVCC oder Application-Model-View-Controller-Component nenne.

BILD: AMVCC-DIAGRAMM

  • Anwendung – Ein einziger Einstiegspunkt zu Ihrer Anwendung und Container aller kritischen Instanzen und anwendungsbezogenen Daten.
  • MVC - Das sollten Sie inzwischen wissen. :)
  • Komponente - Kleines, übersichtliches Skript, das wiederverwendet werden kann.

Diese beiden Modifikationen haben meine Anforderungen für alle Projekte erfüllt, in denen ich sie verwendet habe.

Beispiel: 10 Bounces

Schauen wir uns als einfaches Beispiel ein kleines Spiel namens 10 Bounces an, bei dem ich die Kernelemente des AMVCC-Musters verwenden werde.

Der Spielaufbau ist einfach: Ein Ball mit einem SphereCollider und einem Rigidbody (der nach „Play“ zu fallen beginnt), ein Cube als Boden und 5 Skripte, um das AMVCC zu bilden.

Hierarchie

Bevor ich Skripte schreibe, beginne ich normalerweise mit der Hierarchie und erstelle einen Überblick über meine Klasse und meine Assets. Immer diesem neuen AMVCC-Stil folgen.

SCREENSHOT: AUFBAU DER HIERARCHIE

Wie wir sehen können, enthält das view GameObject alle visuellen Elemente und auch solche mit anderen View -Skripten. Die model und controller -GameObjects enthalten für kleine Projekte normalerweise nur ihre jeweiligen Skripte. Bei größeren Projekten enthalten sie GameObjects mit spezifischeren Skripten.

Wenn jemand, der in Ihrem Projekt navigiert, darauf zugreifen möchte:

  • Daten: Gehen Sie zu application > model > ...
  • Logik/Workflow: Gehen Sie zu application > controller > ...
  • Rendering/Schnittstelle/Erkennung: Gehen Sie zu application > view > ...

Wenn alle Teams diese einfachen Regeln befolgen, sollten Legacy-Projekte kein Problem darstellen.

Beachten Sie, dass es keinen Component gibt, da sie, wie wir besprochen haben, flexibler sind und nach Belieben des Entwicklers an verschiedene Elemente angehängt werden können.

Skripterstellung

Hinweis: Die unten gezeigten Skripte sind abstrakte Versionen realer Implementierungen. Eine detaillierte Implementierung würde dem Leser nicht viel nützen. Wenn Sie jedoch mehr erfahren möchten, finden Sie hier den Link zu meinem persönlichen MVC-Framework für Unity, Unity MVC. Sie werden Kernklassen finden, die das strukturelle AMVCC-Framework implementieren, das für die meisten Anwendungen benötigt wird.

Werfen wir einen Blick auf die Struktur der Skripte für 10 Bounces .

Bevor wir beginnen, lassen Sie uns für diejenigen, die mit dem Arbeitsablauf von Unity nicht vertraut sind, kurz erläutern, wie Skripts und GameObjects zusammenarbeiten. In Unity werden „Komponenten“ im Sinne von Entitätskomponenten durch die MonoBehaviour -Klasse dargestellt. Damit eine zur Laufzeit existiert, sollte der Entwickler ihre Quelldatei entweder per Drag-and-Drop in ein GameObject (das die „Entität“ des Entity-Component-Musters ist) ziehen oder den Befehl AddComponent<YourMonobehaviour>() verwenden. Danach wird das Skript instanziiert und ist während der Ausführung einsatzbereit.

Zunächst definieren wir die Anwendungsklasse (das „A“ in AMVCC), die die Hauptklasse sein wird, die Verweise auf alle instanziierten Spielelemente enthält. Wir erstellen auch eine Hilfsbasisklasse namens Element , die uns Zugriff auf die Instanz der Anwendung und die MVC-Instanzen ihrer untergeordneten Elemente gewährt.

Lassen Sie uns vor diesem Hintergrund die Application (das „A“ in AMVCC) definieren, die eine eindeutige Instanz haben wird. Darin geben uns die drei Variablen model , view und controller während der Laufzeit Zugriffspunkte für alle MVC-Instanzen. Diese Variablen sollten MonoBehaviour s mit public Verweisen auf die gewünschten Skripte sein.

Dann erstellen wir auch eine Hilfsbasisklasse namens Element , die uns Zugriff auf die Instanz der Anwendung gewährt. Dieser Zugriff ermöglicht es jeder MVC-Klasse, sich gegenseitig zu erreichen.

Beachten Sie, dass beide Klassen MonoBehaviour erweitern. Sie sind „Komponenten“, die an GameObject „Entities“ angehängt werden.

 // 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() { } }

Aus BounceElement können wir die MVC-Kernklassen erstellen. Die BounceModel , BounceView und BounceController fungieren normalerweise als Container für spezialisiertere Instanzen, aber da dies ein einfaches Beispiel ist, hat nur die Ansicht eine verschachtelte Struktur. Das Modell und der Controller können jeweils in einem Skript ausgeführt werden:

 // 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!!”); } }

Nachdem alle Skripte erstellt wurden, können wir mit dem Anhängen und Konfigurieren fortfahren.

Das Hierarchie-Layout sollte wie folgt aussehen:

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

Am Beispiel des BounceModel können wir sehen, wie es im Editor von Unity aussieht:

SCREENSHOT: BounceModel IN INSPECTOR
BounceModel mit den Feldern bounces und winCondition .

Wenn alle Skripte gesetzt sind und das Spiel läuft, sollten wir diese Ausgabe im Konsolenbereich erhalten.

SCREENSHOT: KONSOLENAUSGANG

Benachrichtigungen

Wie im obigen Beispiel gezeigt, führt seine Ansicht app.controller.OnBallGroundHit() aus, was eine Methode ist, wenn der Ball den Boden berührt. Es ist keineswegs „falsch“, dies für alle Benachrichtigungen in der Anwendung zu tun. Meiner Erfahrung nach habe ich jedoch mit einem einfachen Benachrichtigungssystem, das in der AMVCC-Anwendungsklasse implementiert ist, bessere Ergebnisse erzielt.

Um dies zu implementieren, aktualisieren wir das Layout der BounceApplication wie folgt:

 // 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() { /* ... */ } }

Als Nächstes benötigen wir ein neues Skript, in dem alle Entwickler die Namen der Benachrichtigungsereignisse hinzufügen, die während der Ausführung versendet werden können.

 // 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”; /* ... */ }

Es ist leicht zu erkennen, dass auf diese Weise die Lesbarkeit des Codes verbessert wird, da Entwickler nicht den gesamten Quellcode nach controller.OnSomethingComplexName -Methoden durchsuchen müssen, um zu verstehen, welche Art von Aktionen während der Ausführung stattfinden können. Indem nur eine Datei überprüft wird, ist es möglich, das Gesamtverhalten der Anwendung zu verstehen.

Jetzt müssen wir nur noch BallView und BounceController anpassen, um mit diesem neuen System umzugehen.

 // 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; } } }

Größere Projekte haben viele Benachrichtigungen. Um also eine große Switch-Case-Struktur zu vermeiden, ist es ratsam, verschiedene Controller zu erstellen und diese mit unterschiedlichen Benachrichtigungsbereichen zu behandeln.

AMVCC in der realen Welt

Dieses Beispiel hat einen einfachen Anwendungsfall für das AMVCC-Muster gezeigt. Ihre Denkweise in Bezug auf die drei Elemente von MVC anzupassen und zu lernen, die Entitäten als geordnete Hierarchie zu visualisieren, sind die Fähigkeiten, die verbessert werden sollten.

In größeren Projekten werden Entwickler mit komplexeren Szenarien und Zweifeln konfrontiert, ob etwas ein View oder ein Controller sein sollte oder ob eine bestimmte Klasse gründlicher in kleinere getrennt werden sollte.

Faustregeln (von Eduardo)

Es gibt nirgendwo einen „Universal Guide for MVC Sorting“. Aber es gibt einige einfache Regeln, die ich normalerweise befolge, um zu bestimmen, ob ich etwas als Modell, Ansicht oder Controller definieren und wann ich eine bestimmte Klasse in kleinere Teile aufteilen soll.

Normalerweise geschieht dies organisch, während ich über die Softwarearchitektur nachdenke oder während des Scriptings.

Klassensortierung

Modelle

  • Halten Sie die Kerndaten und den Zustand der Anwendung fest, z. B. die health des Spielers oder die ammo .
  • Serialisieren, deserialisieren und/oder konvertieren Sie zwischen Typen.
  • Daten laden/speichern (lokal oder im Web).
  • Benachrichtigen Sie die Controller über den Fortschritt der Operationen.
  • Speichern Sie den Spielstatus für die endliche Zustandsmaschine des Spiels.
  • Greifen Sie niemals auf Ansichten zu.

Ansichten

  • Kann Daten von Modellen abrufen, um dem Benutzer den aktuellen Spielstatus darzustellen. Beispielsweise kann eine View-Methode player.Run() intern model.speed verwenden, um die Fähigkeiten des Spielers zu manifestieren.
  • Sollte niemals Modelle mutieren.
  • Implementiert strikt die Funktionalitäten seiner Klasse. Zum Beispiel:
    • Eine PlayerView sollte keine Eingabeerkennung implementieren oder den Spielstatus ändern.
    • Eine View sollte als Blackbox fungieren, die über eine Schnittstelle verfügt und über wichtige Ereignisse informiert.
    • Speichert keine Kerndaten (wie Geschwindigkeit, Gesundheit, Leben, …).

Controller

  • Speichern Sie keine Kerndaten.
  • Kann manchmal Benachrichtigungen von unerwünschten Ansichten filtern.
  • Aktualisieren und verwenden Sie die Daten des Modells.
  • Verwaltet den Szenen-Workflow von Unity.

Klassenhierarchie

In diesem Fall gibt es nicht viele Schritte, denen ich folge. Normalerweise nehme ich wahr, dass eine Klasse geteilt werden muss, wenn Variablen zu viele „Präfixe“ zeigen oder zu viele Varianten desselben Elements erscheinen (wie Player in einem MMO oder Gun in einem FPS).

Beispielsweise hätte ein einzelnes Model , das die Spielerdaten enthält, viele playerDataA, playerDataB,... oder ein Controller , der Spielerbenachrichtigungen verarbeitet, hätte OnPlayerDidA,OnPlayerDidB,... . Wir wollen die Skriptgröße reduzieren und player und OnPlayer Präfixe loswerden.

Lassen Sie mich die Verwendung einer Model -Klasse demonstrieren, da es einfacher ist, nur Daten zu verwenden.

Beim Programmieren beginne ich normalerweise mit einer einzigen Model -Klasse, die alle Daten für das Spiel enthält.

 // 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; }

Es ist leicht zu erkennen, dass je komplexer das Spiel ist, desto zahlreicher werden die Variablen. Bei ausreichender Komplexität könnten wir mit einer riesigen Klasse enden, die model.playerABCDFoo Variablen enthält. Das Verschachteln von Elementen vereinfacht die Codevervollständigung und gibt auch Raum zum Wechseln zwischen Variationen von Daten.

 // 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 }

Mit dieser Konfiguration von Klassen können Entwickler intuitiv Konzept für Konzept im Quellcode navigieren. Nehmen wir ein Ego-Shooter-Spiel an, bei dem Waffen und ihre Konfigurationen sehr zahlreich werden können. Die Tatsache, dass GunModel in einer Klasse enthalten ist, ermöglicht die Erstellung einer Liste von Prefabs (vorkonfigurierte GameObjects, die schnell dupliziert und im Spiel wiederverwendet werden können) für jede Kategorie und zur späteren Verwendung gespeichert.

Wenn im Gegensatz dazu alle Waffeninformationen zusammen in einer einzigen GunModel -Klasse in Variablen wie gun0Ammo , gun1Ammo , gun0Clips usw. gespeichert würden, müsste der Benutzer, wenn er Gun speichern müsste, die gesamten Waffendaten speichern Model einschließlich der unerwünschten Player . In diesem Fall wäre es offensichtlich, dass eine neue GunModel -Klasse besser wäre.

BILD: KLASSENHIERARCHIE
Verbesserung der Klassenhierarchie.

Wie bei allem gibt es zwei Seiten der Medaille. Manchmal kann man die Codekomplexität unnötigerweise überkompartimentieren und erhöhen. Nur Erfahrung kann Ihre Fähigkeiten genug verfeinern, um die beste MVC-Sortierung für Ihr Projekt zu finden.

Neue Spezialfähigkeit des Spielentwicklers freigeschaltet: Unity-Spiele mit dem MVC-Muster.
Twittern

Fazit

Es gibt Unmengen von Softwaremustern da draußen. In diesem Beitrag habe ich versucht, das zu zeigen, das mir in früheren Projekten am meisten geholfen hat. Entwickler sollten neues Wissen immer aufnehmen, aber auch immer hinterfragen. Ich hoffe, dieses Tutorial hilft Ihnen dabei, etwas Neues zu lernen, und dient gleichzeitig als Sprungbrett für die Entwicklung Ihres eigenen Stils.

Außerdem ermutige ich Sie wirklich, andere Muster zu erforschen und dasjenige zu finden, das am besten zu Ihnen passt. Ein guter Ausgangspunkt ist dieser Wikipedia-Artikel mit seiner hervorragenden Liste von Mustern und ihren Eigenschaften.

Wenn Ihnen das AMVCC-Muster gefällt und Sie es ausprobieren möchten, vergessen Sie nicht, meine Bibliothek Unity MVC auszuprobieren, die alle Kernklassen enthält, die zum Starten einer AMVCC-Anwendung erforderlich sind.


Weiterführende Literatur im Toptal Engineering Blog:

  • Unity-KI-Entwicklung: Ein endliches Maschinen-Tutorial