Cum de a codifica uși și încuietori

În jocurile realizate din camere conectate cum ar fi The Legend of Zelda, cea mai recentă The Binding of Isaac sau orice tip de ușă Roguelike sau chiar Metroidvania joacă un rol esențial în navigarea și progresul jucătorului.

Ușile permit jucătorului să călătorească dintr-o cameră sau alta în alta și să aibă un loc important în navigarea și conectarea diferitelor încăperi unul la celălalt și în definirea hărții ca pe o lume deschisă sau pe o podeță. Ele pot acționa și ca obstacole temporare pe care jucătorul va trebui să le deblocheze printr-un mecanic specific (cum ar fi obținerea unei chei sau activarea unui comutator).

În acest tutorial, voi demonstra mai multe mecanisme de blocare și vă propun modalități de implementare a acestora în jocurile dvs. Acestea nu sunt în nici un caz menite să fie singurele sau cele mai bune implementări; ele sunt exemple practice.

Demo-urile interactive din acest tutorial au fost făcute cu instrumentul de creare a jocurilor HTML5 Construct 2 și ar trebui să fie compatibile cu versiunea gratuită a acestuia. (Fișierele CAPX sunt disponibile în descărcarea sursei.) Cu toate acestea, acest tutorial vă va ajuta să învățați cum să implementați ușile și să blocați logica în orice motor doriți. Odată ce obțineți ideea din spatele logicii, totul se limitează la cunoașterea dvs. de instrument / limbă de codare și modul în care doriți să o adaptați la jocul pe care îl faceți în prezent.

Hai să ne aruncăm!

Mecanicul de bază

O ușă este în principiu un bloc de peisaj care nu poate fi trecut, împiedicând caracterul jucătorului să treacă până când acesta este deblocat. Ușa poate avea stări diferite: blocat sau deblocat, închis sau deschis.

Trebuie să existe o reprezentare evidentă a acesteia din urmă; jucătorul trebuie să poată spune că ușa este de fapt o ușă și dacă este în starea ei blocată sau deblocată.

În următoarele demonstrații, ușile sunt prezentate prin două elemente grafice:


Aceasta este o ușă închisă.

Aceasta este o ușă deschisă.

De asemenea, am folosit culori diferite pentru a reprezenta diferitele materiale pe care ar putea fi construite ușile - dar, pentru a fi sincer, aspectul grafic este al dvs., al jocului și al universului. Cea mai importantă parte este aceea că ușa ar trebui să poată fi identificată clar ca fiind o ușă și ar trebui să fie evident dacă va bloca progresia jucătorului sau va fi deschisă și va duce la restul nivelului sau lumii.

Când este închis sau blocat, ușa trebuie să fie un bloc de stare solidă. Când este deschis, starea solidă ar trebui dezactivată, permițând ca caracterele să treacă prin ea. Asigurați-vă că indiferent de motorul dvs. de coliziune, vă permite să modificați această stare zbuciumată destul de ușor.

Din perspectiva programării, obiectul ușii ar trebui să conțină sau să fie legat de un obiect este încuiat Variabilă booleană. În funcție de valoarea acestei variabile, puteți determina ce sprite să afișeze și dacă blocul ar trebui să fie solid sau nu.

Pentru a debloca ușa, caracterul trebuie să conțină a has_key Variabila booleană în sine când jucătorul a luat o cheie: Adevărat dacă o au, fals dacă nu.

În acest mecanic de bază, cheia acționează ca o parte a inventarului caracterului și se deschide o cheie toate uși. Utilizarea cheii de pe o ușă nu o consumă; cheia rămâne în inventarul personajului.

Pentru ao vizualiza, putem afișa pur și simplu o imagine a cheii în HUD pentru a lăsa jucătorul să știe că "deține" o cheie care ar putea deschide ușile odată ce personajul la luat (prin mutarea caracterului peste cheia din cameră ).

Luați în considerare următorul exemplu de bază:

Faceți clic pe demo pentru a vă concentra, apoi controlați caracterul folosind tastele săgeți ale tastaturii și efectuați acțiuni cu bara de spațiu. (În acest exemplu, acțiunea este "deschideți o ușă".)

Pereții sunt blocuri solide care nu permit personajul să treacă prin coliziune cu ele. Ușile închise sunt de asemenea solide.

Pentru a deschide o ușă, caracterul trebuie să se situeze în limitele a 64 de pixeli ai ușii și să posede o cheie (adică has_key Variabila boolean care determină dacă caracterul are cheia în inventarul său trebuie să fie Adevărat). 

În aceste condiții, când aparatul apasă bara de spațiu, se schimbă starea corespunzătoare a ușii. Variabila lui booleană blocat este setat sa fals, și starea sa "solidă" este dezactivată.

În pseudocod, ar părea ceva asemănător:

Door.Locked = True Door.AnimationFrame = 0 // Cadrul de animație care afișează ușa ca fiind blocată Door.Solid = Enabled // Starea solidă a ușii este activată Door.Locked = False Door.AnimationFrame = 1 // Animația cadru care afișează ușa ca fiind deschisă Door.Solid = Dezactivat // Starea solidă a portierei este dezactivată Tasta tastaturii "Spațiu" este apăsată și Distanța (caracter, ușă) <= 64px and Door.Locked = True and Character.Has_Key = True //The player has a key Door.Locked = False Keyboard Key "Space" is pressed and Distance(Character,Door) <= 64px and Door.Locked = True and Character.Has_Key = False //The player does not have a key Text.text = "You don't have a key for that door" 

Memento: acest cod nu reprezintă o anumită limbă; ar trebui să puteți să o implementați în orice limbă doriți.

Puteți, de asemenea, să rețineți că facem o verificare când jucătorul are nu au cheia așteptată și afișează un mesaj de feedback explicând de ce ușa nu a fost deblocată. Puteți să vă ocupați de cecuri, cum ar fi cele care se potrivesc cel mai bine jocului dvs. - trebuie doar să știți că este întotdeauna plăcut să oferiți feedback jucătorului dvs. că acțiunea sa a fost înregistrată și să explicați motivul pentru care nu a fost finalizat.

Aceasta este o ușă de bază și o logică de blocare și cum să o implementați. În restul tutorialului, vom examina alte sisteme de blocare care sunt variante ale acestui sistem de bază.

Sisteme de blocare diferite

Am văzut sistemul de bază în care cheia este o parte integrală a inventarului personajului și o cheie deschide toate ușile și poate fi reutilizată pentru a deschide mai multe uși. Să construim pe asta.

Exemplu KeyStack

În exemplul următor, personajul va avea a grămadă de chei din inventar. Deși există mai multe culori diferite de ușă, diferența este strict grafică aici - obiectul ușii este logic același ca în exemplul de bază și un tip de cheie poate deschide oricare dintre ele. Cu toate acestea, de fiecare dată când utilizați o cheie pentru a deschide o ușă, acea cheie este scoasă din teanc.


În ceea ce privește codarea, această modificare este în mare parte la nivelul personajului. În loc de a avea has_key Variabila boolean, veți dori să aveți o numeric variabilă care va deține numărul de taste pe care personajul le are "în stoc".

De fiecare dată când personajul preia o cheie, adaugă 1 la această variabilă pentru a reprezenta stivuirea. De fiecare dată când personajul deschide o ușă, scade 1 din această variabilă pentru a reprezenta utilizarea unei chei. (În terenurile de joc video, cheile sunt distruse imediat ce sunt folosite o singură dată.)

O altă modificare este atunci când jucătorul apasă bara de spațiu: în loc să verifice dacă a has_key Variabila booleană este Adevărat, de fapt, dorim să verificăm valoarea KeyStack este mai mult decât zero, astfel încât să putem consuma o cheie după deschiderea ușii.

În pseudocod, aceasta arată cam așa:

Mecanica ușilor = aceeași ca în exemplul de bază de mai sus. Tasta tastaturii "Spațiu" este apăsată și caracteristicăKeyStack> 0 și distanța (caracter, ușă) <= 64 and Door.Locked = True Character.KeyStack = Character.KeyStack - 1 Door.Locked = False 

Care este exemplul cheie

În acest nou exemplu, vom lua în considerare un scenariu în care diferite tipuri de uși necesită diferite tipuri de chei pentru a fi deblocate.

Aici, ca și în primul exemplu de bază, cheile vor face parte din inventarul personajului. Vom reveni la folosirea variabilelor booleene pentru a determina dacă personajul a preluat cheile necesare. Deoarece vom avea chei diferite, vom avea și alte tipuri de uși (ușă neagră, ușă roșie, ușă de aur) care va necesita și o cheie adecvată pentru a fi deschise (cheia neagră, cheia roșie, cheia de aur, respectiv ).

Obiectele ușii vor folosi diferite sprite pentru a-și arăta materialul și vor conține o variabilă numerică denumită WhichKey care va indica tipul de cheie care se așteaptă, precum și tipul de grafic pe care ar trebui să îl afișeze. Diferitele valori cheie sunt conținute ca variabile constante, pentru o mai bună citire.


În pseudocod:

CONSTANT BLACK_KEY = 0 CONSTANT RED_KEY = 1 CONSTANT GOLD_KEY = 2 Mecanica ușii este aceeași ca în exemplul de bază. Tasta tastaturii "Spațiu" este apăsată / / Ușa necesită o cheie neagră, dar caracterul nu are unul Dacă Door.Locked = Adevărat și Door.WhichKey = BLACK_KEY și Character.Has_Black_Key = False și Distant (Door, Caracter) <= 64 Text.text="You need a black key for this door" //The door requires a red key but the character doesn't have one Else If Door.Locked = True and Door.WhichKey = RED_KEY and Character.Has_Red_Key = False and Distance(Door,Character) <= 64 Text.text="You need a red key for this door" //The door requires a gold key but the character doesn't have one Else If Door.Locked = True and Door.WhichKey = GOLD_KEY and Character.Has_Gold_Key = False and Distance(Door,Character) <= 64 Text.text="You need a red key for this door" //The door requires a black key and the character has one Else If Door.Locked = True and Door.WhichKey = BLACK_KEY and Character.Has_Black_Key = True and Distance(Door,Character) <= 64 Door.Locked = False //The door requires a red key and the character has one Else If Door.Locked = True and Door.WhichKey = RED_KEY and Character.Has_Red_Key = True and Distance(Door,Character) <= 64 Door.Locked = False //The door requires a gold key and the character has one Else If Door.Locked = True and Door.WhichKey = GOLD_KEY and Character.Has_Gold_Key = True and Distance(Door,Character) <= 64 Door.Locked = False

Aceasta este o variantă a exemplului de bază care permite mai multe tipuri de chei și uși și care nu consumă chei pentru a deschide ușile. Odată ce ai cheia, face parte din inventarul dvs. - parte din "statisticile" personajului,.

Exemplu de comutare

De data aceasta, în loc să acționeze direct asupra ușilor, jucătorul trebuie să activeze un comutator specific pentru a deschide sau închide o ușă specifică.

Ușile sunt în esență același obiect ca și în exemplul de bază. Ele ar putea afișa grafică diferită, dar logica obiectului este în continuare aceeași. Există însă o adăugare: adăugăm două variabile numerice DoorID și SwitchID, pe care le folosim pentru a ști care comutator este legat la care ușă.

Întreruperi sunt un tip nou de obiecte pe care am ales să le fac solid (dar nu trebuie să). Ele conțin o variabilă booleană, activat, și variabile numerice DoorID și SwitchID care, după cum puteți ghici, vom folosi pentru a determina care comutator este legat la care ușă.

Deci, atunci când un switch are Activat: Adevărat, ușa "legată" este setată să aibă Blocat: Fals. Acțiunea noastră cu bara de spațiu se va întâmpla atunci când se află lângă a intrerupator, mai degrabă decât lângă o ușă. Rețineți absența unei chei în acest exemplu, deoarece comutatoarele funcționează ca chei:

Am putea folosi doar un cod simplu care verifică legăturile întrerupătoarelor de ușă din aceeași cameră (deoarece acest exemplu afișează trei uși și întrerupătoare în aceeași cameră), dar mai târziu, vom vedea că este posibil să avem întrerupătoare care acționează asupra ușilor care sunt în o alta cameră, astfel încât acțiunea lor nu va avea loc în momentul în care jucătorul activează comutatorul; acesta va apărea mai târziu, când noua cameră este încărcată.

Din acest motiv, avem nevoie persistență. O opțiune pentru aceasta este de a folosi arrays pentru a urmări datele cum ar fi starea comutatoarelor (adică dacă fiecare întrerupător este activat sau nu).

În pseudocod:

CONSTANT SWITCH_DOORID = 0 CONSTANT SWITCH_ACTIVATION = 1 // Aceste constante ne vor permite să păstrăm o amintire lizibilă a coordonatelor matrice 
// Definiți o anumită matrice // Coordonarea X a matricei va corespunde valorii SwitchID // Coordonarea Y-0 va fi DoorID // Coordonata Y-1 va fi starea de activare aSwitch (numărul de comutatoare, 2) // 2 este numărul de înălțime (Y), adesea bazat pe 0.
Rulați o anumită asociere a codurilor de comutare cu identificatoarele de uși Doar mecanicul este în continuare același ca în exemplul de bază. // Afișarea graficului corect al comutatorului în funcție de starea lor de activare Switch.Activated = Adeverință Afișarea cadrului de animație Switch_ON Switch.Activated = Fals Afișarea cadrului de animație Switch_OFF Tasta tastaturii "Spațiu" este apăsată și Distanță (caracter, comutator) <= 64 Switch.Toggle(Activated) //A function that will set the value to either True or False) aSwitch(Switch.SwitchID,SWITCH_ACTIVATION) = Switch.Activated //It can depend on your coding language, but the idea is to set the value in the array where X is the SwitchID and where Y is the state of activation of the switch. The value itself is supposed to be the equivalent of the Switch.Activated boolean value. Door.DoorID = aSwitch(Switch.SwitchID,SWITCH.DOORID) //Allows us to make sure we're applying/selecting the correct door instance //Now according to the activation value, we lock or unlock the door aSwitch(Switch.SwitchID,SWITCH.ACTIVATION) = True Door.Locked = False aSwitch(Switch.SwitchID,SWITCH.ACTIVATION) = False Door.Locked = True

Pentru acest exemplu specific, în cazul în care întrerupătoarele se află în aceeași cameră ca și ușile la care sunt conectate, utilizarea tehnicii de matrice este supraîncărcată. Dacă jocul dvs. este configurat astfel încât fiecare comutator care acționează asupra unei uși să fie poziționat în aceeași cameră, atunci cu toate mijloacele mergeți pentru o metodă mai simplă, scapați de matrice și verificați dacă există obiecte care sunt pe numai ecran.

Exemplu de comutator placă

Plăcuțele de tip placă sunt similare cu întrerupătoarele, în sensul că ele sunt activate sau nu și că le putem lega de ușile pentru a le bloca sau debloca. Diferența constă în modul în care este activat un comutator de placă, care este prin presiune.

În acest exemplu de vizualizare de sus în jos, comutatorul plat va fi activat ori de câte ori caracterul se suprapune. Puteți apăsa bara de spațiu pentru a arunca o piatră pe comutatorul plăcii, lăsând-o activată chiar și atunci când caracterul nu se află pe ea.

Punerea în aplicare a acestui lucru este similară cu exemplul precedent, cu două modificări minore:

  • Trebuie să activați butonul de comutare atunci când un caracter sau o rocă se află în partea superioară a acestuia.
  • Trebuie să faceți ca bara de spațiu să coboare o piatră (din inventar) pe comutatorul platoului.
// Majoritatea implementării este aceeași ca și exemplul precedent înlocuiește obiectul Switch cu obiectul PlateSwitch // Caracterul mecanic al comutatorului plăcii SAU Rock nu se suprapune PlateSwitch PlateSwitch.Activated = Comutator fals (PlateSwitch.SwitchID, SWITCH_ACTIVATION) = PlateSwitch.Activated Door.DoorID = aSwitch (PlateSwitch.SwitchID, SWITCH.DOORID) // Ne permite să ne asigurăm că aplicăm / selectând instanța corectă a ușii Door.Locked = Caracterul adevărat SAU Rock-ul se suprapune PlateSwitch PlateSwitch.Activated = Avertizare adevărată (PlateSwitch .SwitchID, SWITCH_ACTIVATION) = PlateSwitch.Activated Door.DoorID = aSwitch (PlateSwitch.SwitchID, SWITCH.DOORID) // Ne permite să ne asigurăm că aplicăm / selectând instanța de ușă corectă Door.Locked = Cheia tastaturii false "Space" este apăsat și caracterul se suprapune PlateSwitch Spawn Rock la PlateSwitch.position 

Exemplu de micii

Un alt posibil mecanism de blocare este acela de a cere jucatorului sa scape de toti inamicii (cunoscuti si ca mobs) intr-o camera sau in zona pentru a declansa deblocarea usilor.

Pentru acest exemplu, am făcut câteva zone într-o singură cameră; fiecare zonă are o ușă și mai multe mulțimi (deși acei dușmani nu se mișcă și nu tratează daunele).
Fiecare zonă are o culoare proprie.

Bara de spațiu va face ca personajul să tragă niște proiectile; trei proiectile vor ucide o mulțime.

Acest tip de mecanic este folosit în Legenda lui Zelda și Legarea lui Isaac și se învârte în jurul unei funcții de verificare a numărului de dușmani vii din cameră sau zonă. În acest exemplu, fiecare zonă colorată deține un număr de mulțimi vii, inițiată atunci când se încarcă camera și este legată de ușă. Moartea fiecărei mulțimi scade 1 de pe acest contor; odată ce ajunge la 0, usile Blocat statul este schimbat Fals.

// La începutul jocului Pentru fiecare zonă Pentru fiecare zona de suprapunere Mob Mobila.AliveMobs = Area.AliveMobs + 1 
Mașina mecanică este identică cu cea din exemplul de bază
Tastatura Keyboard "Spațiu" este apăsat Spălați un proiectil din poziția Caracterului Projectile se ciocnește cu Mob Mob. HP = Mob.HP - 1 Distruge Projectile Mob.HP <=0 //Mob is dead and Mob is overlapping Area Destroy Mob Area.AliveMobs = Area.AliveMobs - 1 Area.AliveMobs <= 0 and Door is linked to Area //By means of an ID, a pointer or whatever Door.Locked = False

În acest exemplu, un Zonă este un sprite colorat cu o variabilă numerică asociată, AliveMobs, care numără numărul de mulțimi suprapuse zonei. Odată ce toate mulțimile dintr-o zonă sunt înfrânte, ușa corespunzătoare este deblocată (folosind același mecanic ca cel pe care l-am văzut de la exemplul de bază).

Exemplu de navigare

Așa cum am menționat în introducere, ușile pot acționa ca obstacole în calea blocării, dar pot fi de asemenea folosite pentru a permite caracterului jucătorului să navigheze dintr-o cameră în alta.

În acest exemplu, ușile vor fi deblocate implicit, deoarece suntem mai interesați de aspectul de navigație.

Mecanicul depinde foarte mult de jocul pe care îl faci, precum și de felul în care gestionezi structura de date pentru podelele tale. Nu voi intra în detalii despre modul în care funcționează implementarea mea aici, deoarece este foarte specific pentru Construct 2, dar îl puteți găsi în fișierele sursă dacă doriți.

Concluzie

În acest articol, am văzut cum sunt ușile obstacole temporare care necesită chei sau mecanisme de deblocare, cum ar fi întrerupătoare, comutatoare de plăcuțe sau chiar moarte. De asemenea, am văzut cum pot acționa ca "poduri", permițând navigarea prin diferite zone ale lumii jocului.

Ca o memento rapidă, iată câteva mecanisme de blocare posibile:

  • O cheie pentru toate ușile, ca parte a inventarului.
  • Cheile consumabile: de fiecare dată când deschideți o ușă, o cheie este scoasă din teancul cheie.
  • Ușile diferite necesită chei diferite.
  • Întrerupătoare sau plăcuțe, în care nu acționați direct pe ușă pentru al debloca, ci printr-un dispozitiv separat, conectat.
  • Uciderea tuturor mafioților dintr-o zonă deschide automat o ușă.

Dacă ați amestecat toți acei mecanici într-un joc, ați putea ajunge la ceva de genul:

Aici avem o selecție frumoasă de mecanică ușă și de blocare diferite, care necesită jucătorul să treacă prin mai multe camere pentru a debloca diferitele uși. În scopuri de învățare, ați putea dori să reproduceți acest lucru în propriul mediu de programare, utilizând toate implementările anterioare pe care le-am parcurs.

Sper că v-ați bucurat de acest articol și că a fost util pentru dvs. și aș vrea să vă reamintesc că puteți găsi sursa tuturor demo-urilor de pe Github. Puteți să le deschideți și să le editați în versiunea gratuită a lui Construct 2 (versiunea r164.2 sau mai recentă).

Referințe

  • Imaginea de previzualizare: Blocul proiectat de João Miranda de la Proiectul Noun