Esmakordselt programmeerijad hakkavad ametit õppima tavaliselt klassikaga Hello World
programmi. Sealt lähevad kindlasti järjest suuremad ülesanded. Iga uus väljakutse viib koju olulise õppetunni:
Mida suurem on projekt, seda suuremad on spagetid.
Varsti on lihtne mõista, et suurtes või väikestes meeskondades ei saa hoolimatult teha nii, nagu heaks arvatakse. Kood tuleb säilitada ja see võib kesta kaua. Ettevõtted, kus olete töötanud, ei saa lihtsalt otsida teie kontaktandmeid ja küsida teilt iga kord, kui nad soovivad koodibaasi parandada või parandada (ja te ei soovi, et nad seda ka teeksid).
See on põhjus, miks tarkvara kujundamise mustrid olemas; nad kehtestavad lihtsad reeglid tarkvaraprojekti üldise struktuuri dikteerimiseks. Need aitavad ühel või mitmel programmeerijal eraldada suure projekti põhiosad ja korraldada need standardiseeritud viisil, välistades segaduse, kui tekib mõni tundmatu osa koodibaasist.
Need reeglid võimaldavad kõigi järgimisel pärandkoodi paremini hooldada ja navigeerida ning uut koodi kiiremini lisada. Arendamise metoodika kavandamiseks kulutatakse vähem aega. Kuna probleemid ei esine ühes maitses, pole hõbekuulide kujundusmustrit. Hoolikalt tuleb mõelda iga mustri tugevatele ja nõrkadele külgedele ning leida sobivaim väljakutse jaoks.
Selles õpetuses seostan oma kogemust populaarsega Unity mängude arendusplatvorm ja mudeli-vaate-kontrolleri (MVC) muster mängude arendamiseks. Oma seitsme arenguaasta jooksul, olles maadelnud oma mängude dev spagettide õiglase osakaaluga, olen selle kujundusmustri abil saavutanud suurepärase koodistruktuuri ja arenduskiiruse.
Alustan natuke Unity baasarhitektuuri, Entity-Component mustri selgitamisest. Seejärel selgitan, kuidas MVC selle peale sobib, ja kasutan näiteks väikest näidisprojekti.
mis teeb hea andmebaasi
Tarkvara kirjandusest leiame suure hulga kujundusmustreid. Ehkki neil on reeglite kogum, teevad arendajad reeglina veidi reeglit paindma, et mustrit paremini oma konkreetse probleemiga kohandada.
See „programmeerimisvabadus“ on tõestus selle kohta, et me pole veel leidnud ühte kindlat meetodit tarkvara kujundamiseks. Seega ei ole see artikkel mõeldud teie probleemi lõplikuks lahenduseks, vaid selleks, et näidata kahe tuntud mustri eeliseid ja võimalusi: Entity-Component ja Model-View-Controller.
Entity-Component (EC) on kujundusmuster, kus määratleme kõigepealt rakenduse moodustavate elementide hierarhia (Entities) ja hiljem määratleme funktsioonid ja andmed, mida igaüks sisaldab (Components). Programmeerija mõistes võib olem olla objekt, mille massiivi on 0 või enam komponenti. Kujutame sellist üksust:
some-entity [component0, component1, ...]
Siin on lihtne näide EÜ puust.
- 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 on hea muster mitmekordse pärimise probleemide leevendamiseks, kus keeruline klassistruktuur võib tekitada selliseid probleeme nagu teemandiprobleem kus klass D, mis pärineb kahest klassist, B ja C, sama baasklassiga A, võib tekitada konflikte, sest kuidas B ja C muudavad A omadusi erinevalt.
Sellised probleemid võivad olla levinud mängude arendamisel, kus pärandit kasutatakse sageli laialdaselt.
Jagades funktsioonid ja andmekäitlejad väiksemateks komponentideks, saab neid kinnitada ja taaskasutada erinevates üksustes, tuginedes mitmele pärandile (mis, muide, pole isegi Unity peamiste keelte C # või Javascripti omadus) ).
OOP-st kõrgemal olles aitab EC teie koodiarhitektuuri defragmentida ja paremini korraldada. Suurtes projektides oleme siiski endiselt „liiga vabad” ja võime sattuda „ookeani ookeani”, meil on raske leida õigeid üksusi ja komponente või nuputada, kuidas nad peaksid suhtlema. Konkreetse ülesande jaoks olemite ja komponentide kokkupanekuks on lõpmatuid viise.
Üks viis segaduse vältimiseks on Entity-Componenti peal mõnede lisajuhiste kehtestamine. Näiteks üks viis, kuidas mulle tarkvara üle mõelda, on selle jagamine kolme erinevasse kategooriasse:
Õnneks on meil juba muster, mis käitub täpselt nii.
The Mudel-vaade-kontroller muster (MVC) jagab tarkvara kolmeks põhikomponendiks: mudelid (Data CRUD), vaated (liides / tuvastamine) ja kontrollerid (otsus / tegevus). MVC on piisavalt paindlik, et seda saaks rakendada isegi ECS-i või OOP-i peal.
Mängu ja kasutajaliidese väljatöötamisel on tavaline töövoog kasutaja sisendi või muu käivitava tingimuse ootamine, nendest sündmustest teavitamine kuhugi sobivasse kohta, otsustamine, mida vastuseks teha, ja andmete värskendamine vastavalt sellele. Need toimingud näitavad selgelt nende rakenduste ühilduvust MVC-ga.
See metoodika tutvustab teist abstraktsioonikihti, mis aitab tarkvara kavandamisel ja võimaldab uutel programmeerijatel navigeerida ka suuremas koodibaasis. Jagades mõtlemisprotsessi andmeteks, liideseks ja otsusteks, saavad arendajad vähendada lähtefailide arvu, mida tuleb funktsionaalsuse lisamiseks või parandamiseks otsida.
Vaatame kõigepealt lähemalt, mida ühtsus meile ette annab.
Unity on EÜ-põhine arendusplatvorm, kus kõik üksused on GameObject
eksemplarid ja funktsioonid, mis muudavad need „nähtavaks”, „teisaldatavaks”, „interaktiivseks” ja nii edasi, pakuvad klassid, mis laienevad Component
Unity toimetaja Hierarhia paneel ja Inspektoripaneel pakkuda tõhusat viisi oma rakenduse kokkupanekuks, komponentide lisamiseks, nende algse oleku seadistamiseks ja mängu alglaadimiseks palju vähem lähtekoodiga kui tavaliselt.
Hierarhia paneel nelja GameObjectsiga paremal
Inspektoripaneel koos GameObjecti komponentidega
Siiski, nagu me oleme arutanud, võime tabada probleemi „liiga palju funktsioone“ ja sattuda hiiglaslikku hierarhiasse, kus funktsioonid on laiali kõikjal, muutes arendaja elu palju raskemaks.
Mõeldes MVC-le, võime selle asemel alustada asjade jagamisest vastavalt nende funktsioonidele, struktureerides oma rakenduse nagu allpool toodud näide:
Nüüd tahaksin tutvustada üldise MVC mustri kahte väikest modifikatsiooni, mis aitavad seda kohandada ainulaadsete olukordadega, mida olen kohanud MVC-ga Unity projektide ehitamisel:
GetComponent( ... )
. - Kui Unity jookseb kokku või mõni viga paneb kõik lohistatud viited kaduma, saab kadunud viide põrgu. - See muudab vajalikuks ühe juure viiteobjekti olemasolu, mille kaudu kõik Rakendus on võimalik kätte saada ja taastada.Rotator
Komponent, mis pöörab asju ainult etteantud nurkkiiruse järgi ega teavita, ei salvesta ega otsusta midagi.Nende kahe probleemi leevendamiseks pakkusin välja muudetud mustri, mida kutsun AMVCC või Rakendus-Mudel-Vaade-Kontrolleri-Komponent.
Need kaks modifikatsiooni on rahuldanud minu vajadused kõigi projektide jaoks, milles olen neid kasutanud.
kuidas kasutada discord bot käske
Lihtsa näitena vaatame väikest mängu nimega 10 põrgatust , kus kasutan AMVCC mustri põhielemente.
Mängu seadistamine on lihtne: A Ball
koos SphereCollider
ja a Rigidbody
(mis hakkab pärast “Play” kukkuma), a Cube
ja 5 skripti, mis moodustavad AMVCC.
Enne skriptimist alustan tavaliselt hierarhiast ja loon oma klassi ja varade ülevaate. Järgides alati seda uut AMVCC stiili.
Nagu näeme, on view
GameObject sisaldab kõiki visuaalseid elemente ja ka teisi View
skriptid. model
ja controller
Väikeste projektide jaoks mõeldud GameObjects sisaldab tavaliselt ainult vastavaid skripte. Suuremate projektide korral sisaldavad need spetsiifilisemate skriptidega GameObjecte.
Kui keegi teie projektis navigeerib, soovib sellele juurde pääseda
application > model > ...
application > controller > ...
application > view > ...
Kui kõik meeskonnad järgivad neid lihtsaid reegleid, ei tohiks pärandprojektid probleemiks saada.
Pange tähele, et pole ühtegi Component
konteiner, sest nagu me oleme arutanud, on need paindlikumad ja neid saab arendaja vabal ajal erinevate elementide külge kinnitada.
Märkus. Allpool toodud skriptid on tegeliku rakenduse abstraktsed versioonid. Üksikasjalik teostus ei tooks lugejale palju kasu. Kui soovite siiski rohkem uurida, siin on link minu isiklikule MVC raamistikule Unity, Unity MVC. Leiate põhiklassid, mis rakendavad enamiku rakenduste jaoks vajalikku AMVCC struktuurilist raamistikku.
Vaatame skriptide ülesehitust 10 põrgatust .
Enne alustamist selgitame neile, kes Unity'i töövoogu ei tunne, lühidalt, kuidas skriptid ja GameObjects koos töötavad. Ühtsuses esindavad “komponente” olemuse-komponendi tähenduses MonoBehaviour
klass. Et üks käitamise ajal olemas oleks, peaks arendaja kas lohistama oma lähtefaili GameObjecti (mis on mustri Entity-Component “Entity”) või kasutama käsku AddComponent()
Pärast seda skript instantsitakse ja on kasutamiseks valmis kasutamiseks.
Alustuseks määratleme rakendusklassi (AMVCC-s A-tähe), mis on põhiklass, mis sisaldab viiteid kõigile eksemplaritud mänguelementidele. Loome ka abistajate baasklassi nimega Element
, mis annab meile juurdepääsu rakenduse eksemplarile ja selle laste MVC-eksemplaridele.
Seda silmas pidades määratleme Application
klass (AMVCC-s „A”), millel on ainulaadne eksemplar. Selle sees annavad kolm muutujat model
, view
ja controller
meile käitamisajal kõigi MVC-eksemplaride pöörduspunkte. Need muutujad peaksid olema MonoBehaviour
s koos public
viited soovitud skriptidele.
Seejärel loome ka abistajate baasklassi nimega Element
, mis annab meile juurdepääsu rakenduse eksemplarile. See juurdepääs võimaldab igal MVC klassil jõuda teiseni.
Pange tähele, et mõlemad klassid laienevad MonoBehaviour
. Need on „komponendid”, mis kinnitatakse GameObjecti „Entities”.
// 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(); }} } // 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() { } }
Alates BounceElement
saame luua MVC põhiklassid. BounceModel
, BounceView
Ja BounceController
skriptid toimivad tavaliselt spetsiaalsemate eksemplaride konteineritena, kuid kuna see on lihtne näide, on pesatud struktuur ainult vaates. Mudeli ja kontrolleri saab teha ühe skripti jaoks:
// 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().isKinematic=true; // stops the ball OnGameComplete(); } } // Handles the win condition public void OnGameComplete() { Debug.Log(“Victory!!”); } }
Kõigi loodud skriptide korral saame jätkata nende kinnitamist ja konfigureerimist.
Hierarhia paigutus peaks olema järgmine:
- application [BounceApplication] - model [BounceModel] - controller [BounceController] - view [BounceView] - ... - ball [BallView] - ...
Kasutades BounceModel
näitena näeme, kuidas see Unity redaktoris välja näeb:
BounceModel
koos bounces
ja winCondition
väljad.
Kui kõik skriptid on seatud ja mäng töötab, peaksime selle väljundi saama Konsoolipaneel .
Nagu ülaltoodud näites näidatud, täidab pall maapinnale jõudes app.controller.OnBallGroundHit()
mis on meetod. See pole mingil juhul 'vale' seda teha kõigi rakenduse teadete puhul. Oma kogemuse põhjal olen siiski saavutanud paremaid tulemusi, kasutades AMVCC rakenduste klassi rakendatud lihtsat teavitussüsteemi.
Selle rakendamiseks värskendame BounceApplication
paigutust olla:
// 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() { /* ... */ } }
Järgmiseks vajame uut skripti, kuhu kõik arendajad lisavad teavitussündmuse nimed, mida saab käivitamise ajal saata.
// 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”; /* ... */ }
On lihtne mõista, et sel viisil paraneb koodi loetavus, kuna arendajatel pole vaja kogu lähtekoodilt otsida controller.OnSomethingComplexName
meetodeid, et mõista, milliseid toiminguid võib juhtuda täitmise ajal. Ainult ühte faili kontrollides on võimalik mõista rakenduse üldist käitumist.
Nüüd peame ainult kohandama BallView
ja BounceController
selle uue süsteemi käsitsemiseks.
// 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().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; } } }
Suurematel projektidel on palju teateid. Niisiis, selleks, et vältida suurt jaotuskastistruktuuri saamist, on soovitatav luua erinevad kontrollerid ja panna need käitama erinevaid teavitamisulatusi.
See näide on näidanud AMVCC mustri lihtsat kasutamist. Oma mõtlemisviisi kohandamine MVC kolme elemendi osas ja üksuste kui korrastatud hierarhia visualiseerimise õppimine on oskused, mida tuleks lihvida.
Suuremates projektides seisavad arendajad silmitsi keerukamate stsenaariumidega ja kahtlevad, kas miski peaks olema vaade või kontroller või tuleks antud klass väiksematest põhjalikumalt eraldada.
Kuskil pole ühtegi universaalset juhendit MVC sortimiseks. Kuid on mõned lihtsad reeglid, mida tavaliselt järgin, et aidata mul otsustada, kas määratleda midagi mudeli, vaate või kontrollerina ning millal jagada antud klass väiksemateks osadeks.
Tavaliselt juhtub see orgaaniliselt, kui mõtlen tarkvaraarhitektuurile või skriptimise ajal.
Mudelid
health
või relv ammo
.Vaated
player.Run()
saab sisemiselt kasutada model.speed
avaldada mängija võimeid.PlayerView
ei tohiks rakendada sisendi tuvastamist ega muuta mängu olekut.Kontrollerid
Sel juhul pole palju samme, mida ma järgin. Tavaliselt tajun, et mõni klass tuleb jagada, kui muutujad hakkavad näitama liiga palju „eesliiteid” või hakkab ilmuma liiga palju sama elemendi variante (näiteks Player
klassid MMO-s või Gun
tüübid FPS-is).
Näiteks üks Model
mängija andmeid sisaldades oleks palju playerDataA, playerDataB,...
või a Controller
mängija märguannete käsitsemisel oleks OnPlayerDidA,OnPlayerDidB,...
. Soovime skripti suurust vähendada ja player
-st vabaneda ja OnPlayer
eesliited.
Lubage mul näidata Model
klassi, sest ainult andmete abil on lihtsam aru saada.
Programmeerimise ajal alustan tavaliselt ühega Model
klass, kus on kõik mängu andmed.
// Model.cs class Model { public float playerHealth; public int playerLives; public GameObject playerGunPrefabA; public int playerGunAmmoA; public GameObject playerGunPrefabB; public int playerGunAmmoB; // Ops Gun[C D E ...] will appear... /* ... */ public float gameSpeed; public int gameLevel; }
On lihtne mõista, et mida keerukam on mäng, seda arvukamad muutujad saavad. Piisava keerukusega võiksime lõpuks saada hiiglasliku klassi, mis sisaldab model.playerABCDFoo
muutujad. Elementide pesitsemine lihtsustab koodi täitmist ja annab ruumi ka andmete variatsioonide vahel vahetamiseks.
võrrelda ja vastandada füüsilisest isikust ettevõtja partnerlust ja korporatsiooni
// 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 }
Klasside selle konfiguratsiooni abil saavad arendajad intuitiivselt lähtekoodis navigeerida üks kontseptsioon korraga. Oletame, et mängib esimese isiku laskemängu, kus relvi ja nende seadistusi võib saada tõesti palju. Asjaolu, et GunModel
sisaldab klassi võimaldab luua Prefabs
loendi (eelkonfigureeritud GameObjects tuleb kiiresti dubleerida ja mängus uuesti kasutada) iga kategooria jaoks ja salvestada hilisemaks kasutamiseks.
Seevastu, kui kogu relva teave salvestati ühtsesse GunModel
klass muutujatega nagu gun0Ammo
, gun1Ammo
, gun0Clips
ja nii edasi, siis kasutaja, kui seisab silmitsi vajadusega Gun
andmeid, peaks kogu Model
salvestama sealhulgas soovimatud Player
andmed. Sel juhul oleks ilmne, et uus GunModel
klass oleks parem.
Klasside hierarhia parandamine.
Nagu kõigil, on ka mündil kaks külge. Mõnikord võib asjatult koodi lahterdada ja keerukust suurendada. Ainult kogemused võivad teie oskusi piisavalt lihvida, et leida oma projekti jaoks parim MVC-sortimine.
Uus mäng dev Spetsiaalne võimekus lukustamata: MVC mustriga ühtsusmängud. PiiksumaSeal on palju tarkvaramustreid. Selles postituses proovisin näidata seda, mis mind varasemates projektides kõige rohkem aitas. Arendajad peaks alati omama uusi teadmisi, kuid seab need ka alati kahtluse alla. Loodan, et see õpetus aitab teil midagi uut õppida ja on samal ajal oma stiili väljatöötamisel hüppelauaks.
Samuti soovitan teil tõesti uurida teisi mustreid ja leida endale sobivaim. Üks hea lähtepunkt on see Vikipeedia artikkel , oma suurepärase mustrite ja nende omaduste loendiga.
Kui teile meeldib AMVCC muster ja soovite seda proovida, ärge unustage proovida minu teeki, Ühtsuse MVC , mis sisaldab kõiki AMVCC-rakenduse käivitamiseks vajalikke põhiklasse.