Ia Imersiunea jocului tău la nivelul următor cu muzică responsabilă de joc

Muzica capabilă să se schimbe dinamic și perfect pentru a reflecta ceea ce se întâmplă pe ecran poate adăuga un nou nivel de imersiune într-un joc. În acest tutorial ne uităm la una dintre modalitățile mai ușoare de a adăuga muzică receptivă la un joc.

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


Demo

Iată o demonstrație live pe care vă puteți juca (cu cod sursă descărcabilă). Puteți urmări o versiune înregistrată a demo-ului în următorul videoclip, dacă browserul dvs. web nu poate rula demonstrația live:


Notă importantă: În momentul elaborării acestui tutorial, API-ul W3C Web Audio (folosit de demo-ul JS) este o tehnologie experimentală și este disponibil numai în browserul web Google Chrome.


Introducere

Journey, un joc dezvoltat de această companie, este un bun punct de plecare pentru acest tutorial. Grafica și muzica jocului se combină pentru a crea o experiență interactivă uimitoare și emoțională, dar există ceva special în muzică în joc, care face ca experiența să fie la fel de puternică ca și cum ar fi ea - curge fără probleme prin întregul joc și evoluează dinamic ca jucătorul progresează și declanșează anumite evenimente în joc. Journey folosește muzică "receptivă" pentru a spori emoțiile pe care le are playerul în timpul jocului.

Pentru a fi corect, o mulțime de jocuri moderne folosesc muzică receptivă într-un fel sau altul - Tomb Raider și Bioshock Infinite sunt două exemple care îmi amintesc - dar fiecare joc poate beneficia de muzică receptivă.

Deci, cum puteți adăuga muzică receptivă la jocurile dvs.? Există numeroase modalități de a realiza acest lucru; unele moduri sunt mult mai sofisticate decât altele și necesită canale audio multiple pentru a fi redirecționate de la un dispozitiv de stocare local, dar adăugarea unor muzică de bază receptivă într-un joc este, de fapt, destul de ușoară dacă aveți acces la un API de sunet de nivel scăzut.

Vom arunca o privire la o soluție destul de simplă și suficient de ușoară pentru a fi folosită astăzi în jocuri online - inclusiv jocuri bazate pe JavaScript.


Pe scurt

Cea mai ușoară modalitate de a atinge o muzică receptivă într-un joc online este prin încărcarea unui singur fișier audio în memorie la timpul de execuție și apoi prin secționarea programabilă a unor secțiuni specifice din acel fișier audio. Acest lucru necesită un efort coordonat din partea programatorilor de jocuri, a inginerilor de sunet și a designerilor.

Primul lucru pe care trebuie să-l luăm în considerare este structura reală a muzicii.


Structura muzicii

Soluția muzicală receptivă la care ne uităm aici necesită muzica să fie structurată într-un mod care să permită ca părți ale aranjamentului muzical să fie bucle fără întrerupere - aceste părți loopable ale muzicii vor fi numite "zone" în tot acest tutorial.

Pe lângă faptul că au zone, muzica poate sa sunt compuse din părți care nu sunt loopabile care sunt folosite ca tranziții între diferite zone - acestea vor fi numite "umpluturi" pe tot parcursul acestui tutorial.

Următoarea imagine vizualizează o structură muzicală foarte simplă constând din două zone și două umpleri:


Dacă sunteți un programator care a folosit anterior API-uri de nivel redus de sunet, este posibil să fi lucrat deja acolo unde mergem cu asta: dacă muzica este structurată astfel încât să permită ca părți ale aranjamentului să fie bucle fără întrerupere, muzica poate fi secvențiată programatic - tot ce trebuie să știm este locul în care zonele și umpluturile sunt situate în interiorul muzicii. Acolo unde a descriptor fișierul este util.

Notă: Nu trebuie să existe nici o tăcere la începutul muzicii; trebuie să înceapă imediat. Dacă există o bucată aleatoare de tăcere la începutul muzicii, zonele și umplerea muzicii nu vor fi aliniate la bare (importanța acestui lucru va fi acoperită mai târziu în acest tutorial).


Descriptor de muzică

Dacă vrem să putem juca și bucla în mod programat anumite părți ale unui fișier muzical, trebuie să știm unde sunt zonele muzicale și umplerile care se află în muzică. Soluția cea mai evidentă este un fișier descriptor care poate fi încărcat împreună cu muzica și pentru a păstra lucrurile simple vom folosi un fișier JSON deoarece majoritatea limbajelor de programare sunt capabile să decodifice și să codifice datele JSON în aceste zile.

Următorul este un fișier JSON care descrie structura muzicală simplă din imaginea anterioară:

 "tip": 0, "dimensiune": 2, "nume": "relaxat", "tip": 0, : "2", "nume": "vânat", "tip": 1, "dimensiune" : "B"]
  • bpm câmpul este tempo-ul muzicii, în batai pe minut.
  • BPB câmpul este semnătura muzicii, în bătăi pe bar.
  • structura câmpul este o serie de obiecte care descriu fiecare zonă și completează muzica.
  • tip câmpul ne spune dacă obiectul este o zonă sau un umplere (zero și respectiv unul).
  • mărimea câmpul este lungimea sau zona sau umplut, în bare.
  • Nume câmpul este un identificator pentru zonă sau umple.

Sincronizarea muzicii

Informațiile din descriptorul de muzică ne permit să calculam diferite valori legate de timp care sunt necesare pentru a reda cu exactitate muzica printr-un API de sunet de nivel scăzut.

Cea mai importantă informație de care avem nevoie este lungimea unei singure bare de muzică, în eșantioane. Zonele muzicale și umpluturile sunt aliniate la bare și când trebuie să trecem de la o parte a muzicii la alta tranziția trebuie să se întâmple la începutul unui bar - nu vrem ca muzica să sară dintr-o poziție aleatorie într-un bar, pentru că ar suna într-adevăr deranjant.

Următorul pseudocod calculează lungimea eșantionului unei singure bare de muzică:

 bpm = 120 // bate pe minut bpb = 4 // bate pe bar srt = 44100 // rata de eșantionare bar_length = srt * (60 / (bpm / bpb))

Cu bar_length calculată, putem să stabilim acum poziția și lungimea eșantionului și să umplem muzica. În următorul pseudocod, pur și simplu bifăm prin descriptor structura și adaugă două valori noi zonei și umple obiectele:

 i = 0 n = descriptor.structure.length // numărul de zone și umpleri s = 0 în timp ce (i < n )  o = descriptor.structure[i++] o.start = s o.length = o.size * bar_length s += o.length 

Pentru acest tutorial, aceasta este toata informatia de care avem nevoie pentru solutia noastra de muzica receptiva - acum stim pozitia si lungimea fiecarei zone si umplem muzica, ceea ce inseamna ca acum sunt capabile sa joace zonele si sa umple orice ordine ne place. În esență, putem secvența programatic o pistă de muzică infinit de lungă la timpul de execuție, cu foarte puțin aeriene.


Redare muzică

Acum, că avem toate informațiile de care avem nevoie pentru a reda muzica, jocurile zonale și umplerile din muzică sunt o sarcină relativ simplă și putem face acest lucru cu două funcții.

Prima funcție se ocupă de sarcina de a trage probe din fișierul nostru muzical și de a le împinge la API-ul de sunet de nivel scăzut. Din nou, o să demonstrez acest lucru folosind pseudocod deoarece diferite limbi de programare au API-uri diferite pentru a face acest lucru, dar teoria este consistentă în toate limbile de programare.

 input // tampon conținând eșantioanele de la ieșirea noastră de muzică // tampon de ieșire API cu nivel scăzut al sunetului playhead = 0 // poziția capului de joc în fișierul de muzică, în probele start = 0 // poziția de start a zonei active sau umplerea, în eșantionul lungime = 0 // lungimea zonei active sau umpleți, în eșantioanele următoare = null // următoarea zonă sau obiectul (obiect) care trebuie redat // invocat ori de câte ori API-ul de sunet de nivel inferior necesită mai multe funcții de eșantionare a datelor update () i = 0 n = output.length // lungimea probei de ieșire tampon de ieșire = lungime - începe în timp ce (i < n )  // is the playhead at the end of the active zone or fill if( playhead == end )  // is another zone or fill waiting to be played if( next != null )  start = next.start length = next.length next = null  // reset the playhead playhead = start  // pull samples from the input and push them to the output output[i++] = input[playhead++]  

Cea de-a doua funcție este folosită pentru a coaliza următoarea zonă sau umplere care trebuie redată:

 // param 'nume' este numele zonei sau funcția de umplere (definită în descriptor) setNext (nume) i = 0 n = descriptor.structure.length // numărul de zone și umplere în timp ce (i < n )  o = descriptor.structure[i++] if( o.name == name )  // set the 'next' value and return from the function next = o return   // the requested zone or fill could not be found throw new Exception() 

Pentru a juca zona "relaxată" a muzicii, am sunat setNext ( "Relaxat"), iar zona ar fi plasată în coadă și apoi jucată la următoarea posibilă ocazie.

Următoarea imagine vizualizează redarea zonei "Relaxat":


Pentru a reda zona muzicală "Vânată", am sunat setNext ( "Hunted"):


Credeți sau nu, avem acum suficient să lucrăm pentru a adăuga o muzică ușor receptivă la orice joc care are acces la un API de sunet de nivel scăzut, dar nu există niciun motiv pentru care această soluție trebuie să rămână simplă - putem juca diferite părți ale muzica în orice ordine ne place, și care deschide ușa pentru mai multe coloane sonore complexe.

Unul dintre lucrurile pe care le-am putea face este să grupăm diferite părți ale muzicii pentru a crea secvențe și acele secvențe ar putea fi folosite ca tranziții complexe între diferitele zone din muzică.


Secvențierea muzicii

Gruparea împreună a diferitelor părți ale muzicii pentru a crea secvențe va fi acoperită într-un tutorial viitor, dar între timp, ia în considerare ceea ce se întâmplă în următoarea imagine:


În loc să trecem direct dintr-o secțiune de muzică foarte tare într-o secțiune de muzică foarte liniștită, am putea liniști lucrurile treptat folosind o secvență - adică o tranziție lină.


Concluzie

Am analizat o posibilă soluție pentru muzica de joc receptivă în acest tutorial, folosind o structură muzicală și un descriptor de muzică și codul de bază necesar pentru a gestiona redarea muzicii.

Muzica receptivă poate adăuga un nou nivel de imersie la un joc și este cu siguranță un lucru pe care dezvoltatorii de jocuri ar trebui să ia în considerare să profite de la începerea dezvoltării unui nou joc. Dezvoltatorii de jocuri nu ar trebui să facă greșeala de a părăsi astfel de lucruri până la ultimele etape ale dezvoltării; necesită un efort coordonat din partea programatorilor de jocuri, a inginerilor de sunet și a designerilor.