Unity with MVC: jak podnieść poziom rozwoju gry
Opublikowany: 2022-03-11Nowicjusze zwykle zaczynają uczyć się handlu od klasycznego programu Hello World
. Stamtąd będą następować coraz większe zadania. Każde nowe wyzwanie prowadzi do ważnej lekcji:
Im większy projekt, tym większe spaghetti.
Wkrótce łatwo zauważyć, że w dużych czy małych zespołach nie można lekkomyślnie robić tego, co się komuś podoba. Kod musi być utrzymany i może trwać przez długi czas. Firmy, dla których pracowałeś, nie mogą po prostu wyszukać Twoich danych kontaktowych i zapytać Cię za każdym razem, gdy chcą naprawić lub ulepszyć bazę kodu (a Ty też tego nie chcesz).
Dlatego istnieją wzorce projektowe oprogramowania; narzucają proste zasady dyktujące ogólną strukturę projektu oprogramowania. Pomagają jednemu lub kilku programistom oddzielić kluczowe elementy dużego projektu i zorganizować je w standardowy sposób, eliminując zamieszanie, gdy napotka jakąś nieznaną część bazy kodu.
Zasady te, jeśli są przestrzegane przez wszystkich, pozwalają na lepszą konserwację i nawigację starszego kodu oraz szybsze dodawanie nowego kodu. Mniej czasu poświęca się na planowanie metodologii rozwoju. Ponieważ problemy nie występują w jednym smaku, nie ma wzorca projektowego srebrnej kuli. Należy dokładnie rozważyć mocne i słabe strony każdego wzoru i znaleźć najlepsze dopasowanie do danego wyzwania.
W tym samouczku przedstawię moje doświadczenie z popularną platformą do tworzenia gier Unity i wzorcem Model-View-Controller (MVC) do tworzenia gier. W ciągu siedmiu lat mojego rozwoju, zmagając się ze sporym udziałem spaghetti dla twórców gier, osiągnąłem świetną strukturę kodu i szybkość tworzenia, korzystając z tego wzorca projektowego.
Zacznę od wyjaśnienia nieco podstawowej architektury Unity, wzorca Entity-Component. Następnie przejdę do wyjaśnienia, w jaki sposób MVC pasuje do tego, i użyję małego makiety projektu jako przykładu.
Motywacja
W literaturze oprogramowania znajdziemy ogromną ilość wzorców projektowych. Mimo że mają zestaw reguł, programiści zazwyczaj trochę naginają zasady, aby lepiej dostosować wzór do swojego konkretnego problemu.
Ta „wolność programowania” jest dowodem na to, że nie znaleźliśmy jeszcze jednej, ostatecznej metody projektowania oprogramowania. Dlatego ten artykuł nie ma być ostatecznym rozwiązaniem Twojego problemu, ale raczej pokazać korzyści i możliwości dwóch dobrze znanych wzorców: Entity-Component i Model-View-Controller.
Wzorzec encja-komponent
Entity-Component (EC) to wzorzec projektowy, w którym najpierw definiujemy hierarchię elementów składających się na aplikację (Entities), a następnie definiujemy cechy i dane, które każda z nich będzie zawierać (Components). Mówiąc bardziej „programistycznie”, Entity może być obiektem z tablicą 0 lub więcej Komponentów. Zobrazujmy taki podmiot:
some-entity [component0, component1, ...]
Oto prosty przykład drzewa 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 jest dobrym wzorcem do złagodzenia problemów wielokrotnego dziedziczenia, gdzie złożona struktura klas może powodować problemy, takie jak problem diamentowy, gdzie klasa D, dziedzicząca dwie klasy, B i C, z tą samą klasą bazową A, może powodować konflikty, ponieważ jak B i C inaczej modyfikują cechy A.
Tego rodzaju problemy mogą być powszechne podczas tworzenia gier, w których dziedziczenie jest często szeroko stosowane.
Rozbijając funkcje i procedury obsługi danych na mniejsze komponenty, można je dołączać i ponownie wykorzystywać w różnych encjach bez polegania na wielu dziedziczeniach (co, nawiasem mówiąc, nie jest nawet funkcją C# lub JavaScript, głównych języków używanych przez Unity ).
Gdzie element encji nie spełnia oczekiwań
Będąc o jeden poziom powyżej OOP, EC pomaga defragmentować i lepiej organizować architekturę kodu. Jednak w dużych projektach nadal jesteśmy „zbyt wolni” i możemy znaleźć się w „oceanie funkcji”, mając trudności ze znalezieniem odpowiednich encji i komponentów lub ustaleniem, w jaki sposób powinny ze sobą współdziałać. Istnieje nieskończenie wiele sposobów na łączenie Encji i Komponentów dla danego zadania.
Jednym ze sposobów na uniknięcie bałaganu jest nałożenie dodatkowych wytycznych na element Entity-Component. Na przykład jednym ze sposobów, w jaki lubię myśleć o oprogramowaniu, jest podzielenie go na trzy różne kategorie:
- Niektóre obsługują surowe dane, umożliwiając ich tworzenie, odczytywanie, aktualizowanie, usuwanie lub wyszukiwanie (tj. koncepcja CRUD).
- Inne implementują interfejs umożliwiający interakcję z innymi elementami, wykrywając zdarzenia związane z ich zakresem i wyzwalając powiadomienia, gdy się pojawią.
- Wreszcie, niektóre elementy są odpowiedzialne za odbieranie tych powiadomień, podejmowanie decyzji dotyczących logiki biznesowej i decydowanie o tym, jak należy manipulować danymi.
Na szczęście mamy już wzór, który zachowuje się dokładnie w ten sposób.
Wzorzec Model-View-Controller (MVC)
Wzorzec Model-View-Controller (MVC) dzieli oprogramowanie na trzy główne komponenty: modele (Data CRUD), widoki (interfejs/wykrywanie) i kontrolery (decyzja/działanie). MVC jest wystarczająco elastyczny, aby można go było wdrożyć nawet na wierzchu ECS lub OOP.
Tworzenie gier i interfejsu użytkownika ma zwykły przepływ pracy polegający na oczekiwaniu na dane wejściowe użytkownika lub inny warunek wyzwalający, wysyłaniu powiadomień o tych zdarzeniach w odpowiednim miejscu, decydowaniu, co zrobić w odpowiedzi i odpowiednio aktualizując dane. Działania te wyraźnie pokazują zgodność tych aplikacji z MVC.
Ta metodologia wprowadza kolejną warstwę abstrakcji, która pomoże w planowaniu oprogramowania, a także pozwoli nowym programistom na nawigację nawet w większej bazie kodu. Dzieląc proces myślenia na dane, interfejs i decyzje, programiści mogą zmniejszyć liczbę plików źródłowych, które należy przeszukać, aby dodać lub poprawić funkcjonalność.
Jedność i WE
Przyjrzyjmy się najpierw bliżej temu, co Unity daje nam z góry.
Unity to platforma programistyczna oparta na EC, w której wszystkie Entity są instancjami GameObject
, a funkcje, które sprawiają, że są „widoczne”, „ruchome”, „interaktywne” itd., są dostarczane przez klasy rozszerzające Component
.
Panel Hierarchii i Panel Inspektora edytora Unity zapewniają potężny sposób na składanie aplikacji, dołączanie komponentów, konfigurowanie ich stanu początkowego i uruchamianie gry z dużo mniejszą ilością kodu źródłowego niż normalnie.
Mimo to, jak już wspomnieliśmy, możemy trafić na problem „zbyt wielu funkcji” i znaleźć się w gigantycznej hierarchii, z funkcjami rozproszonymi wszędzie, co znacznie utrudnia życie programistom.
Myśląc w sposób MVC, zamiast tego możemy zacząć od podzielenia rzeczy zgodnie z ich funkcją, strukturyzując naszą aplikację jak w poniższym przykładzie:
Adaptacja MVC do środowiska tworzenia gier
Teraz chciałbym wprowadzić dwie małe modyfikacje do ogólnego wzorca MVC, które pomogą dostosować go do wyjątkowych sytuacji, z którymi się zetknąłem budując projekty Unity za pomocą MVC:
- Odwołania do klas MVC są łatwo rozproszone po całym kodzie. - W Unity programiści zazwyczaj muszą przeciągać i upuszczać instancje, aby były dostępne, lub dotrzeć do nich za pomocą niewygodnych instrukcji find, takich jak
GetComponent( ... )
. - Piekło utraconych referencji nastąpi, jeśli Unity ulegnie awarii lub jakiś błąd sprawi, że wszystkie przeciągnięte referencje znikną. — To sprawia, że konieczne jest posiadanie jednego głównego obiektu referencyjnego, przez który można uzyskać dostęp i odzyskać wszystkie instancje w Aplikacji . - Niektóre elementy zawierają ogólną funkcjonalność, która powinna być w wysokim stopniu wielokrotnego użytku i która naturalnie nie należy do jednej z trzech głównych kategorii: Model, Widok lub Kontroler. Te lubię nazywać po prostu komponentami . Są one również „Komponentami” w sensie Entity-Component, ale działają jedynie jako pomocnicy w ramach MVC. - Na przykład komponent
Rotator
, który obraca przedmioty tylko o określoną prędkość kątową i niczego nie powiadamia, nie przechowuje ani nie decyduje.
Aby pomóc złagodzić te dwa problemy, wymyśliłem zmodyfikowany wzorzec, który nazywam AMVCC lub Application-Model-View-Controller-Component.
- Aplikacja — pojedynczy punkt dostępu do aplikacji i kontenera wszystkich krytycznych wystąpień i danych związanych z aplikacją.
- MVC - Powinieneś już to wiedzieć. :)
- Komponent — mały, dobrze zawarty skrypt, który można ponownie wykorzystać.
Te dwie modyfikacje zaspokoiły moje potrzeby we wszystkich projektach, w których je wykorzystałem.
Przykład: 10 odbić
Jako prosty przykład spójrzmy na małą grę o nazwie 10 Bounces , w której wykorzystam podstawowe elementy wzorca AMVCC.
Konfiguracja gry jest prosta: Ball
ze SphereCollider
i Rigidbody
(która zacznie spadać po „Play”), Cube
jako podłoże i 5 skryptów tworzących AMVCC.
Hierarchia
Przed pisaniem skryptów zwykle zaczynam od hierarchii i tworzę zarys mojej klasy i zasobów. Zawsze podążając za tym nowym stylem AMVCC.
Jak widać, view
GameObject zawiera wszystkie elementy wizualne, a także te z innymi skryptami View
. GameObjects model
i controller
, w przypadku małych projektów, zwykle zawierają tylko odpowiednie skrypty. W przypadku większych projektów będą one zawierać GameObjects z bardziej szczegółowymi skryptami.
Gdy osoba nawigująca w Twoim projekcie chce uzyskać dostęp do:
- Dane: Przejdź do
application > model > ...
- Logika/przepływ pracy: Przejdź do
application > controller > ...
- Renderowanie/Interfejs/Wykrywanie: Przejdź do
application > view > ...
Jeśli wszystkie zespoły przestrzegają tych prostych zasad, starsze projekty nie powinny stanowić problemu.
Zwróć uwagę, że nie ma kontenera Component
, ponieważ, jak wspomnieliśmy, są one bardziej elastyczne i mogą być dołączane do różnych elementów w czasie wolnym od programisty.
Skrypty
Uwaga: Skrypty pokazane poniżej to abstrakcyjne wersje rzeczywistych implementacji. Szczegółowa implementacja nie przyniosłaby wiele korzyści czytelnikowi. Jeśli jednak chcesz dowiedzieć się więcej, oto link do mojego osobistego frameworka MVC dla Unity, Unity MVC. Znajdziesz podstawowe klasy, które implementują strukturę strukturalną AMVCC potrzebną w większości aplikacji.
Przyjrzyjmy się strukturze skryptów dla 10 Bounces .
Zanim zaczniemy, dla tych, którzy nie są zaznajomieni z przepływem pracy w Unity, wyjaśnijmy pokrótce, jak współpracują ze sobą skrypty i GameObjects. W Unity „Komponenty” w sensie Entity-Component są reprezentowane przez klasę MonoBehaviour
. Aby taki plik istniał w czasie wykonywania, programista powinien albo przeciągnąć i upuścić swój plik źródłowy do GameObject (który jest „Entity” wzorca Entity-Component) lub użyć polecenia AddComponent<YourMonobehaviour>()
. Następnie skrypt zostanie utworzony i będzie gotowy do użycia podczas wykonywania.
Na początek zdefiniujemy klasę Application („A” w AMVCC), która będzie główną klasą zawierającą odniesienia do wszystkich elementów gry. Stworzymy również pomocniczą klasę bazową o nazwie Element
, która da nam dostęp do instancji Application i jej podrzędnych instancji MVC.
Mając to na uwadze, zdefiniujmy klasę Application
(„A” w AMVCC), która będzie miała unikalną instancję. Wewnątrz niego trzy zmienne, model
, view
i controller
, dadzą nam punkty dostępu do wszystkich instancji MVC w czasie wykonywania. Te zmienne powinny być zmiennymi MonoBehaviour
z public
odniesieniami do żądanych skryptów.
Następnie utworzymy również pomocniczą klasę bazową o nazwie Element
, która da nam dostęp do instancji Application. Ten dostęp pozwoli każdej klasie MVC na dotarcie do siebie nawzajem.
Zauważ, że obie klasy rozszerzają MonoBehaviour
. Są to „komponenty”, które zostaną dołączone do „podmiotów” GameObject.

// 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() { } }
Z BounceElement
możemy stworzyć podstawowe klasy MVC. BounceModel
, BounceView
i BounceController
zwykle działają jako kontenery dla bardziej wyspecjalizowanych instancji, ale ponieważ jest to prosty przykład, tylko View będzie miał strukturę zagnieżdżoną. Model i kontroler można wykonać w jednym skrypcie dla każdego:
// 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!!”); } }
Po stworzeniu wszystkich skryptów możemy przystąpić do ich dołączania i konfigurowania.
Układ hierarchii powinien wyglądać tak:
- application [BounceApplication] - model [BounceModel] - controller [BounceController] - view [BounceView] - ... - ball [BallView] - ...
Na przykładzie BounceModel
możemy zobaczyć, jak wygląda w edytorze Unity:
BounceModel
z polami winCondition
bounces
Gdy wszystkie skrypty są ustawione i gra jest uruchomiona, powinniśmy otrzymać to wyjście w Panelu Konsoli .
Powiadomienia
Jak pokazano w powyższym przykładzie, gdy piłka uderza w ziemię, jej widok wykonuje app.controller.OnBallGroundHit()
, który jest metodą. W żadnym wypadku nie jest to „błędem” w przypadku wszystkich powiadomień w aplikacji. Z mojego doświadczenia wynika jednak, że lepsze wyniki osiągnąłem używając prostego systemu powiadomień zaimplementowanego w klasie AMVCC Application.
Aby to zaimplementować, zaktualizujmy układ BounceApplication
aby był:
// 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() { /* ... */ } }
Następnie potrzebujemy nowego skryptu, w którym wszyscy programiści dodadzą nazwy zdarzeń powiadomień, które mogą zostać wywołane podczas wykonywania.
// 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”; /* ... */ }
Łatwo zauważyć, że w ten sposób poprawia się czytelność kodu, ponieważ programiści nie muszą przeszukiwać całego kodu źródłowego w poszukiwaniu metod controller.OnSomethingComplexName
, aby zrozumieć, jakie akcje mogą wystąpić podczas wykonywania. Sprawdzając tylko jeden plik, można zrozumieć ogólne zachowanie aplikacji.
Teraz musimy tylko dostosować BallView
i BounceController
do obsługi tego nowego systemu.
// 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; } } }
Większe projekty będą miały dużo powiadomień. Tak więc, aby uniknąć dużej struktury obudowy przełączników, zaleca się utworzenie różnych kontrolerów i sprawienie, aby obsługiwały różne zakresy powiadomień.
AMVCC w prawdziwym świecie
W tym przykładzie pokazano prosty przypadek użycia wzorca AMVCC. Dostosowanie sposobu myślenia w zakresie trzech elementów MVC i nauka wizualizacji encji jako uporządkowanej hierarchii to umiejętności, które należy szlifować.
W większych projektach deweloperzy będą mieli do czynienia z bardziej złożonymi scenariuszami i wątpliwościami, czy coś powinno być View, czy Controllerem, czy dana klasa powinna być dokładniej rozdzielona na mniejsze.
Zasady kciuka (przez Eduardo)
Nigdzie nie ma żadnego „Uniwersalnego przewodnika po sortowaniu MVC”. Ale jest kilka prostych zasad, których zwykle przestrzegam, aby pomóc mi określić, czy zdefiniować coś jako model, widok lub kontroler, a także kiedy podzielić daną klasę na mniejsze części.
Zwykle dzieje się to organicznie, gdy myślę o architekturze oprogramowania lub podczas pisania skryptów.
Sortowanie klas
Modele
- Przechowuj podstawowe dane i stan aplikacji, takie jak
health
gracza lubammo
do broni . - Serializuj, deserializuj i/lub konwertuj między typami.
- Załaduj/zapisz dane (lokalnie lub w sieci).
- Powiadamiaj Kontrolerów o postępach operacji.
- Przechowuj stan gry dla automatu skończonego gry.
- Nigdy nie otwieraj widoków.
Wyświetlenia
- Może pobierać dane od Modeli, aby przedstawić użytkownikowi aktualny stan gry. Na przykład metoda View
player.Run()
może wewnętrznie użyćmodel.speed
do zamanifestowania umiejętności gracza. - Nigdy nie powinien mutować modeli.
- Ściśle realizuje funkcjonalności swojej klasy. Na przykład:
-
PlayerView
nie powinien implementować wykrywania danych wejściowych ani modyfikować stanu gry. - Widok powinien działać jak czarna skrzynka, która ma interfejs i powiadamia o ważnych wydarzeniach.
- Nie przechowuje podstawowych danych (takich jak prędkość, zdrowie, życie itp.).
-
Kontrolery
- Nie przechowuj podstawowych danych.
- Czasami może filtrować powiadomienia z niepożądanych widoków.
- Zaktualizuj i wykorzystaj dane Modelu.
- Zarządza przepływem pracy na scenach Unity.
Hierarchia klas
W tym przypadku nie podążam za wieloma krokami. Zazwyczaj dostrzegam, że jakaś klasa musi zostać podzielona, gdy zmienne zaczynają pokazywać zbyt wiele „prefiksów” lub pojawia się zbyt wiele wariantów tego samego elementu (jak klasy Player
w MMO lub typy Gun
w FPS).
Na przykład pojedynczy Model
zawierający dane odtwarzacza miałby wiele danych odtwarzaczaA, danych odtwarzaczaB playerDataA, playerDataB,...
lub Controller
obsługujący powiadomienia odtwarzacza miałby OnPlayerDidA,OnPlayerDidB,...
. Chcemy zmniejszyć rozmiar skryptu i pozbyć się prefiksów player
i OnPlayer
.
Pozwolę sobie zademonstrować za pomocą klasy Model
, ponieważ łatwiej jest zrozumieć, używając tylko danych.
Podczas programowania zwykle zaczynam od jednej klasy Model
zawierającej wszystkie dane do gry.
// 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; }
Nietrudno zauważyć, że im bardziej złożona gra, tym więcej zmiennych dostaniemy. Przy wystarczającej złożoności możemy skończyć z gigantyczną klasą zawierającą zmienne model.playerABCDFoo
. Zagnieżdżanie elementów uprości uzupełnianie kodu, a także daje miejsce na przełączanie się między odmianami danych.
// 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 }
Dzięki takiej konfiguracji klas programiści mogą intuicyjnie nawigować w kodzie źródłowym, po jednej koncepcji na raz. Załóżmy, że jest to strzelanka pierwszoosobowa, w której broni i ich konfiguracji może być naprawdę wiele. Fakt, że GunModel
jest zawarty w klasie, umożliwia tworzenie listy prefabrykatów (wstępnie skonfigurowanych Prefabs
, które można szybko powielać i ponownie wykorzystywać w grze) dla każdej kategorii i przechowywać do późniejszego wykorzystania.
W przeciwieństwie do tego, gdyby wszystkie informacje o broni były przechowywane razem w pojedynczej klasie GunModel
, w zmiennych, takich jak gun0Ammo
, gun1Ammo
, gun0Clips
, itd., wówczas użytkownik, mając do czynienia z koniecznością przechowywania danych o Gun
, musiałby przechowywać całe Model
zawierający niechciane dane Player
. W tym przypadku byłoby oczywiste, że nowa klasa GunModel
byłaby lepsza.
Jak ze wszystkim, są dwie strony medalu. Czasami można niepotrzebnie przekompensować i zwiększyć złożoność kodu. Tylko doświadczenie może udoskonalić twoje umiejętności na tyle, aby znaleźć najlepsze sortowanie MVC dla twojego projektu.
Wniosek
Istnieje mnóstwo wzorców oprogramowania. W tym poście starałem się pokazać ten, który najbardziej mi pomógł w poprzednich projektach. Deweloperzy powinni zawsze przyswajać nową wiedzę, ale też zawsze ją kwestionować. Mam nadzieję, że ten samouczek pomoże ci nauczyć się czegoś nowego, a jednocześnie będzie odskocznią w rozwijaniu własnego stylu.
Ponadto naprawdę zachęcam do zbadania innych wzorców i znalezienia tego, który najbardziej Ci odpowiada. Dobrym punktem wyjścia jest ten artykuł w Wikipedii, z doskonałą listą wzorców i ich cechami.
Jeśli podoba Ci się wzorzec AMVCC i chciałbyś go przetestować, nie zapomnij wypróbować mojej biblioteki Unity MVC , która zawiera wszystkie podstawowe klasy niezbędne do uruchomienia aplikacji AMVCC.
Dalsza lektura na blogu Toptal Engineering:
- Rozwój SI w Unity: samouczek dotyczący maszyny skończonej