Giriş
Ses, multimedya deneyimlerini ilgi çekici kılan en önemli unsurlardan biridir. Filmi sessiz izlemeyi denediyseniz bunu fark etmişsinizdir.
Oyunlar da bu kapsamdadır. Video oyunlarıyla ilgili en güzel anılarımın müzik ve ses efektleriyle ilgili olduğunu söyleyebilirim. Favori oyunlarımı oynadıktan yaklaşık yirmi yıl sonra bile Koji Kondo'nun Zelda bestelerini ve Matt Uelmen'in atmosferik Diablo film müziğini hâlâ kafamdan çıkaramıyorum. Warcraft'taki birim tıklama yanıtları ve Nintendo'nun klasik oyunlarından alınan örnekler gibi ses efektleri de akılda kalıcı olabilir.
Oyun sesleri bazı ilginç zorluklar sunar. Tasarımcıların, inandırıcı oyun müzikleri oluşturmak için oyuncuların kendilerini bulabileceği, tahmin edilemeyen oyun durumuna uyum sağlaması gerekir. Uygulamada, oyunun bazı bölümleri bilinmeyen bir süre boyunca devam edebilir, sesler çevreyle etkileşime girebilir ve oda efektleri ve göreli ses konumlandırması gibi karmaşık şekillerde karıştırılabilir. Son olarak, aynı anda çalınabilecek çok sayıda ses olabilir. Bu seslerin hepsinin birlikte iyi ses çıkarması ve performans cezaları oluşturmadan oluşturulması gerekir.
Web'de Oyun Sesi
Basit oyunlar için <audio>
etiketini kullanmak yeterli olabilir. Ancak birçok tarayıcı, ses sorunlarına ve yüksek gecikmeye neden olan kötü uygulamalar sunar. Tedarikçi firmalar kendi uygulamalarını iyileştirmek için yoğun şekilde çalıştığından bu sorunun geçici olmasını umuyoruz. <audio>
etiketinin durumunu incelemek için areweplayingyet.org adresindeki test paketini kullanabilirsiniz.
Ancak <audio>
etiketi spesifikasyonunu daha ayrıntılı incelediğinizde, bu etiketle yapılamayacak çok şey olduğu anlaşılır. Bu durum, medya oynatma için tasarlandığından şaşırtıcı değildir. Sınırlılıklardan bazıları şunlardır:
- Ses sinyaline filtre uygulayamazsınız.
- Ham PCM verilerine erişme imkanı yoktur.
- Kaynakların ve dinleyicilerin konumu ve yönü gibi kavramlar yoktur.
- Ayrıntılı zamanlama yoktur.
Makalenin geri kalanında, Web Audio API ile yazılmış oyun sesleri bağlamında bu konuların bazılarını ayrıntılı olarak ele alacağım. Bu API'ye kısa bir giriş için başlangıç eğitimine göz atın.
Arka plan müziği
Oyunlarda genellikle arka planda döngü halinde müzik çalar.
Döngü kısa ve tahmin edilebilirse çok can sıkıcı olabilir. Bir oyuncu bir alanda veya seviyede takılı kaldıysa ve arka planda sürekli olarak aynı kesit çalıyorsa daha fazla hayal kırıklığına yol açmamak için parçayı kademeli olarak azaltmak yararlı olabilir. Diğer bir strateji de oyunun bağlamına bağlı olarak, çeşitli yoğunluklarda seslerin kademeli olarak birbirine geçiş yaptığı bir mix oluşturmaktır.
Örneğin, oyuncunuz destansı bir patron savaşının olduğu bir bölgedeyse atmosferik, haberci ve yoğun olmak üzere duygusal açıdan farklı çeşitli mix'ler kullanabilirsiniz. Müzik sentezleme yazılımları genellikle, dışa aktarma işleminde kullanılacak parça grubunu seçerek bir parçaya dayalı olarak birkaç miksaj (aynı uzunlukta) dışa aktarmanıza olanak tanır. Böylece, parçalar arasında geçiş yaparken rahatsız edici geçişler olmasını önler ve parçalar arasında tutarlılık sağlayabilirsiniz.
Ardından, Web Audio API'yi kullanarak bu örneklerin tümünü XHR üzerinden BufferLoader sınıfı gibi bir şey kullanarak içe aktarabilirsiniz (bu konu Web Audio API tanıtım makalesinde ayrıntılı olarak ele alınmıştır. Seslerin yüklenmesi zaman aldığından oyunda kullanılan öğeler sayfa yüklenirken, seviyenin başında veya oyuncu oyun oynarken kademeli olarak yüklenmelidir.
Ardından, her düğüm için bir kaynak ve her kaynak için bir kazanç düğümü oluşturup grafiği bağlarsınız.
Bunu yaptıktan sonra bu kaynakların tümünü aynı anda döngüde oynatabilirsiniz. Hepsi aynı uzunlukta olduğundan Web Audio API, bunların hizalı kalmasını sağlar. Karakter son patron savaşına yaklaştıkça veya uzaklaştıkça oyun, aşağıdaki gibi bir kazanç miktarı algoritması kullanarak zincirdeki ilgili düğümlerin her biri için kazanç değerlerini değiştirebilir:
// Assume gains is an array of AudioGainNode, normVal is the intensity
// between 0 and 1.
var value = normVal - (gains.length - 1);
// First reset gains on all nodes.
for (var i = 0; i < gains.length; i++) {
gains[i].gain.value = 0;
}
// Decide which two nodes we are currently between, and do an equal
// power crossfade between them.
var leftNode = Math.floor(value);
// Normalize the value between 0 and 1.
var x = value - leftNode;
var gain1 = Math.cos(x - 0.5*Math.PI);
var gain2 = Math.cos((1.0 - x) - 0.5*Math.PI);
// Set the two gains accordingly.
gains[leftNode].gain.value = gain1;
// Check to make sure that there's a right node.
if (leftNode < gains.length - 1) {
// If there is, adjust its gain.
gains[leftNode + 1].gain.value = gain2;
}
Yukarıdaki yaklaşımda iki kaynak aynı anda çalınır ve eşit güç eğrileri kullanarak (girişte açıklandığı gibi) aralarında geçiş yaparız.
Eksik bağlantı: Ses etiketi ile Web Audio
Günümüzde birçok oyun geliştirici, içerik yayınlamaya uygun olduğu için arka plan müzikleri için <audio>
etiketini kullanır. Artık <audio>
etiketindeki içeriği Web Audio bağlamına getirebilirsiniz.
<audio>
etiketi, akış içeriğiyle çalışabildiğinden bu teknik faydalı olabilir. Böylece, içeriğin tamamını indirmek yerine arka plan müziğini hemen çalabilirsiniz. Akışları Web Audio API'ye getirerek akışları değiştirebilir veya analiz edebilirsiniz. Aşağıdaki örnekte, <audio>
etiketi aracılığıyla çalınan müziğe düşük geçiş filtresi uygulanmaktadır:
var audioElement = document.querySelector('audio');
var mediaSourceNode = context.createMediaElementSource(audioElement);
// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
mediaSourceNode.connect(filter);
filter.connect(context.destination);
<audio>
etiketini Web Audio API ile entegre etme hakkında daha ayrıntılı bilgi için bu kısa makaleyi inceleyin.
Ses efektleri
Oyunlar genellikle kullanıcı girişine veya oyun durumundaki değişikliklere yanıt olarak ses efektleri çalar. Ancak arka plan müziği gibi ses efektleri de çok çabuk can sıkıcı hale gelebilir. Bunu önlemek için genellikle benzer ancak farklı seslerden oluşan bir havuz bulundurmak faydalıdır. Bu, ayak sesi örneklerinde hafif varyasyonlardan Warcraft serisinde birimleri tıklamaya yanıt olarak görülen büyük varyasyonlara kadar değişiklik gösterebilir.
Oyunlardaki ses efektlerinin bir diğer önemli özelliği, aynı anda birden fazla ses efektinin çalınabilmesidir. Birden fazla aktör tarafından makineli tüfekle ateşlenen bir silahlı çatışmanın ortasında olduğunuzu hayal edin. Her makineli tüfek saniyede birçok kez ateş eder ve aynı anda onlarca ses efektinin çalınmasına neden olur. Web Audio API'nin en iyi performans gösterdiği alanlardan biri, birden fazla kaynaktan gelen sesleri tam olarak zamanlanmış şekilde aynı anda oynatmaktır.
Aşağıdaki örnekte, oynatma süresi zaman içinde kademeli olarak ayarlanmış birden fazla ses kaynağı oluşturarak birden fazla ayrı mermi örneğinden makineli tüfek mermisi oluşturulmaktadır.
var time = context.currentTime;
for (var i = 0; i < rounds; i++) {
var source = this.makeSource(this.buffers[M4A1]);
source.noteOn(time + i - interval);
}
Oyununuzdaki tüm makineli tüfeklerin sesi tam olarak böyle olsaydı bu oldukça sıkıcı olurdu. Elbette bunlar, hedefe olan mesafeye ve göreli konuma göre sese göre değişir (bu konu hakkında daha sonra daha fazla bilgi verilecektir). Ancak bu bile yeterli olmayabilir. Neyse ki Web Audio API, yukarıdaki örneği iki şekilde kolayca değiştirmenizi sağlar:
- Mermilerin ateşlenmesi arasındaki zamanda küçük bir kayma
- Gerçek dünyadaki rastgeleliği daha iyi simüle etmek için her bir örneğin playbackRate değerini değiştirerek (ayrıca perdeyi de değiştirerek).
Bu tekniklerin gerçek hayatta nasıl kullanıldığını gösteren daha gerçekçi bir örnek için rastgele örnekleme kullanan ve daha ilginç bir top çarpışması sesi için playbackRate değerini değiştiren bilardo masası demosuna göz atın.
3D konumsal ses
Oyunlar genellikle 2D veya 3D olarak bazı geometrik özelliklere sahip bir dünyada geçer. Bu durumda, stereo konumlandırılmış ses deneyimin sürükleyiciliğini büyük ölçüde artırabilir. Neyse ki Web Audio API, kullanımı oldukça kolay yerleşik donanım hızlandırmalı konumsal ses özellikleriyle birlikte gelir. Bu arada, aşağıdaki örneğin anlaşılır olması için stereo hoparlörleriniz (tercihen kulaklık) olduğundan emin olmanız gerekir.
Yukarıdaki örnekte, kanvasın ortasında bir dinleyici (kişi simgesi) vardır ve fare, kaynağın (hoparlör simgesi) konumunu etkiler. Yukarıda, bu tür bir etki elde etmek için AudioPannerNode'un kullanıldığı basit bir örnek verilmiştir. Yukarıdaki örnekte temel fikir, ses kaynağının konumunu ayarlayarak fare hareketine yanıt vermektir.
PositionSample.prototype.changePosition = function(position) {
// Position coordinates are in normalized canvas coordinates
// with -0.5 < x, y < 0.5
if (position) {
if (!this.isPlaying) {
this.play();
}
var mul = 2;
var x = position.x / this.size.width;
var y = -position.y / this.size.height;
this.panner.setPosition(x - mul, y - mul, -0.5);
} else {
this.stop();
}
};
Web Audio'nun uzamsallaştırmayı işleme şekli hakkında bilmeniz gerekenler:
- Dinleyici varsayılan olarak orijindedir (0, 0, 0).
- Web Audio konum API'leri birimsizdir. Bu nedenle, demonun sesini daha iyi hale getirmek için bir çarpan ekledim.
- Web Audio, y yukarı olan Kartezyen koordinatları kullanır (çoğu bilgisayar grafik sistemiyle tam tersi). Bu nedenle, yukarıdaki snippet'te y eksenini değiştiriyorum.
Gelişmiş: ses konileri
Konum modeli, büyük ölçüde OpenAL'a dayalı, çok güçlü ve oldukça gelişmiştir. Daha ayrıntılı bilgi için yukarıdaki bağlantıdaki spesifikasyonun 3. ve 4. bölümlerine bakın.
Web Audio API bağlamına bağlı tek bir AudioListener vardır. Bu dinleyici, konum ve yön aracılığıyla uzayda yapılandırılabilir. Her kaynak, giriş sesini üç boyutlu hale getiren bir AudioPannerNode üzerinden iletilebilir. Kaydırma düğümünde konum ve yönün yanı sıra mesafe ve yön modeli bulunur.
Mesafe modeli, kaynağa olan yakınlığa bağlı olarak kazanç miktarını belirtir. Yönsel model ise dinleyicinin iç koni içinde, iç ve dış koni arasında veya dış koni dışında olması durumunda kazanç miktarını (genellikle negatif) belirleyen bir iç ve dış koni belirterek yapılandırılabilir.
var panner = context.createPanner();
panner.coneOuterGain = 0.5;
panner.coneOuterAngle = 180;
panner.coneInnerAngle = 0;
Örneğim 2D olsa da bu model üçüncü boyuta kolayca genellenebilir. 3D olarak uzamsallaştırılmış ses örneği için bu konumsal örnek bölümüne bakın. Web Audio ses modeli, konuma ek olarak isteğe bağlı olarak Doppler kaymaları için hızı da içerir. Bu örnekte Doppler etkisi daha ayrıntılı bir şekilde gösterilmektedir.
Bu konu hakkında daha fazla bilgi için [konumsal ses ve WebGL'yi karıştırma][webgl] konulu ayrıntılı eğitimimizi okuyun.
Oda efektleri ve filtreleri
Gerçekte, sesin algılanma şekli büyük ölçüde sesin duyulduğu odaya bağlıdır. Aynı gıcırdayan kapı, büyük bir açık salona kıyasla bodrum katında çok farklı ses çıkarır. Üretim değeri yüksek oyunlarda bu efektler taklit edilmelidir. Çünkü her ortam için ayrı bir örnek grubu oluşturmak çok pahalı ve daha fazla öğe ve daha fazla oyun verisi anlamına gelir.
Kabaca söylemek gerekirse, ham ses ile gerçekte duyulan ses arasındaki fark için kullanılan ses terimi dürtü yanıtıdır. Bu impuls yanıtları zahmetli bir şekilde kaydedilebilir. Hatta size kolaylık sağlamak için önceden kaydedilmiş bu impuls yanıt dosyalarının çoğunu barındıran siteler vardır (ses olarak depolanır).
Belirli bir ortamdan nasıl dürtü yanıtları oluşturulabileceği hakkında daha fazla bilgi için Web Audio API spesifikasyonunun Dönüşüm bölümündeki "Kayıt Ayarı" bölümünü okuyun.
Amacımız açısından daha da önemlisi, Web Audio API'nin ConvolverNode'u kullanarak bu dürtü yanıtlarını seslerimize uygulamanın kolay bir yolunu sunmasıdır.
// Make a source node for the sample.
var source = context.createBufferSource();
source.buffer = this.buffer;
// Make a convolver node for the impulse response.
var convolver = context.createConvolver();
convolver.buffer = this.impulseResponseBuffer;
// Connect the graph.
source.connect(convolver);
convolver.connect(context.destination);
Web Audio API spesifikasyonu sayfasındaki oda efektleri demosuna ve harika bir caz standardının kuru (ham) ve ıslak (örnekleyici aracılığıyla işlenmiş) miksajı üzerinde kontrol sahibi olmanızı sağlayan bu örneğe de göz atın.
Son geri sayım
Bir oyun oluşturdunuz, konumsal sesinizi yapılandırdınız ve şimdi grafınızda aynı anda çalınan çok sayıda AudioNode'unuz var. Harika. Ancak dikkate almanız gereken bir şey daha var:
Birden fazla ses normalleştirme olmadan birbirinin üzerine yığıldığı için hoparlörünüzün kapasitesinin eşiğini aştığınız bir durumla karşılaşabilirsiniz. Tuval sınırlarını aşan resimler gibi, ses dalga biçimi maksimum eşiğini aşarsa sesler de kırpılabilir ve belirgin bir bozulma meydana gelir. Dalga biçimi şöyle görünür:
Aşağıda, kırpma işleminin kullanıldığı gerçek bir örnek verilmiştir. Dalga biçimi kötü görünüyor:
Yukarıdaki gibi sert bozulmaları veya tam tersine, dinleyicilerinizi sesi sonuna kadar açmaya zorlayan aşırı bastırılmış miksleri dinlemeniz önemlidir. Böyle bir durumdaysanız bu sorunu düzeltmeniz gerekir.
Kırpma algılama
Teknik açıdan bakıldığında, kırpma, herhangi bir kanaldaki sinyalin değeri geçerli aralığı (-1 ile 1 arasında) aştığında gerçekleşir. Bu durum tespit edildiğinde, bunun gerçekleştiğine dair görsel geri bildirim vermek faydalı olur. Bunu güvenilir bir şekilde yapmak için grafiğinize bir JavaScriptAudioNode ekleyin. Ses grafiği aşağıdaki gibi ayarlanır:
// Assume entire sound output is being piped through the mix node.
var meter = context.createJavaScriptNode(2048, 1, 1);
meter.onaudioprocess = processAudio;
mix.connect(meter);
meter.connect(context.destination);
Aşağıdaki processAudio
işleyicisinde de kırpma algılanabilir:
function processAudio(e) {
var buffer = e.inputBuffer.getChannelData(0);
var isClipping = false;
// Iterate through buffer to check if any of the |values| exceeds 1.
for (var i = 0; i < buffer.length; i++) {
var absValue = Math.abs(buffer[i]);
if (absValue >= 1) {
isClipping = true;
break;
}
}
}
Genel olarak, performans nedeniyle JavaScriptAudioNode
öğesini aşırı kullanmamaya dikkat edin. Bu durumda, ölçümün alternatif bir uygulaması, requestAnimationFrame
tarafından belirlenen oluşturma zamanında ses grafiğinde getByteFrequencyData
için bir RealtimeAnalyserNode
anketi yapabilir. Bu yaklaşım daha verimlidir ancak ses sinyali çok daha hızlı değişirken oluşturma işlemi en fazla saniyede 60 kez gerçekleştiği için sinyalin çoğu (kesilebileceği yerler dahil) atlanır.
Klip algılama çok önemli olduğu için gelecekte yerleşik bir MeterNode
Web Audio API düğümü görebiliriz.
Kırpma işlemini önleme
Ana AudioGainNode'daki kazancı ayarlayarak, sesinizi kırpmayı önleyecek bir seviyeye indirebilirsiniz. Ancak pratikte, oyununuzda çalınan sesler çok çeşitli faktörlere bağlı olabileceğinden, tüm durumlar için kırpmayı önleyen ana kazanç değerine karar vermek zor olabilir. Genel olarak, en kötü durumu öngörmek için kazançlarda ayarlamalar yapmanız gerekir ancak bu, bilimden çok sanata yakın bir konudur.
Biraz şeker ekleyin
Sıkıştırıcılar, müzik ve oyun prodüksiyonunda sinyali yumuşatmak ve genel sinyaldeki ani artışları kontrol etmek için yaygın olarak kullanılır. Bu işlev, Web Audio dünyasında DynamicsCompressorNode
aracılığıyla kullanılabilir. DynamicsCompressorNode
, ses grafiğinize eklenebilir. Böylece daha yüksek, daha zengin ve daha dolgun bir ses elde edebilir, ayrıca ses kırpmaya yardımcı olabilirsiniz.
Spesifikasyonu doğrudan alıntılayan bu düğüm
Dinamik sıkıştırma kullanmak genellikle iyi bir fikirdir. Özellikle de daha önce de belirtildiği gibi, hangi seslerin ne zaman çalınacağını tam olarak bilmediğiniz oyun ortamlarında dinamik sıkıştırma kullanmak iyi bir fikirdir. DinahMoe Labs'in Plink uygulaması, çalınan seslerin tamamen size ve diğer katılımcılara bağlı olması nedeniyle bu duruma mükemmel bir örnektir. Sık karşılaşılan bir durum olmasa da, titizlikle masterlanmış ve zaten "tam kıvamda" ses verecek şekilde ayarlanmış parçalarla uğraştığınız durumlar dışında, sıkıştırıcı çoğu durumda kullanışlıdır.
Bunu uygulamak için ses grafiğinize genellikle hedeften önceki son düğüm olarak bir DynamicsCompressorNode eklemeniz yeterlidir:
// Assume the output is all going through the mix node.
var compressor = context.createDynamicsCompressor();
mix.connect(compressor);
compressor.connect(context.destination);
Dinamik sıkıştırma hakkında daha fazla bilgi için bu Wikipedia makalesini inceleyebilirsiniz.
Özetlemek gerekirse, kırpmayı dikkatlice dinleyin ve bir ana kazanç düğümü ekleyerek bunu önleyin. Ardından, dinamik sıkıştırıcı düğümü kullanarak tüm miksi sıkılaştırın. Ses grafiğiniz şöyle görünebilir:
Sonuç
Web Audio API'yi kullanarak oyun sesleri geliştirmenin en önemli yönlerini ele aldık. Bu tekniklerle, doğrudan tarayıcınızda gerçekten ilgi çekici ses deneyimleri oluşturabilirsiniz. Sohbet oturumunu kapatmadan önce size tarayıcıya özgü bir ipucu vermek istiyorum: Sekmeniz sayfa görünürlüğü API'sini kullanarak arka plana giderse sesi duraklattığınızdan emin olun. Aksi takdirde, kullanıcınız için can sıkıcı bir deneyim oluşturabilirsiniz.
Web Audio hakkında daha fazla bilgi için daha giriş niteliğinde olan başlangıç makalesine göz atın. Sorularınız varsa Web Audio SSS bölümünde yanıtlarını bulabilirsiniz. Son olarak, başka sorularınız varsa Stack Overflow'da web-audio etiketini kullanarak sorun.
Sohbet oturumunu sonlandırmadan önce, Web Audio API'nin günümüzde gerçek oyunlarda kullanıldığı bazı harika örnekleri paylaşmak isterim:
- Field Runners ve teknik ayrıntılar hakkında bir makale
- Angry Birds kısa süre önce Web Audio API'ye geçiş yaptı. Daha fazla bilgi için bu makaleyi inceleyin.
- Üç boyutlu sesi mükemmel şekilde kullanan Skid Racer.