Acest articol reprezintă o imagine de ansamblu la nivel înalt pentru crearea unui joc JRPG (Jocul de roluri japonez), cum ar fi jocurile Final Fantasy. Ne vom uita la arhitectura și sistemele care alcătuiesc scheletul unui JRPG, cum să gestionăm modurile de joc, cum să folosim tilemaps pentru a afișa lumea și cum să codificăm un sistem de luptă RPG.
Notă: Acest articol este scris folosind un limbaj de cod pseudo-cod, dar conceptele sunt aplicabile în orice mediu de dezvoltare a jocului.
În 1983, Yuji Horii, Koichi Nakamura și Yukinobu Chida au zburat în America și au participat la AppleFest '83, o adunare de dezvoltatori care și-a prezentat ultimele creații pentru Apple II. Au fost suflate de cea mai recentă versiune a unui RPG numit Wizardry.
La întoarcerea în Japonia, au decis să creeze Dragon Warrior, un RPG similar, dar raționalizat pentru SEN. A fost un succes masiv, definind genul JRPG. Dragon Warrior nu a mers atât de bine în America, dar câțiva ani mai târziu a avut loc un alt joc.
În 1987, originalul Final Fantasy a fost lansat, producând una dintre cele mai bine vândute francize de jocuri video de pe Pământ, care a devenit, cel puțin în Occident, iconicul JRPG.
Genurile de jocuri nu sunt definite niciodată cu precizie - sunt mai mult o colecție fuzzy de convenții. RPG-urile tind să aibă un sistem de nivelare, unul sau mai mulți personaje de jucători cu abilități și statistici, arme și armuri, moduri de luptă și explorare și narațiuni puternice; progresul jocului este adesea realizat prin avansarea pe o hartă.
RPG-urile japoneze sunt RPG-uri create în matrița Dragon Warrior; ele sunt mai liniare, lupta este adesea bazată pe turn și există de obicei două tipuri de hărți: o hartă a lumii și o hartă locală. JRPG-urile arhetipale includ Dragon Warrior, Final Fantasy, arme sălbatice, Star fantastice și declanșator de crono. Tipul de JRPG despre care vom vorbi în acest articol este unul asemănător unui Final Fantasy Final.
Jocuri precum Final Fantasy VI și Chrono Trigger sunt încă foarte plăcute pentru a juca. Dacă faci un JRPG, înveți un format de joc pe care jucătorii moderni încă îl mai recepționează. Acestea fac ca un cadru minunat să vă adauge propriul dvs. răsucire și experiment - fie în narațiune, prezentare sau mecanică. Este un lucru minunat dacă puteți face un joc care este încă jucat și sa bucurat de decenii după prima lansare!
Call of Duty, unul dintre cele mai populare jocuri FPS din lume, folosește elemente RPG; boom-ul social al jocurilor din jurul Farmville era în principiu o clonă a Lunii Harvest SNPG RPG; și chiar jocuri de curse precum Gran Turismo au nivele și experiență.
În măsura în care un scriitor ar putea fi intimidat de o foaie de hârtie goală, un dezvoltator de jocuri se poate paraliza de numărul mare de posibile alegeri atunci când proiectează un nou joc. Cu un JRPG au fost alese multe dintre alegeri, deci nu ai paralizia alegerii, ești liber să urmezi convențiile pentru majoritatea deciziilor și să devii de la convenție la punctele care contează pentru tine.
Final Fantasy a fost aproape în întregime codificată de un singur programator, Nasir Gebelli, și el a făcut-o în asamblare! Cu instrumente și limbi moderne, este mult mai ușor să creați acest tip de joc. Cea mai mare parte a majorității RPG-uri nu este programarea conținutului - dar acest lucru nu trebuie să fie cazul pentru jocul dvs. Dacă îl formați înapoi puțin în conținut și vă concentrați asupra calității peste cantitate, atunci un JRPG este un mare proiect solo.
Dacă ai o echipă care te poate ajuta cu orice joc și poate vrei să externalizezi arta și muzica sau să folosești unele dintre excelentele bunuri de creație comună din locuri precum opengameart.org. (Notă editorului: site-ul nostru sursă GraphicRiver vinde și foi de sprite.)
JRPG-urile au un program dedicat următor și un număr de JRPG-uri indie (cum ar fi cele prezentate mai jos) au făcut bine comercial și sunt disponibile pe platforme precum Steam.
JRPG-urile împărtășesc atât de multe convenții și mecanisme încât este posibil să spargeți un JRPG tipic într-un număr de sisteme:
În dezvoltarea de software, un model este văzut de peste și peste din nou: Stratificarea. Acest lucru se referă la modul în care sistemele unui program se construiesc unul peste celălalt, cu straturi aplicabile la scară largă și cu straturi, care se ocupă mai mult de problema în mână, aproape de partea de sus. JRPG-urile nu sunt diferite și pot fi văzute ca un număr de straturi - straturile inferioare se ocupă cu funcțiile grafice de bază și straturile superioare se ocupă de quest-uri și statistici de caractere.
Bacsis: Atunci când dezvoltați un nou sistem, este mai bine să începeți prin crearea mai întâi a straturilor inferioare și apoi trecerea stratului după strat în partea de sus. Folosind middleware-ul vă ajută să renunțați la mai multe straturi joase comune pentru multe jocuri. Pe diagrama de arhitectură de mai sus, toate straturile de sub linia punctată sunt tratate de un motor de joc 2D.După cum puteți vedea din diagrama de arhitectură de mai sus, există o mulțime de sisteme care alcătuiesc un JRPG, dar majoritatea sistemelor pot fi grupate împreună ca separate moduri a jocului. JRPG-urile au moduri foarte diferite de joc; au o hartă a lumii, o hartă locală, un mod de luptă și mai multe moduri de meniu. Aceste moduri sunt aproape complet separate, separate de coduri, făcând fiecare să fie ușor de dezvoltat.
Modurile sunt importante, dar ar fi inutile fără conținut de joc. Un RPG conține multe fișiere hărți, definiții de monștri, linii de dialog, scripturi pentru a rula tăieturi și cod de joc pentru a controla progresul jucătorului. Cum să construim în detaliu un JRPG ar umple o carte întreagă, așa că ne vom concentra pe unele dintre cele mai importante părți. Manipularea cu ușurință a modurilor de joc este esențială pentru a produce un JRPG gestionabil, deci acesta este primul sistem pe care îl vom explora.
Imaginea de mai jos arată că bucla de joc este pompată, numind o funcție de actualizare în fiecare cadru. Aceasta este bătăile inimii jocului și aproape toate jocurile sunt structurate în acest fel.
Ați început vreodată un proiect, dar a fost blocat deoarece ați fost prea greu să adăugați noi caracteristici sau ați fost afectați de bug-uri misterioase? Poate că ați încercat să vă strângeți tot codul în funcția de actualizare cu puțină structură și ați descoperit că codul a devenit o mizerie criptică. O soluție excelentă la aceste tipuri de probleme este de a separa codul de la altul stadiile de joc, oferind o imagine mult mai clară asupra a ceea ce se întâmplă.
Un instrument comun pentru gamedev este mașină de stat; este folosit peste tot, pentru manipularea animațiilor, meniurilor, fluxului de jocuri, AI ... este un instrument esențial de a avea în kit-ul nostru. Pentru JRPG putem folosi o mașină de stat pentru a face față diferitelor moduri de joc. Vom arunca o privire la o mașină normală de stat și apoi o vom amesteca puțin, pentru ao face mai potrivită pentru JRPG. Dar mai întâi să luăm puțin timp să luăm în considerare fluxul general de jocuri prezentat mai jos.
Într-un JRPG tipic, veți începe, probabil, în modul jocului local, liber să rătăciți în jurul unui oraș și să interacționați cu locuitorii săi. Din oraș puteți pleca - aici veți intra într-un alt mod de joc și veți vedea harta lumii.
Harta lumii acționează foarte mult ca harta locală, dar la o scară mai largă; puteți vedea munți și orașe, în loc de copaci și garduri. În timp ce pe harta lumii, dacă vă întoarceți în oraș, modul va reveni la harta locală.
Fie în harta lumii, fie în harta locală, puteți să aduceți un meniu pentru a verifica personajele dvs. și, uneori, pe harta lumii, veți fi aruncate în luptă. Diagrama de mai sus descrie aceste moduri de joc și tranziții; acesta este fluxul de bază al gameplay-ului JRPG și este ceea ce vom crea statele noastre de joc de la.
O mașină de stat, în scopurile noastre, este o bucată de cod care deține toate modurile diferite ale jocurilor noastre, care ne permite să trecem de la un mod la altul și că actualizările și redarea oricăror moduri curente.
În funcție de limba de implementare, o mașină de stat constă, de obicei, dintr-un a StateMachine
clasă și o interfață, IState
, pe care toate statele să le pună în aplicare.
O mașină de stat este cel mai bine descrisă prin schițarea unui sistem de bază în pseudocod:
clasa StateMachine MapmStates = hartă nouă (); IState mCurrentState = EmptyState; public void Actualizare (float elapsedTime) mCurrentState.Update (elapsedTime); void public Render () mCurrentState.Render (); public void Schimbare (String stateName, optional var params) mCurrentState.OnExit (); mCurrentState = mStatele [stateName]; mCurrentState.OnEnter (params); public void Adăugați (numele șirului, statul IState) mStates [name] = state;
Acest cod de mai sus prezintă o mașină de stare simplă fără verificarea erorilor.
Să ne uităm la modul în care codul mașinii de stat de mai sus este folosit într-un joc. La începutul jocului a StateMachine
va fi creat, toate stările diferite ale jocului adăugate și setul inițial de stare. Fiecare stat este identificat în mod unic de a Şir
nume care se utilizează atunci când se apelează funcția de modificare a stării. Există doar o stare actuală, mCurrentState
, și este redat și actualizat fiecare buclă de joc.
Codul ar putea să arate astfel:
StateMachine gGameMode = new StateMachine (); // O stare pentru fiecare mod de joc gGameMode.Add ("mainmenu", noul MainMenuState (gGameMode)); gGameMode.Add ("localmap", noul LocalMapState (gGameMode)); gGameMode.Add ("worldmap", noul WorldMapState (gGameMode)); gGameMode.Add ("luptă", noul BattleState (gGameMode)); gGameMode.Add ("ingamemenu", noul InGameMenuState (gGameMode)); gGameMode.Change ( "MainMenu"); // Actualizarea jocului principal Bucl public void Update () float elapsedTime = GetElapsedFrameTime (); gGameMode.Update (elapsedTime); gGameMode.Render ();
În acest exemplu, creăm toate stările necesare, adăugăm-le la StateMachine
și setați starea de pornire în meniul principal. Dacă am executat acest cod MainMenuState
ar fi prestate și actualizate mai întâi. Acesta reprezintă meniul pe care îl vedeți în majoritatea jocurilor când porniți prima dată, cu opțiuni cum ar fi Incepe jocul și Incarca jocul.
Când un utilizator selectează Incepe jocul, MainMenuState
sună ceva de genul gGameMode.Change ("localmap", "map_001")
si LocalMapState
devine noua stare actuală. Această stare va actualiza și reda harta, permițând jucătorului să înceapă explorarea jocului.
Diagrama de mai jos prezintă o vizualizare a unei mașini de stare care se deplasează între WorldMapState
și BattleState
. Într-un joc acest lucru ar echivala cu un jucător care se rătăcește în jurul lumii, fiind atacat de monștri, intră în modul de luptă și apoi se întoarce pe hartă.
Să aruncăm o privire rapidă la interfața de stat și la o EmptyState
clasa care o implementează:
interfața publică IState public virtual void Update (float elapsedTime); public void virtuale Render (); public virtual void OnEnter (); public void virtual OnExit (); public EmptyState: IState public void Update (float elapsedTime) // Nimic de actualizat în stare goală. public void Render () // Nimic de redat în stare goală void public OnEnter () // Nu se ia nici o acțiune când statul este introdus void public OnExit () // Nu se ia nici o acțiune când statul este ieșit
Interfața IState
cere ca fiecare stat să aibă patru metode înainte de a putea fi folosit ca stat în mașina de stat: Actualizați()
, Face()
, OnEnter ()
și OnExit ()
.
Actualizați()
și Face()
se numesc fiecare cadru pentru starea activă curentă; OnEnter ()
și OnExit ()
sunt numite atunci când se schimbă starea. În afară de asta este destul de simplu. Acum știi că poți crea tot felul de stări pentru toate părțile diferite ale jocului tău.
Asta e mașina de bază. Este util pentru multe situații, dar când ne confruntăm cu modurile de joc, putem să le îmbunătățim! Cu sistemul actual, schimbarea stării poate avea o mulțime de cheltuieli - uneori când se schimbă la a BattleState
Vom vrea să părăsim WorldState
, conduceți bătălia și apoi reveniți la WorldState
în configurația exactă a fost înainte de bătălie. Acest tip de operațiune poate fi stângace folosind mașina de stat standard pe care am descris-o. O soluție mai bună ar fi să utilizați a grămadă de state.
Putem comuta mașina standard de stare într-o stivă de stări, după cum se arată în diagrama de mai jos. De exemplu, MainMenuState
este împins în primul rând la stivă, la începutul jocului. Când începem un joc nou, LocalMapState
este împins peste asta. În acest moment MainMenuState
nu mai este redat sau actualizat, dar așteaptă în jur, pregătit pentru noi să ne întoarcem.
În continuare, dacă începem o bătălie, BattleState
este împins în partea de sus; când bătălia se termină, este scoasă din stack și putem relua pe hartă exact unde am rămas. Dacă vom muri în joc atunci LocalMapState
este expediat și ne întoarcem MainMenuState
.
Diagrama de mai jos oferă o vizualizare a unui stack de stare, care arată InGameMenuState
fiind apăsat pe stivă și apoi scos.
Acum avem o idee despre modul în care stack-ul funcționează, să examinăm un cod care să-l implementeze:
public class StateStack HartamStates = hartă nouă (); Listă mStack = Listă (); public void Actualizare (float elapsedTime) IState top = mStack.Top () top.Update (elapsedTime) void public Render () IState top = mStack.Top IState state = mStates [nume]; mStack.Push (stat); publicul IState Pop () return mStack.Pop ();
Acest cod stack de mai sus nu are nicio verificare a erorilor și este destul de simplu. Stările pot fi împinse pe stivă folosind Apăsați()
apela și a ieșit cu a Pop ()
apel, iar starea de pe partea superioară a stiva este cea actualizată și redată.
Folosirea unei abordări bazate pe stiva este bună pentru meniuri și, cu o mică modificare, poate fi folosită și pentru casete de dialog și notificări. Dacă vă simțiți aventuros, puteți combina ambele și aveți o mașină de stat care susține, de asemenea, stive.
Utilizarea StateMachine
, StateStack
, sau o combinație a celor două, creează o structură excelentă pentru a vă construi RPG-ul.
MenuMenuState
și GameState
moștenirea de la IState
.
Hărți descriu lumea; deserturile, navele spațiale și junglele pot fi reprezentate folosind o hartă de tile. O tabelă este o modalitate de a utiliza un număr limitat de imagini mici pentru a crea o imagine mai mare. Diagrama de mai jos vă arată cum funcționează:
Diagrama de mai sus are trei părți: paleta de țigle, o vizualizare a modului în care este construită harta țiglelor și harta finală afișată pe ecran.
Paleta de plăci este o colecție de toate dalele folosite pentru a crea o hartă. Fiecare țiglă din paletă este identificată în mod unic printr-un număr întreg. De exemplu, numărul 1 este iarba; observați locurile în care este folosit în vizualizarea tilemap.
O tabelă este doar o serie de numere, fiecare număr referindu-se la o piesă din paletă. Dacă vrem să facem o hartă plină de iarbă, am putea avea doar o gamă largă, umplută cu numărul 1, și când am făcut aceste plăci am vedea o hartă de iarbă formată din multe plăci de iarbă mici. Paleta de placi este, de obicei, încărcată ca o textura mare care conține mai multe plăci mai mici, dar fiecare intrare din paletă ar putea fi la fel de ușor ca propriul fișier grafic.
Bacsis: De ce să nu folosiți o matrice de tablouri pentru a reprezenta harta țiglelor? Prima matrice ar putea reprezenta printr-o serie de rânduri de dale.Motivul pentru care nu facem acest lucru este doar pentru simplitate și eficiență. Dacă aveți o mulțime de numere întregi, acesta este un bloc continuu de memorie. Dacă aveți o matrice de matrice, atunci acesta este un bloc de memorie pentru prima matrice care conține pointeri, cu fiecare indicatorul îndreptat către un rând de gresie. Această indirecție poate încetini lucrurile - și din moment ce desenăm harta în fiecare cadru, cu atât mai repede cu atât mai bine!
Să ne uităm la un cod pentru a descrie o hartă de țigle:
// // Realizează o hartă de textură a mai multor plăci și o sparge în // imagini individuale de 32 x 32. // Matricea finală va arăta astfel: // gTilePalette [1] = Imaginea // Prima noastră țiglă de iarbă // gTilePalette [2] = Imagine // Varianta a doua placă de iarbă // ... // gTilePalette [15] = Imagine // Placă de piatră și de iarbă // Array gTilePalette = SliceTexture ("grass_tiles.png", 32, 32) gMap1Width = 10 gMap1Height = 10 Array gMap1Layer1 = Array nou () [2, 2, 7, 3, 11, 11, 11, 12, 2, 2, 1, 1, 10, 11, 2, 1, 13, 5, 11, 11, 11, 4, 8, 2, 1, 2, 1, 10, 11, 11, 11, 11, 11, 11, 11, 11, 4, 13, 14, 15, 11, 11, 11, 11, 11, 11, 11, 11, 2, 2, 2, 2, 2, 2, 11, 11, 11, 11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
Comparați codul de mai sus cu diagrama și este destul de clar cum se construiește o tablă de tile dintr-o mică serie de plăci. Odată ce o hartă este descrisă astfel, putem scrie o funcție de randare simplă pentru ao desena pe ecran. Detaliile exacte ale funcției se vor schimba în funcție de funcțiile de configurare a ferestrei și de desen. Funcția noastră de redare este prezentată mai jos.
static int TilePixelSize = 32; // Elaborează o mapă de la stânga sus, în poziția pixelului x, y // x, y - Poziția pixelului harta va fi redată de la // map - harta de randare // lățime - lățimea hărții în dale public void RenderMap (int x, int y, harta array, int mapWidth) // Începeți prin indexarea celei mai de sus stânga a celei mai multe coloane int tileColumn = 1; int tileRow = 1; pentru (int i = 1; map.Count (); i ++) // Minus 1 astfel încât prima țiglă desenează la 0, 0 int pixelPosX = x + (tileColumn - 1) * TilePixelSize; int pixelPosY = y * (tileRow - 1) * TilePixelSize; RenderImage (x, y, gTilePalette [gMap1Layer1 [i]]); // Advance la următorul tigla țiglă Column + = 1; dacă (tileColumn> mapWidth) tileColumn = 1; tileRow + = 1; - Cum se utilizează în buclă de actualizare principală void public Update () // Efectuați de fapt o hartă pe ecran RenderMap (0, 0, gMap1Layer1, gMap1Width)
Harta pe care am folosit-o până acum este destul de fundamentală; cele mai multe JRPG-uri vor folosi mai multe straturi de tilemaps pentru a crea scene mai interesante. Diagrama de mai jos prezintă prima noastră hartă, cu adăugarea a încă trei straturi, rezultând o hartă mult mai plăcută.
După cum am văzut mai devreme, fiecare tabelă de tile este doar o serie de numere și, prin urmare, o hartă cu straturi complete poate fi făcută dintr-o serie de astfel de matrice. Desigur, redarea tabloului de imagini este într-adevăr doar primul pas în adăugarea explorării jocului; hărțile trebuie, de asemenea, să aibă informații despre coliziune, suport pentru entitățile în mișcare și utilizarea interactivității de bază declanșatoare.
Un declanșator este o bucată de cod care se declanșează numai atunci când jucătorul îl declanșează prin efectuarea unor acțiuni. Există o mulțime de acțiuni pe care un declanșator le-ar putea recunoaște. De exemplu, mutarea caracterului jucătorului pe o țiglă poate declanșa o acțiune - acest lucru se întâmplă de obicei atunci când se deplasează pe o ușă, pe un teleport sau pe o țiglă de hartă. Declanșatoarele pot fi plasate pe aceste dale pentru a teleporta caracterul spre o hartă interioară, o hartă a lumii sau o hartă locală aferentă.
Un alt declanșator ar putea depinde de apăsarea butonului "utilizare". De exemplu, dacă jucătorul merge la un semn și apasă "folosiți", atunci se declanșează un declanșator și se afișează o casetă de dialog care arată textul semnului. Declanșatoarele sunt folosite peste tot pentru a ajuta la îmbinarea hărților împreună și pentru a oferi interactivitate.
JRPG-urile au adesea o mulțime de hărți destul de detaliate și complicate, așadar vă recomand să nu încercați să le faceți manual, este o idee mult mai bună de a utiliza un editor de tilemap. Aveți posibilitatea să utilizați una dintre soluțiile excelente existente sau să vă răsturnați. Dacă doriți să încercați un instrument existent, atunci recomand cu certitudine să verificați cu plăci în formă de piatră, care este instrumentul pe care l-am folosit pentru a crea aceste hărți de exemplu.
postări asemănatoareÎn cele din urmă, la luptă! Ce bine este un JRPG fără luptă? Combat este locul în care o mulțime de jocuri aleg să inoveze, introducând noi sisteme de calificare, o nouă structură de luptă sau sisteme de vrăji diferite - există o mulțime de variații.
Cele mai multe sisteme de luptă folosesc o structură în mișcare, cu un singur combatant care are permisiunea de a lua o acțiune la un moment dat. Primele sisteme de luptă bazate pe ture au fost simple, fiecare entitate având un turn în ordine: rândul jucătorului, rândul inamicului, rândul jucătorului, rândul inamicului și așa mai departe. Acest lucru a dus rapid la sisteme mai complexe, oferind mai multă libertate pentru tactici și strategie.
Vom avea o privire atentă Active-Time bazate pe sisteme de luptă, în care combatanții nu primesc în mod necesar un număr egal de ture. Entitățile mai rapide pot obține mai multe întoarceri, iar tipul acțiunilor întreprinse afectează de asemenea cât durează o întoarcere. De exemplu, un războinic cu un pumnal poate dura 20 de secunde, dar un vrăjitor care cheamă un monstru poate dura două minute.
Imaginea de mai sus prezintă modul de luptă într-un JRPG tipic. Caracterele controlate de jucător sunt în dreapta, personaje inamice din stânga și o casetă de text din partea de jos afișează informații despre combatanți.
La începutul luptei, monstrul și spritele jucătorilor sunt adăugate la scenă și apoi există o decizie cu privire la ce ordine entitățile își vor lua rândul. Această decizie poate depinde parțial de modul în care a fost lansată lupta: dacă jucătorul a fost în ambuscadă, atunci monștrii vor ataca mai întâi, altfel se bazează, de obicei, pe unul dintre statisticile entității, cum ar fi viteza.
Tot ceea ce fac jucătorii sau monștrii este o acțiune: atacul este o acțiune, folosirea magiei este o acțiune, chiar dacă decideți ce acțiune să faceți este o acțiune! Ordinea acțiunilor este cel mai bine urmărită utilizând o coadă. Acțiunea din partea de sus este acțiunea care va avea loc în continuare, cu excepția cazului în care acțiunea mai rapidă o preemptă. Fiecare acțiune va avea o numărătoare inversă care scade ca fiecare cadru trece.
Fluxul de combatere este controlat folosind o mașină de stat cu două stări; o stare care să bifeze acțiunile și un alt stat pentru a executa acțiunea de sus atunci când vine timpul. Ca întotdeauna, cel mai bun mod de a înțelege ceva este să te uiți la cod. Următorul exemplu implementează o stare de luptă de bază cu o coadă de acțiune:
clasa BattleState: IState ListmActions = Listă (); Listă mEntities = Listă (); StateMachine mBattleStates = noua mașină StateMachine (); public static bool SortByTime (Acțiune a, Acțiunea b) returnați a.TimeRemaining ()> b.TimeRemaining () BattleState publice () mBattleStates.Add ("bifați", nou BattleTick (mBattleStates, mActions)); mBattleStates.Add ("execute", noul BattleExecute (mBattleStates, mActions)); public void OnEnter (var params) mBattleStates.Change ("bifați"); // // Efectuați o acțiune de decizie pentru fiecare entitate din coada de acțiune // Sortați-o astfel încât acțiunile cele mai rapide să fie cele mai bune // mEntities = params.entities; foreach (entitate în mEntities) if (e.playerControlled) PlayerDecide acțiune = new PlayerDecide (e, e.Speed ()); mActions.Add (acțiune); altceva AIDecide action = new AIDecide (e, e.Speed ()); mActions.Add (acțiune); Sort (mărimi, BattleState :: SortByTime); public void Actualizare (float elapsedTime) mBattleStates.Update (elapsedTime); void public Render () // Desenați scena, gui, caractere, animații etc mBattleState.Render (); void public OnExit ()
Codul de mai sus demonstrează controlul fluxului de modul de luptă folosind o mașină simplă de stat și o coadă de acțiuni. În primul rând, toate entitățile implicate în luptă au un a să decidă-acțiune adăugat la coadă.
O decizie-acțiune pentru jucător va aduce un meniu cu opțiunile stabile RPG Atac, Magie, și Articol; odată ce jucătorul decide asupra unei acțiuni, decizia-acțiune este scoasă din coadă și se adaugă acțiunea nouă aleasă.
O decizie-acțiune pentru AI va inspecta scena și va decide ce trebuie să facă în continuare (folosind ceva asemănător unui arbore de comportament, arbore de decizie sau tehnică similară) și apoi și el va elimina acțiunea sa de decizie și va adăuga noua sa acțiune la coadă.
BattleTick
clasa controlează actualizarea acțiunilor, după cum se arată mai jos:
clasa BattleTick: IState StateMachine mStateMachine; ListămActions; public BattleTick (StateMachine stateMachine, Listă ()): mStateMachine (stateMachine), mActions (action) // Lucrurile se pot întâmpla în aceste funcții, dar nu ne interesează nimic. public void Rendered ) public void Actualizare (float elapsedTime) foreach (Acțiune a în mActions) a.Update (elapsedTime); dacă (mActions.Top (). IsReady ()) Acțiune sus = mActions.Pop (); mStateMachine: Modificare ("executa", sus);
BattleTick
este o stare secundară a statului BattleMode și doar căpușe până când numărătoarea inversă a acțiunii de sus este zero. Apoi afișează acțiunea de sus din coadă și se schimbă în a executa stat.
Diagrama de mai sus prezintă o coadă de acțiune la începutul unei bătălii. Nimeni nu a luat încă o acțiune și toată lumea este ordonată de timpul lor să ia o decizie.
Planta Giant are o numărătoare inversă de 0, deci la următorul bilet pe care îl execută AIDecide
acțiune. În acest caz AIDecide
acțiunea are ca rezultat monstrul care decide să atace. Acțiunea de atac este aproape imediată și se adaugă din nou în coadă ca a doua acțiune.
În următoarea iterație de BattleTick
, jucătorul va alege ce acțiune trebuie să ia piticul său "Mark", care va schimba din nou coada de așteptare. Următoarea iterație de BattleTick
după aceea, Plantul va ataca unul dintre pitici. Acțiunea de atac va fi eliminată din coadă și trimisă către BattleExecute
stat, și va anima planta ataca, precum și de a face toate calculele de luptă necesare.
Odată ce atacul monstrui este terminat altul AIDecide
acțiunea va fi adăugată la coada de așteptare pentru monstru. BattleState
va continua astfel până la sfârșitul luptei.
Dacă orice entitate moare în timpul luptei, toate acțiunile ei trebuie să fie eliminate din coadă - nu vrem ca monștrii morți să se reanimă brusc și să atace în timpul jocului (dacă nu facem zombi intenționat sau un fel de nemaiauzit!).
Coada de acțiune și mașina simplă de stat sunt inima sistemului de luptă și acum ar trebui să aveți o senzație bună despre cum se potrivește împreună. Nu este suficient de completă pentru a fi o soluție independentă, dar poate fi folosită ca un șablon pentru a construi ceva mai complet funcțional și complex. Acțiunile și statele sunt o abstracție bună care ajută la gestionarea complexității luptei și facilitează extinderea și dezvoltarea acesteia.
BattleExecute
stat.