Cu excepția cazului în care se specifică altfel, următoarea postare pe blog a fost scrisă de un membru al comunității Gamasutra.
Gândurile și opiniile exprimate sunt cele ale autorului și nu ale Gamasutra sau ale companiei-mamă.
Partea 1 – Mesagerie
Partea 2 – Memorie
Partea 3 – Cache de date &
Partea 4 – Biblioteci grafice
Trăim o perioadă excelentă pentru a fi dezvoltatori. Cu o cantitate atât de mare de motoare grozave de nivel AAA disponibile pentru toată lumea, realizarea unor jocuri simple poate fi la fel de ușoară ca și cum ai trage și arunca. Se pare că nu mai există niciun motiv pentru a scrie un motor în aceste zile. Și cu sentimentul comun, „Scrieți jocuri, nu motoare”, de ce ați face-o?
Acest articol se adresează în primul rând dezvoltatorilor solo și echipelor mici. Presupun o oarecare familiaritate cu programarea orientată pe obiecte.
Vreau să vă ofer o perspectivă asupra abordării dezvoltării de motoare și voi folosi un motor fictiv simplu pentru a ilustra acest lucru.
De ce să scrieți un motor?
Răspunsul scurt este: Nu o faceți, dacă puteți evita.
Viața este prea scurtă pentru a scrie un motor pentru fiecare joc (Preluat din cartea 3D Graphics Programming de Sergei Savchenko)
Selecția actuală de Motoare excelente, cum ar fi Unity, Unreal sau CryEngine, sunt atât de flexibile pe cât se poate spera și pot fi folosite pentru a realiza aproape orice joc. Pentru sarcini mai specializate, există, bineînțeles, soluții mai specializate, cum ar fi Adventure Game Studio sau RPG Maker, pentru a numi doar câteva. Nici măcar costul motoarelor de nivel comercial nu mai reprezintă un argument.
Au mai rămas doar câteva motive de nișă pentru a vă scrie propriul motor:
- Vreți să învățați cum funcționează un motor
- Aveți nevoie de anumite funcționalități care nu sunt disponibile sau soluțiile disponibile sunt instabile
- Credeți că o puteți face mai bine / mai repede
- Vreți să păstrați controlul asupra dezvoltării
Toate acestea sunt motive perfect valabile și, dacă citiți aceste rânduri, probabil că aparțineți uneia dintre aceste tabere. Scopul meu nu este să intru aici într-o lungă dezbatere „Ce motor ar trebui să folosesc?” sau „Ar trebui să scriu un motor?” și voi trece direct la subiect. Așadar, să începem.
Cum să nu reușesc să scriu un Motor
Așteptați. Mai întâi vă spun să nu scrieți unul, apoi vă explic cum să eșuați? Grozavă introducere…
În orice caz, sunt o mulțime de lucruri de luat în considerare înainte de a scrie o singură linie de cod. Prima și cea mai mare problemă pe care o au toți cei care se apucă să scrie un motor de joc poate fi rezumată la asta:
Vreau să văd cât mai repede ceva gameplay!
Cu toate acestea, cu cât îți dai seama mai repede că va dura mult timp până când vei vedea ceva interesant întâmplându-se, cu atât mai bine îți va fi să scrii motorul.
Forțarea codului dvs. pentru a arăta o formă de grafică sau gameplay cât mai repede posibil, doar pentru a avea o confirmare vizuală a „progresului”, este cel mai mare dușman al dvs. în acest moment. Luați. Dumneavoastră. Timp!
Nici nu vă gândiți să începeți cu grafica. Probabil că ați citit o mulțime de tutoriale și cărți despre OpenGL / DirectX și știți cum să redați un simplu triunghi sau sprite. S-ar putea să credeți că un scurt fragment de cod de redare a unui mic mesh pe ecran este un loc bun pentru a începe. Nu este.
Da, progresul tău inițial va fi uimitor. Heck, ai putea rula în jurul unui mic nivel în First-Person în doar o zi copiind-lipind fragmente de cod din diverse tutoriale și Stack Overflow. Dar vă garantez că veți șterge fiecare linie din acel cod 2 zile mai târziu. Chiar mai rău, s-ar putea chiar să vă descurajați să mai scrieți un Engine, deoarece nu este motivant să vedeți din ce în ce mai puțin.
A doua mare problemă cu care se confruntă dezvoltatorii în timp ce scriu Engines este feature creep. Tuturor le-ar plăcea să scrie Sfântul Graal al Motoarelor. Toată lumea vrea acel Motor perfect care poate face totul. First Person Shooter, RPG-uri tactice, orice. Dar simplul fapt este că nu se poate. Încă. Uitați-vă doar la numele mari. Nici măcar Unity nu poate satisface cu adevărat fiecare gen de joc perfect.
Nici măcar nu vă gândiți să scrieți un motor care poate face mai mult de un gen de la prima încercare. Nu vă gândiți!
De unde să începi de fapt când scrii un Motor
Scrierea unui Motor este ca și cum ai ingineri un Motor adevărat pentru o mașină. Pașii sunt de fapt destul de evidenți, presupunând că știi la ce Joc (sau Mașină) lucrezi. Iată care sunt acestea:
- Descoperiți exact de ce trebuie să fie capabil motorul dumneavoastră și de ce nu trebuie să fie capabil motorul dumneavoastră.
- Organizați nevoile în Sisteme de care va avea nevoie motorul dumneavoastră.
- Proiectați arhitectura perfectă care leagă toate aceste Sisteme între ele.
- Repetați pașii 1. – 3. cât mai des posibil.
- Codificați.
Dacă (= dacă și numai dacă) petreceți suficient timp și efort la pașii 1. – 4. și designul jocului nu se schimbă brusc dintr-un joc de groază într-un aparat de jocuri mecanice (a se citi: Silent Hill), codificarea va fi un efort foarte plăcut. Codificarea va fi în continuare departe de a fi ușoară, dar perfect gestionabilă, chiar și de către Dezvoltatorii solitari.
Acesta este motivul pentru care acest articol se referă în principal la pașii 1. – 4. Gândiți-vă la pasul 5. ca la „Completarea spațiilor goale”. 50.000 LOC of Blanks”.
Partea cea mai crucială din toate acestea este Pasul 3. Aici ne vom concentra cele mai multe eforturi!”
Etapa 1. Identificarea nevoilor și a celor care nu sunt necesare
Toate aceste etape pot părea destul de banale la început. Dar ele chiar nu sunt. Ați putea crede că Pasul 1 al procesului de dezvoltare a unui motor de First Person Shooter se poate reduce la acest lucru:
Trebuie să încarc un Nivel, arma Jucătorului, niște Inamici cu AI. Gata, trecem la pasul 2.
Dacă ar fi atât de ușor. Cel mai bun mod de a parcurge pasul 1 este să parcurgeți întregul joc click cu click, acțiune cu acțiune, de la clic pe pictograma de pe desktop, până la apăsarea tastei de ieșire după ce ați rulat creditele. Faceți o listă, o listă mare cu ceea ce vă trebuie. Faceți o Listă cu ceea ce cu siguranță nu aveți nevoie.
Aceasta va fi probabil cam așa:
Pornesc jocul și acesta merge direct la meniul principal. Meniul va folosi o imagine statică? O scenă tăiată? Cum controlez Meniul principal, cu mouse-ul? Tastatura? De ce fel de elemente GUI am nevoie pentru meniul principal? Butoane, formulare, bare de defilare? Ce ziceți de muzică?
Și acestea sunt doar niște macroconcursuri. Intrați cât mai detaliat posibil. Să decideți că aveți nevoie de butoane este bine și frumos, dar conciliați și ce poate face un buton.
Vreau ca butoanele să aibă 4 stări, Up, Hover, Down, Disabled. Voi avea nevoie de sunet pentru butoane? Ce ziceți de efecte speciale? Ar fi ele animate în starea de inactivitate?
Dacă lista ta de nevoi și nevoi nu conține decât aproximativ 10 elemente până la sfârșitul meniului principal, ai făcut ceva greșit.
În această etapă, ceea ce faci este să simulezi motorul în creierul tău și să scrii ceea ce trebuie făcut. Pasul 1 va deveni mai clar la fiecare iterație, nu vă faceți griji că vă lipsește ceva de prima dată.
Pasul 2. Organizați nevoile în Sisteme
Atunci, aveți listele cu lucrurile de care aveți nevoie și de care nu aveți nevoie. Este timpul să le organizați. Evident, lucrurile legate de GUI, cum ar fi butoanele, vor intra într-un fel de Sistem GUI. Elementele legate de randare merg în Sistemul / Motorul grafic.
Din nou, ca și în cazul Pasului 1, a decide ce merge unde va fi mai evident la a doua iterație, după Pasul 3. Pentru prima trecere, grupați-le logic ca în exemplul de mai sus.
Cea mai bună referință despre „ce merge unde” și „ce face ce” este, fără îndoială, cartea Game Engine Architecture de Jason Gregory.
Începeți să grupați funcționalitatea. Începeți să vă gândiți la modalități de a le combina. Nu aveți nevoie de Camera->rotateYaw(float yaw)
și Camera->rotatePitch(float pitch)
dacă le puteți combina în Camera->rotate(float yaw, float pitch)
. Păstrați-o simplă. Prea multă funcționalitate (nu uitați, feature creep) vă va face rău mai târziu.
Gândiți-vă la ce funcționalitate trebuie să fie expusă public și ce funcționalitate trebuie să stea doar în interiorul sistemului însuși. De exemplu, Renderer-ul dvs. trebuie să sorteze toate Sprites transparente înainte de a le desena. Cu toate acestea, funcția de sortare a acestor sprites nu trebuie să fie expusă. Știți că trebuie să sortați sprites transparente înainte de desenare, nu aveți nevoie de un sistem extern care să vă spună acest lucru.
Pasul 3. Arhitectura (Sau, articolul propriu-zis)
Am fi putut la fel de bine să începem articolul aici. Aceasta este partea interesantă și importantă.
Una dintre cele mai simple arhitecturi posibile pe care le poate avea motorul dumneavoastră este să puneți fiecare Sistem într-o Clasă și să aveți bucla principală a jocului care să cheme subrutinele acestora. Ar putea arăta cam așa:
while(isRunning)
{
Input->readInput();
isRunning = GameLogic->doLogic();
Camera->update();
World->update();
GUI->update();
AI->update();
Audio->play();
Render->draw();
}
Pare perfect rezonabil la început. Aveți toate elementele de bază acoperite, Input -> procesare Input -> Output.
Și, într-adevăr, acest lucru va fi suficient pentru un Joc simplu. Dar va fi o pacoste de întreținut. Motivul ar trebui să fie evident: dependențele.
Care sistem trebuie să comunice cu alte sisteme într-un fel sau altul. Noi nu avem nici un mijloc de a face acest lucru în bucla noastră de joc de mai sus. Prin urmare, exemplul indică în mod clar, că fiecare Sistem trebuie să aibă o anumită Referință a celorlalte Sisteme pentru a face ceva semnificativ. Interfața grafică și logica de joc trebuie să știe ceva despre intrările noastre. Renderer-ul nostru trebuie să știe ceva despre logica noastră de joc pentru a afișa ceva semnificativ.
Acest lucru va duce la această minune arhitecturală:
Dacă miroase a spaghete, sunt spaghete. Categoric nu este ceea ce ne dorim. Da, este ușor și rapid de codat. Da, vom avea rezultate acceptabile. Dar mentenabil, nu este. Schimbați o bucățică de cod undeva și aceasta ar putea avea efecte devastatoare asupra tuturor celorlalte sisteme fără ca noi să știm.
În plus, va exista întotdeauna cod la care multe Sisteme vor avea nevoie de acces. Atât GUI cât și Renderer trebuie să facă apeluri de desenare sau cel puțin să aibă acces la un fel de interfață care să se ocupe de acest lucru pentru noi. Da, am putea doar să dăm fiecărui sistem puterea de a apela direct funcțiile OpenGL / DirectX, dar vom ajunge la o mulțime de redundanțe.
Am putea rezolva acest lucru prin colectarea tuturor funcțiilor de desenare în interiorul sistemului Renderer și apelarea acestora din sistemul GUI. Dar atunci Sistemul de randare va avea funcții specifice pentru GUI. Acestea nu își au locul în Renderer și, prin urmare, este contrar pasului 1 și 2. Decizii, Decizii.
Deciziile, Deciziile.
Deciziile, Deciziile.
Lasagna de motor
Lasagna este mai bună decât Spaghetti. Cel puțin din punct de vedere al programării. Rămânând la exemplul nostru de Renderer, ceea ce ne dorim este să apelăm funcțiile OpenGL / DirectX fără a le apela direct în System. Acest lucru miroase a Wrapper. Și, în mare parte, așa este. Colectăm toate funcționalitățile de desenare în interiorul unei alte clase. Aceste clase sunt chiar mai simple decât sistemele noastre. Să numim aceste clase noi Framework.
Ideea din spatele acestui lucru este de a abstractiza multe dintre apelurile API de nivel scăzut și de a le forma în ceva adaptat jocului nostru. Nu vrem să setăm bufferul de vertexuri, să setăm bufferul de indici, să setăm texturile, să activăm asta, să dezactivăm asta doar pentru a face un simplu apel de desen în sistemul nostru de redare. Să punem toate aceste lucruri de nivel scăzut în cadrul nostru. Și voi numi această parte a cadrului „Draw”. De ce? Ei bine, tot ceea ce face este să pregătească totul pentru desen și apoi să deseneze. Nu-i pasă ce desenează, unde desenează, de ce desenează. Acest lucru este lăsat pe seama sistemului de randare.
Acesta ar putea părea un lucru ciudat, vrem viteză în motorul nostru, nu? Mai multe straturi de abstractizare = mai puțină viteză.
Și ați avea dreptate, dacă am fi în anii ’90. Dar avem nevoie de mentenabilitate și putem trăi cu o pierdere de viteză abia sesizabilă pentru majoritatea părților.
Cum ar trebui atunci să fie proiectat cadrul nostru de desenare? Simplu spus, ca propriul nostru mic API. SFML este un mare exemplu în acest sens.
Cele importante de care trebuie să ținem cont:
- Păstrați-l bine documentat. Ce funcții avem? Când pot fi apelate? Cum sunt apelate?
- Păstrați-l simplu. Funcții ușoare precum drawMesh(Mesh* oMesh) sau loadShader(String sPath) vă vor face fericiți pe termen lung.
- Păstrați-o funcțională. Nu fiți prea specific. în loc de
drawButtonSprite
, aveți odrawSprite
funcție și lăsați apelantul să se ocupe de restul.
Ce câștigăm? Multe:
- Nu trebuie să ne configurăm cadrul nostru decât o singură dată și îl putem folosi în fiecare sistem de care avem nevoie (GUI, Renderer….)
- Putem schimba cu ușurință API-urile de bază dacă vrem, fără a rescrie fiecare sistem. Să trecem de la OpenGL la DirectX? Nicio problemă, doar rescrieți clasa Framework.
- Aceasta menține codul din sistemele noastre curat și strâns.
- Având o interfață bine documentată înseamnă că o persoană poate lucra la Framework, în timp ce o altă persoană lucrează în stratul de sistem.
Probabil că vom ajunge la ceva de genul acesta:
Regula mea de bază cu privire la ceea ce intră în Framework este destul de simplă. Dacă am nevoie să apelez o bibliotecă externă (OpenGL, OpenAL, SFML…) sau am structuri de date / algoritmi de care fiecare sistem are nevoie, ar trebui să fac asta în Framework.
Acum avem primul strat de lasagna făcut. Dar încă mai avem această minge uriașă de Spaghetti deasupra. Să o abordăm în continuare.
Mesagerie
Marea problemă rămâne totuși. Sistemele noastre sunt încă toate interconectate. Noi nu vrem asta. Există o multitudine de modalități de abordare a acestei probleme. Evenimente, mesaje, clase abstracte cu pointeri de funcție (cât de ezoteric)…
Să rămânem la mesaje. Acesta este un concept simplu care este încă foarte popular în programarea GUI. Este, de asemenea, foarte potrivit ca un exemplu ușor pentru Motorul nostru.
Funcționează ca un serviciu poștal. Compania A trimite un mesaj către compania B și cere să se facă ceva. Aceste companii nu au nevoie de o conexiune fizică. Compania A presupune pur și simplu că compania B va face acest lucru la un moment dat. Dar, deocamdată, companiei A nu-i prea pasă când sau cum o face compania B. Trebuie doar să o facă. La naiba, compania B ar putea chiar să decidă să redirecționeze mesajul către companiile C și D și să le lase pe acestea să se ocupe de el.
Potăm merge un pas mai departe, compania A nici măcar nu trebuie să îl trimită cuiva anume. Compania A pur și simplu postează scrisoarea și oricine se simte responsabil o va procesa. În acest fel, companiile C și D pot procesa direct cererea.
Evident, companiile sunt egale cu Sistemele noastre. Să ne uităm la un exemplu simplu:
- Framework notifică sistemul de intrare că a fost apăsată tasta „A”
- Input traduce că apăsarea tastei „A” înseamnă „Open Inventory” și trimite un mesaj care conține „Open Inventory”
- GUI se ocupă de mesaj și deschide fereastra de inventar
- Logica jocului se ocupă de mesaj și pune jocul în pauză
- Dacă optați pentru un motor de tip Message Bus, gândiți-vă să codificați mai întâi consola și Message Bus. Odată ce acestea sunt implementate, puteți falsifica existența oricărui sistem care nu a fost încă codificat. Veți avea un control constant asupra întregului motor în fiecare etapă de dezvoltare.
- Considerați să treceți în continuare la GUI, precum și la funcționalitatea de desenare necesară în cadrul framework-ului. O interfață grafică solidă împerecheată cu Consola vă va permite să falsificați și mai ușor toate celelalte Sisteme. Testarea va fi floare la ureche.
- În continuare ar trebui să fie Cadrul, cel puțin interfața acestuia. Funcționalitatea poate urma mai târziu.
- În cele din urmă, treceți la celelalte Sisteme, inclusiv Gameplay.
.
Inputului nici măcar nu-i pasă ce se face cu mesajul său. GUI nu-i pasă că și Game Logic procesează același mesaj. Dacă ar fi toate cuplate, Input ar trebui să apeleze o funcție din sistemul GUI și o funcție din Game Logic. Dar nu mai este nevoie să o facă. Am reușit să decuplați cu succes acest lucru folosind Mesaje.
Cum arată un Mesaj? Ar trebui să aibă cel puțin un anumit Tip. De exemplu, deschiderea inventarului ar putea fi un enum numit OPEN_INVENTORY
. Acest lucru este suficient pentru Mesaje simple ca acesta. Mesajele mai avansate care trebuie să includă date vor avea nevoie de o modalitate de a stoca aceste date. Există o multitudine de moduri de a realiza acest lucru. Cel mai ușor de implementat este utilizarea unei structuri simple de hartă.
Dar cum trimitem Mesaje? Printr-o magistrală de mesaje, desigur!
Nu-i așa că este frumos? Gata cu spaghetele, doar Lasagna bună și simplă. Am pus în mod deliberat logica noastră de joc pe partea cealaltă a autobuzului de mesaje. După cum puteți vedea, nu are nicio conexiune cu stratul Framework. Acest lucru este important pentru a evita orice tentație de a „apela doar acea funcție”. Credeți-mă, veți dori să o faceți mai devreme sau mai târziu, dar acest lucru ar distruge proiectul nostru. Avem destule sisteme care au de-a face cu Framework-ul, nu este nevoie să facem asta în logica noastră de joc.
Busul de mesaje este o clasă simplă cu referințe la fiecare sistem. Dacă are un mesaj în coada de așteptare, Busul de mesaje îl postează către fiecare sistem printr-un simplu apel handleMessage(Msg msg)
. În schimb, fiecare sistem are o referință la Message Bus pentru a posta mesaje. Aceasta poate fi, evident, stocată intern sau transmisă ca argument al unei funcții.
Toate Sistemele noastre trebuie, prin urmare, să moștenească sau să aibă următoarea formă:
class System
{
public:
void handleMessage(Msg *msg);
{
switch(msg->type)
{
//// Example
//case Msg::OPEN_INVENTORY:
// break;
}
}
private:
MessageBus *msgBus;
//// Usage: msgBus->postMessage(msg);
}
(Da, da, pointerii brute…)
Dintr-o dată, bucla noastră de joc se schimbă pentru a lăsa pur și simplu Busul de Mesaje să trimită în jur de Mesaje. Va trebui în continuare să actualizăm periodic fiecare Sistem prin intermediul unei forme de apel update()
. Dar comunicarea va fi gestionată în mod diferit.
Cu toate acestea, la fel ca și în cazul cadrelor noastre, utilizarea Mesajelor creează costuri suplimentare. Acest lucru va încetini un pic motorul, să nu ne facem iluzii. Dar nu ne pasă! Vrem un Design curat și simplu. O arhitectură curată și simplă!
Și cea mai tare parte? Primim lucruri uimitoare pe gratis!
Consola
Care mesaj este în mare parte un apel de funcție. Și fiecare Mesaj este trimis cam peste tot! Ce-ar fi dacă am avea un Sistem care să tipărească pur și simplu fiecare Mesaj trimis într-o fereastră de ieșire? Și dacă acest Sistem poate trimite și Mesajele pe care le scriem în acea fereastră?
Da, tocmai am dat naștere unei Console. Și tot ce ne-a luat sunt câteva linii de cod. Mintea mea a fost spulberată cu mult timp în urmă când am văzut pentru prima dată acest lucru în acțiune. Nici măcar nu este legată de ceva, pur și simplu există.
O consolă este în mod evident foarte utilă în timpul dezvoltării jocului și putem pur și simplu să o scoatem în Release, dacă nu dorim ca Jucătorul să aibă acest tip de acces.
Cinematograme în joc, Replay-uri & Depanare
Și dacă falsificăm Mesaje? Ce-ar fi dacă am crea un nou Sistem care pur și simplu trimite Mesaje la un anumit Timp? Imaginați-vă că trimite ceva de genul MOVE_CAMERA
, urmat de ROTATE_OBJECT
.
Și iată, avem In-Game Cinematics.
Și dacă pur și simplu înregistrăm mesajele de intrare care au fost trimise în timpul jocului și le salvăm într-un fișier?
Și iată, avem Replays.
Ce-ar fi dacă am înregistra pur și simplu tot ceea ce face jucătorul, iar atunci când jocul se blochează, să ne trimită aceste fișiere de date?
Și iată, avem o copie exactă a acțiunilor jucătorului care au dus la blocarea jocului.
Multi-Threading
Multi-Threading? Da, Multi-Threading. Am decuplat toate sistemele noastre. Acest lucru înseamnă că își pot procesa Mesajele oricând doresc, oricum doresc și, cel mai important, oriunde doresc. Putem face ca magistrala noastră de mesaje să decidă pe ce fir fiecare sistem trebuie să proceseze un mesaj -> Multi-Threading
Frame Rate Fixing
Avem prea multe mesaje pentru a procesa acest cadru? Nicio problemă, hai să le păstrăm în coada de așteptare a autobuzului de mesaje și să le trimitem la următorul Cadru. Acest lucru ne va oferi posibilitatea de a ne asigura că jocul nostru rulează la o rată de 60 FPS fără probleme. Jucătorii nu vor observa că AI-ul are nevoie de câteva cadre în plus pentru a „gândi”. Ei vor observa însă scăderi ale Frame Rate-ului.
Mesajele sunt cool.
Este important să documentăm meticulos fiecare Mesaj și parametrii acestuia. Tratați-l ca pe un API. Dacă faceți acest lucru corect, fiecare dezvoltator poate lucra la diferite Sisteme fără să strice nimic. Chiar dacă un Sistem ar trebui să fie offline sau în Construcție, Jocul va rula în continuare și poate fi testat. Nu aveți un sistem audio? Nu-i nimic, tot avem sistemul vizual. Fără Renderer, e în regulă, putem folosi Consola…
Dar Mesajele nu sunt perfecte. Din păcate.
Câteodată, chiar vrem să știm rezultatul unui Mesaj. Uneori chiar avem nevoie ca acestea să fie procesate imediat. Trebuie să găsim opțiuni viabile. O soluție în acest sens este de a avea un Speedway. În afară de o simplă postMessage
funcție, putem implementa o postImmediateMessage
funcție care să fie procesată imediat. Gestionarea Mesajelor de returnare este mult mai ușoară. Acestea sunt trimise mai devreme sau mai târziu la handleMessage
funcția noastră. Trebuie doar să ne amintim acest lucru atunci când trimitem un Mesaj.
Mesajele imediate strică în mod evident Multi-Threading și Frame Rate Fixing dacă sunt făcute în exces. Este deci vital să vă restricționați pentru a limita utilizarea lor.
Dar cea mai mare Problemă cu acest Sistem este latența. Nu este cea mai rapidă Arhitectură. Dacă lucrați la un First Person Shooter cu timpi de răspuns de tip Twitch, acest lucru ar putea fi o problemă.
Înapoi la proiectarea arhitecturii noastre
Am decis să folosim Sisteme și un Bus de mesaje. Știm exact cum vrem să ne structurăm motorul.
Este timpul pentru Pasul 4 al procesului nostru de proiectare. Iterarea. Este posibil ca unele funcții să nu încapă în niciun Sistem, trebuie să găsim o soluție. Unele funcții trebuie să fie apelate pe scară largă și ar bloca Busul de Mesaje, trebuie să găsim o soluție.
Acest lucru necesită timp. Dar merită pe termen lung.
Este în sfârșit timpul să Codăm!
Pasul 4. De unde să începi să codifici?
Înainte de a începe să codificați, citiți cartea/articolul Game Programming Patterns de Robert Nystrom.
În afară de asta, am schițat o mică foaie de parcurs pe care ați putea să o urmați. Nu este nici pe departe cea mai bună cale, dar este productivă.
Vă veți da seama, redarea efectivă a ceva legat de Gameplay ar putea fi ultimul lucru pe care îl faceți. Și acesta este un lucru bun! Se va simți mult mai satisfăcător și vă va menține motivat să terminați ultimele retușuri ale Motorului dumneavoastră.
Designerul dumneavoastră de joc ar putea totuși să vă împuște în timpul acestui proces. Testarea Gameplay-ului prin comenzi de consolă este la fel de amuzantă ca și cum ai juca Counter Strike prin IRC.
Concluzie
Treceți-vă timpul pentru a găsi o Arhitectură solidă și rămâneți cu ea! Acesta este sfatul pe care sper să îl rețineți din acest articol. Dacă faceți acest lucru, veți putea să construiți un Motor perfect bun și ușor de întreținut la sfârșitul zilei. Sau secolul.
Personal, îmi place mai mult să scriu Motoare decât să fac toate acele lucruri legate de Gameplay. Dacă aveți întrebări, nu ezitați să mă contactați prin Twitter @Spellwrath. În prezent, termin un alt Engine folosind metodele pe care le-am descris în acest articol.
Puteți găsi partea 2 aici.
.