Muziek die in staat is om dynamisch en naadloos te veranderen om weer te geven wat er op het scherm gebeurt, kan een geheel nieuw niveau van onderdompeling in een game toevoegen. In deze zelfstudie bekijken we een van de gemakkelijkere manieren om responsieve muziek aan een game toe te voegen.
Notitie: Hoewel deze tutorial geschreven is met behulp van JavaScript en de Web Audio API, zou je in bijna elke game-ontwikkelomgeving dezelfde technieken en concepten moeten kunnen gebruiken..
Hier is een live responsieve JavaScript-demo voor muziek om mee te spelen (met downloadbare broncode). U kunt een opgenomen versie van de demo bekijken in de volgende video als uw webbrowser de live demo niet kan uitvoeren:
Belangrijke notitie: Op het moment dat deze zelfstudie werd geschreven, is de W3C Web Audio API (gebruikt door de JS-demo) een experimentele technologie en is alleen beschikbaar in de Google Chrome-webbrowser.
Journey, een game ontwikkeld door thatgamecompany, is een goed startpunt voor deze tutorial. De graphics en muziek van de game komen samen om een verbluffende en emotionele interactieve ervaring te creëren, maar er is iets speciaals aan de muziek in de game die de ervaring net zo krachtig maakt als hij is - hij stroomt naadloos door het hele spel en evolueert dynamisch als de speler vordert en triggert bepaalde in-game-evenementen. Journey maakt gebruik van 'responsieve' muziek om de emoties te verbeteren die de speler tijdens het spelen van het spel ervaart.
Om eerlijk te zijn, gebruiken veel moderne games responsieve muziek op de een of andere manier - Tomb Raider en Bioshock Infinite zijn twee voorbeelden die bij je opkomen - maar elke game kan baat hebben bij responsieve muziek.
Dus hoe kun je nu reagerende muziek toevoegen aan je games? Welnu, er zijn talloze manieren om dit te bereiken; sommige manieren zijn veel geavanceerder dan andere en vereisen dat meerdere audiokanalen worden gestreamd vanaf een lokaal opslagapparaat, maar het toevoegen van enkele basale, responsieve muziek aan een game is eigenlijk vrij eenvoudig als je toegang hebt tot een low-level sound API.
We gaan een oplossing bekijken die eenvoudig genoeg en licht genoeg is om vandaag in online games te gebruiken - inclusief JavaScript-spellen.
De gemakkelijkste manier om responsieve muziek in een online game te bereiken, is door tijdens runtime één audiobestand in het geheugen te laden en vervolgens specifieke secties van dat audiobestand programmatisch te doorlopen. Dit vereist een gecoördineerde inspanning van de spelprogrammeurs, geluidstechnici en ontwerpers.
Het eerste dat we moeten overwegen, is de feitelijke structuur van de muziek.
De responsieve muziekoplossing waar we hier naar kijken vereist de muziek zodanig te structureren dat delen van het muzikale arrangement naadloos in een lus kunnen worden omgezet - deze loopbare delen van de muziek worden in deze zelfstudie 'zones' genoemd.
Evenals het hebben van zones, de muziek kan bestaan uit niet-loopbare delen die worden gebruikt als overgangen tussen verschillende zones - deze worden 'fills' genoemd gedurende de rest van deze tutorial.
De volgende afbeelding visualiseert een zeer eenvoudige muziekstructuur die bestaat uit twee zones en twee opvullingen:
Als je een programmeur bent die al eerder low-level geluid-API's heeft gebruikt, ben je misschien al uitgewerkt waar we mee bezig zijn: als de muziek zo is gestructureerd dat delen van het arrangement naadloos in een lus kunnen worden omgezet, muziek kan geprogrammeerd worden gesequenced - alles wat we moeten weten is waar de zones en vullingen zich binnen de muziek bevinden. Dat is waar a descriptor bestand komt nuttig.
Notitie: Aan het begin van de muziek mag er geen stilte zijn; het moet onmiddellijk beginnen. Als er aan het begin van de muziek een willekeurig stuk stilte is, worden de zones en de vullingen in de muziek niet uitgelijnd met balken (het belang hiervan wordt verderop in deze tutorial behandeld).
Als we specifieke delen van een muziekbestand programmatisch kunnen afspelen en lus kunnen maken, moeten we weten waar de muziekzones en vullingen zich in de muziek bevinden. De meest voor de hand liggende oplossing is een descriptorbestand dat samen met de muziek kan worden geladen. Om het simpel te houden, gebruiken we een JSON-bestand omdat de meeste programmeertalen tegenwoordig in staat zijn om JSON-gegevens te decoderen en te coderen.
Het volgende is een JSON-bestand dat de eenvoudige muziekstructuur in de vorige afbeelding beschrijft:
"bpm": 120, "bpb": 4, "structure": ["type": 0, "size": 2, "name": "Relaxed", "type": 0, "size" : 2, "name": "Hunted", "type": 1, "size": 1, "name": "A", "type": 1, "size": 1, "name" : "B"]
bpm
field is het tempo van de muziek, in beats per minuut.BPB
field is de handtekening van de muziek, in beats per bar.structuur
veld is een geordende reeks objecten die elke zone beschrijven en de muziek invullen.type
veld vertelt ons of het object een zone of een vulling is (respectievelijk nul en één).grootte
veld is de lengte of de zone of vulling, in staven.naam
veld is een ID voor de zone of opvulling.De informatie in de muziekdescriptor stelt ons in staat om verschillende tijdsgerelateerde waarden te berekenen die nodig zijn om de muziek accuraat af te spelen via een low-levelgeluid-API.
Het belangrijkste stukje informatie dat we nodig hebben, is de lengte van een enkele balk met muziek, in samples. De muzikale zones en vullingen zijn allemaal uitgelijnd met balken, en als we van het ene deel van de muziek naar het andere moeten overstappen, moet de overgang plaatsvinden aan het begin van een balk - we willen niet dat de muziek van een willekeurige positie springt binnen een balk omdat het echt verontrustend zou klinken.
De volgende pseudocode berekent de sample-lengte van een enkele balk met muziek:
bpm = 120 // beats per minute bpb = 4 // beats per bar srt = 44100 // samplefrequentie bar_length = srt * (60 / (bpm / bpb))
Met de bar_length
berekend kunnen we nu de monsterpositie en lengte van de zones en vullingen in de muziek berekenen. In de volgende pseudocode doorlopen we eenvoudig de descriptor's structuur
array en voeg twee nieuwe waarden toe aan de zone- en opvulobjecten:
i = 0 n = descriptor.structure.length // aantal zones en opvullingen s = 0 while (i < n ) o = descriptor.structure[i++] o.start = s o.length = o.size * bar_length s += o.length
Voor deze tutorial is dat alle informatie die we nodig hebben voor onze responsieve muziekoplossing - we kennen nu de voorbeeldpositie en lengte van elke zone en vullen de muziek in, en dat betekent dat we nu de zones en vullingen in elke willekeurige volgorde kunnen spelen wij houden van. We kunnen nu programmatisch een oneindig lang muzieknummer programmeren tijdens runtime met heel weinig overhead.
Nu we alle informatie hebben die we nodig hebben om de muziek af te spelen, is programmatisch spelen van zones en vullingen van de muziek een relatief eenvoudige taak, en we kunnen dit met twee functies aan..
De eerste functie behandelt de taak om samples uit ons muziekbestand te halen en ze naar de low-level sound API te duwen. Nogmaals, ik zal dit demonstreren met pseudocode omdat verschillende programmeertalen verschillende API's hebben om dit soort dingen te doen, maar de theorie is consistent in alle programmeertalen.
input // buffer met de samples van onze muziekuitvoer // laag niveau geluid API output buffer playhead = 0 // positie van de playhead in het muziekbestand, in samples start = 0 // startpositie van de actieve zone of fill, in samples length = 0 // lengte van de actieve zone of fill, in samples next = null // de volgende zone of fill (object) die moet worden gespeeld // wordt aangeroepen telkens wanneer de low-level sound API meer voorbeeldgegevensfunctie vereist update () i = 0 n = uitvoer.lengte // bemonsteringslengte van het uitvoerbuffer-einde = lengte - begin terwijl (i.e. < 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++]
De tweede functie wordt gebruikt om de volgende zone of vulling die moet worden gespeeld in de wachtrij te plaatsen:
// param 'name' is de naam van de zone of opvulling (gedefinieerd in de descriptor) functiereeksNext (naam) i = 0 n = descriptor.structuur.lengte // aantal zones en opvullingen terwijl (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()
Om de 'Relaxed'-zone van de muziek te spelen, zouden we bellen setNext ( "Relaxed")
, en de zone wordt in de wachtrij geplaatst en vervolgens afgespeeld bij de volgende mogelijke gelegenheid.
De volgende afbeelding visualiseert het afspelen van de zone 'Ontspannen':
Om de 'Hunted'-zone van muziek te spelen, zouden we bellen setNext ( "Hunted")
:
Geloof het of niet, we hebben nu genoeg om mee samen te werken om eenvoudige responsieve muziek toe te voegen aan elke game die toegang heeft tot een low-level sound-API, maar er is geen reden waarom deze oplossing simpel moet blijven - we kunnen verschillende onderdelen van de muziek in elke gewenste volgorde, en dat opent de deur naar complexere soundtracks.
Een van de dingen die we zouden kunnen doen is verschillende delen van de muziek groeperen om reeksen te creëren, en die sequenties kunnen worden gebruikt als complexe overgangen tussen de verschillende zones in de muziek.
Het samenvoegen van verschillende delen van de muziek om reeksen te maken, wordt behandeld in een toekomstige zelfstudie, maar onderneem in de tussentijd wat er in de volgende afbeelding gebeurt:
In plaats van direct van een zeer luide muzieksectie over te schakelen naar een heel rustig deel van de muziek, konden we de dingen geleidelijk stiller maken met een reeks - dat is, een vloeiende overgang.
We hebben gekeken naar een mogelijke oplossing voor responsieve gamemuziek in deze tutorial, met behulp van een muziekstructuur en een muziekdescriptor, en de kerncode die nodig is om de muziek af te spelen.
Responsieve muziek kan een geheel nieuw niveau van onderdompeling in een game toevoegen en het is absoluut iets waar ontwikkelaars van games van moeten profiteren bij het starten van de ontwikkeling van een nieuw spel. Gameontwikkelaars mogen echter niet de fout maken om dit soort dingen te laten liggen tot de laatste ontwikkelingsfasen. het vereist een gecoördineerde inspanning van de spelprogrammeurs, geluidstechnici en ontwerpers.