Cum să codificați realizările nedorite pentru jocul dvs. (o abordare simplă)

Realizările sunt extrem de populare în rândul jucătorilor. Ele pot fi folosite într-o varietate de moduri, de la predare la măsurarea progresului, dar cum putem să le codificăm? În acest tutorial, voi prezenta o abordare simplă pentru implementarea realizărilor.

Notă: Deși acest tutorial este scris folosind AS3 și Flash, ar trebui să puteți utiliza aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului.

Puteți descărca sau introduce codul final din repo GitHub: https://github.com/Dovyski/Achieve


Codul proviziilor: Trick sau Treat?

La prima vedere, programarea unui sistem de realizări pare trivială - și asta este parțial adevărat. Acestea sunt, în general, implementate cu contoare, fiecare reprezentând o măsurătoare importantă a jocului, cum ar fi numărul de inamici uciși sau viața jucătorului.

O realizare este deblocată dacă acele contoare se potrivesc cu teste specifice:

 killedEnemies = killedEnemies + 1; dacă (killEnemies> = 10 && trăiește> = 2) // deblocați realizarea

Nu este nimic în neregulă cu această abordare, dar imaginați-vă un test cu 10 sau mai mulți contoare. În funcție de numărul de realizări (și de contoare), este posibil să ajungeți la un cod spaghetti replicat peste tot:

 dacă (deadEnemies> 5 && trăiește> 2 && decese <= 3 && perfectHits > 20 && muniție> = 100 && greșite <= 1)  // unlock achievement 

O idee mai bună

Abordarea mea se bazează, de asemenea, pe contoare, dar acestea sunt controlate de reguli simple:


Realizări pe baza proprietăților. Dreptunghiurile gri sunt proprietăți / realizări active.

O realizare este deblocată când toate proprietățile asociate sunt active. O realizare poate avea una sau mai multe proprietăți asociate, fiecare fiind gestionată de o singură clasă, deci nu este nevoie să scrieți dacă() declarații peste tot în cod.

Iată ideea:

  • Identificați orice metric interesant de joc (ucide, decese, greșeli, meciuri etc.).
  • Fiecare metric devine a proprietate, ghidat de o constrângere de actualizare. Constrângerea controlează dacă proprietatea ar trebui să fie schimbată când apare o nouă valoare.
  • Constrângerile sunt: ​​"Actualizare numai dacă noua valoare este mai mare decât valoarea curentă"; "Actualizare numai dacă noua valoare este mai mică decât valoarea curentă"; și "actualizați indiferent de valoarea curentă".
  • Fiecare proprietate are un activarea regula - de exemplu, "ucide este activă dacă valoarea sa este mai mare de 10".
  • Verificați periodic proprietățile activate. Dacă toate proprietățile conexe ale unei realizări sunt active, atunci realizarea este deblocată.

Pentru a implementa o realizare, trebuie să definim ce proprietăți ar trebui să fie active pentru a debloca acea realizare. După aceea, proprietățile trebuie să fie actualizate în timpul gameplay-ului și ați terminat!

Următoarele secțiuni prezintă o implementare pentru această idee.


Descrierea proprietăților și a realizărilor

Primul pas de implementare este reprezentarea proprietăţi și realizări. Clasa Proprietate pot fi următoarele:

 clasa publică proprietate private var mName: String; privat var mValue: int; private var mActivation: String; privat var mActivationValue: int; privat var mInitialValue: int; funcția publică funcția (theName: String, theInitialValue: int ,Activarea: String ,ActivationValue: int) mName = numeleName; mActivarea = Activarea; mActivationValue = Valoarea de Activare; mInitialValue = Valoarea Inițială; 

O proprietate are un nume (MNAME), o valoare (mValue, care este contorul), o valoare de activare (mActivationValue) și o regulă de activare (mActivation).

Regula de activare este ceva de genul "activă dacă este mai mare decât" și controlează dacă o proprietate este activă (mai târziu, mai târziu). Se spune că o proprietate este activă atunci când valoarea sa este comparată cu valoarea de activare și rezultatul satisface regula de activare.

O realizare poate fi descrisă după cum urmează:

 clasa publică Achievement private var mName: String; // nume de realizare privat var mProps: Array; // array de proprietăți asociate private var mUnlocked: Boolean; // realizarea este deblocată sau nu funcția publică Achievement (theId: String, theRelatedProps: Array) mName = theId; mProps =Problemele relevante; mUnlocked = false; 

O realizare are un nume (MNAME) și un steag pentru a indica dacă acesta este deja deblocat (mUnlocked). Matricea mProps conține o intrare pentru fiecare proprietate necesară pentru a debloca realizarea. Când toate aceste proprietăți sunt active, atunci realizarea ar trebui să fie deblocată.


Gestionarea proprietăților și a realizărilor

Toate proprietățile și realizările vor fi gestionate de o clasă centralizată numită Obține. Această clasă ar trebui să se comporte ca o cutie neagră care primește actualizări de proprietăți și spune dacă o realizare a fost deblocată. Structura sa de baza este:

 public class Obțineți // activarea regulilor static static const ACTIVE_IF_GREATER_THAN: String = ">"; statica statica publica ACTIVE_IF_LESS_THAN: String = "<"; public static const ACTIVE_IF_EQUALS_TO :String = "=="; private var mProps :Object; // dictionary of properties private var mAchievements :Object; // dictionary of achievements public function Achieve()  mProps =  ; mAchievements =  ;  

Deoarece toate proprietățile vor fi actualizate utilizând numele lor ca index de căutare, este convenabil să le stocați într-un dicționar ( mProps atributul în clasă). Realizările vor fi tratate în mod similar, astfel încât acestea să fie stocate în același mod (mAchievements atribut).

Pentru a gestiona adăugarea de proprietăți și realizări, vom crea metodele defineProperty () și defineAchievement ():

 funcția publică defineProperty (numele: String, theInitialValue: int, theActivationMode: String ,Value: int): void mProps [theName] = noua proprietate (theName, theInitialValue ,ActivationMode ,Value);  funcția publică defineAchievement (theName: String, theRelatedProps: Array): void MAchievements [theName] = nou realizare (theName, theRelatedProps); 

Ambele metode adaugă pur și simplu o intrare în proprietate sau în dicționarul realizărilor.


Actualizarea proprietăților

Acum, că Obține clasa se poate ocupa de proprietăți și realizări, este timpul să-l facem capabil să actualizăm valorile proprietății. O proprietate va fi actualizată în timpul jocului și va acționa ca un contor. De exemplu, proprietatea killedEnemies ar trebui să fie incrementat de fiecare dată când un inamic este distrus.

Doar două metode sunt necesare pentru aceasta: una pentru a citi și o altă pentru a seta o valoare de proprietate. Ambele metode aparțin Obține clasă și pot fi implementate după cum urmează:

 funcția publică getValue (theProp: String): int return mProps [theProp] .value;  funcția privată setValue (theProp: String ,Value: int): void mProps [theProp] .value =Value; 

De asemenea, este util să aveți o metodă de adăugare a unei valori unui grup de proprietăți, ceva asemănător unui increment / decrement de lot:

 funcția publică addValue (theProps: Array, Valoarea: int): void pentru (var i: int = 0; i < theProps.length; i++)  var aPropName :String = theProps[i]; setValue(aPropName, getValue(aPropName) + theValue);  

Verificarea pentru realizări

Verificarea rezultatelor deblocate este simplă și ușoară: iterați pe dicționarul realizărilor, verificând dacă toate proprietățile asociate ale unei realizări sunt active.

Pentru a efectua această iterație, mai întâi avem nevoie de o metodă pentru a verifica dacă o proprietate este activă:

 clasa publică a clasei // // restul codului de clasă a fost omis ... // funcția publică isActive (): Boolean var aRet: Boolean = false; comutator (mActivation) caz Achieve.ACTIVE_IF_GREATER_THAN: aRet = mValue> mActivationValue; pauză; caz Achieve.ACTIVE_IF_LESS_THAN: aRet = mValue < mActivationValue; break; case Achieve.ACTIVE_IF_EQUALS_TO: aRet = mValue == mActivationValue; break;  return aRet;  

Acum, să implementăm checkAchievements () metodă în Obține clasă:

 funcția de verificare a funcției publiceAchievements (): Vector var aRet: Vector = new Vector (); pentru (var n: String în mAchievements) var aAchivement: Achievement = Măsurători [n]; dacă (aAchivement.unlocked == false) var aActiveProps: int = 0; pentru (var p: int = 0; < aAchivement.props.length; p++)  var aProp :Property = mProps[aAchivement.props[p]]; if (aProp.isActive())  aActiveProps++;   if (aActiveProps == aAchivement.props.length)  aAchivement.unlocked = true; aRet.push(aAchivement);    return aRet; 

 checkAchievements () metoda se repetă asupra tuturor realizărilor. La sfârșitul fiecărei iterații se testează dacă numărul de proprietăți active pentru respectiva realizare este egal cu cantitatea de proprietăți asociate. Dacă este adevărat, atunci 100% din proprietățile asociate pentru această realizare sunt active, deci jucătorul a deblocat o nouă realizare.

Pentru comoditate, metoda returnează a Vector (care se comportă ca o matrice sau listă) care conține toate realizările care au fost deblocate în timpul verificării. De asemenea, metoda marchează toate realizările găsite ca "deblocate", astfel încât acestea nu vor mai fi analizate din nou în viitor.


Adăugarea constrângerilor la proprietăți

Până acum proprietățile nu au constrângeri, ceea ce înseamnă că orice valoare a trecut SetValue () va actualiza proprietatea. Imaginați-vă o proprietate numită killedWithASingleBomb, care stochează numărul de dușmani ai jucătorului uciși folosind o singură bomba.

Dacă regula de activare este "dacă este mai mare de 5" și jucătorul ucide șase inamici, ar trebui să deblocheze realizarea. Cu toate acestea, presupuneți checkAchievements () metoda nu a fost invocată imediat după ce bomba a explodat. Dacă jucătorul detonă o altă bombă și omoară trei dușmani, proprietatea va fi actualizată 3.

Această schimbare va determina jucătorul să rateze realizarea. Pentru a rezolva aceasta, putem folosi regula de activare a proprietății ca a constrângere. Aceasta înseamnă că o proprietate cu "dacă mai mare de 5" va fi actualizată numai dacă noua valoare este mai mare decât cea curentă:

 funcția privată setValue (theProp: String, Valoarea: int): void // Care regulă de activare? comutare (mProps [theProp] .activație) caz Achieve.ACTIVE_IF_GREATER_THAN: Valoarea = Valoarea> mProps [theProp] .value? Valoarea: mProps [theProp] .value; pauză; caz Achieve.ACTIVE_IF_LESS_THAN: Valoarea = Valoarea < mProps[theProp].value ? theValue : mProps[theProp].value; break;  mProps[theProp].value = theValue; 

Resetarea și etichetarea proprietăților

Deseori, realizările nu sunt legate de întregul joc, ci de perioade precum nivelurile. Ceva ca "bate un nivel care ucide 40 sau mai mulți dușmani" trebuie să fie numărate în timpul nivelului, apoi resetat astfel încât jucătorul să poată încerca din nou la nivelul următor.

O posibilă soluție pentru această problemă este adăugarea Etichete la proprietăți. Utilizarea etichetelor permite manipularea unor grupuri de proprietăți. Folosind exemplul anterior, killedEnemies proprietatea poate fi etichetat ca levelStuff, de exemplu.

În consecință, este posibil să verificăm realizările și să resetați proprietățile pe baza etichetelor:

 // Definiți proprietatea utilizând o etichetă defineProperty ("killEnemies", ..., "levelStuff"); if (levelIsOver ()) // Verificați realizările, dar numai cele bazate pe proprietăți // etichetate cu "levelStuff". Toate celelalte proprietăți vor fi ignorate, // pentru a nu interfera cu alte realizări. checkAchievements ( "levelStuff"); // Resetați toate proprietățile etichetate cu 'levelStuff' resetProperties ("levelStuff"); 

Metoda checkAchievements () devine mult mai versatil cu etichete. Acesta poate fi invocat oricând, atâta timp cât operează grupul corect de proprietăți.


Demonstrație de utilizare

Mai jos este un fragment de cod care demonstrează modul de utilizare a acestei implementări a realizărilor:

 var a: Obțineți = Obțineți noi (); funcția initGame (): void a.defineProperty ("killEnemies", 0, Achieve.ACTIVE_IF_GREATER_THAN, 10, "levelStuff"); a.defineProprietate ("trăiește", 3, Achieve.ACTIVE_IF_EQUALS_TO, 3, "levelStuff"); a.definine proprietate ("niveluri completate", 0, Achieve.ACTIVE_IF_GREATER_THAN, 5); a.defineProprietate ("decese", 0, Achieve.ACTIVE_IF_EQUALS_TO, 0); a.definineAchievement ("masterKill", ["killedEnemies"]); / / Kill 10 + dușmani. a.definirea rezultatelor ("cantTouchThis", ["vie]"); Completați un nivel și nu muriți. a.definineAchievement ("nothingElse", ["completedLevels"]); Beat toate cele 5 nivele. a.definirea rezultatelor ("erou", ["niveluri finalizate", "decese"]); // Bate toate cele 5 nivele, nu muri in timpul procesului functionLoop (): void if (enemyWasKilled ()) a.addValue (["killEnemies"], 1);  dacă (playerJustDied ()) a.addValue (["trăiește"], -1); a.addValue (["decese"], 1);  funcția levelUp (): void a.addValue (["completedLevels"], 1); a.checkAchievements (); // Resetați toate proprietățile etichetate cu 'levelStuff' a.resetProperties ("levelStuff"); 

Concluzie

Acest tutorial a demonstrat o abordare simplă pentru implementarea realizărilor în cod. Concentrându-se pe ghișee și etichete gestionate, ideea încearcă să elimine mai multe teste răspândite pe tot codul.

Puteți descărca sau introduce codul din repo GitHub: https://github.com/Dovyski/Achieve

Sper că această abordare vă ajută să implementați realizările într-un mod mai simplu și mai bun. Mulțumesc că ați citit! Nu uitați să țineți la curent urmând-ne pe Twitter, Facebook sau Google+.

postări asemănatoare
  • Faceți-le să lucreze pentru ea: Proiectarea realizărilor pentru jocurile dvs.
  • Nu-i dați drumul: proiectarea deblochează pentru jocurile tale