Phát triển âm thanh trong trò chơi bằng Web Audio API

Giới thiệu

Âm thanh là một phần quan trọng giúp trải nghiệm đa phương tiện trở nên hấp dẫn. Nếu bạn đã từng thử xem phim có âm thanh, có thể bạn đã nhận thấy điều này.

Trò chơi cũng không phải là ngoại lệ! Những kỷ niệm đáng nhớ nhất về trò chơi điện tử của tôi là âm nhạc và hiệu ứng âm thanh. Hiện tại, trong nhiều trường hợp sau gần hai thập kỷ sau khi chơi các tác phẩm yêu thích, tôi vẫn không thể thoát ra khỏi các bản sáng tác của Zelda của Koji Kondonhạc phim Diablo của Matt Uelmen. Sự hấp dẫn tương tự cũng áp dụng cho hiệu ứng âm thanh, chẳng hạn như phản hồi nhấp chuột đơn vị có thể nhận ra tức thì trong Warcraft, cũng như các mẫu từ các tác phẩm kinh điển của Nintendo.

Âm thanh trò chơi đưa ra một số thử thách thú vị. Để tạo ra âm nhạc trò chơi thuyết phục, nhà thiết kế cần điều chỉnh trạng thái trò chơi tiềm ẩn khó đoán mà người chơi gặp phải. Trên thực tế, các phần của trò chơi có thể diễn ra trong một thời lượng không xác định, âm thanh có thể tương tác với môi trường và kết hợp theo những cách phức tạp, chẳng hạn như hiệu ứng phòng và vị trí âm thanh tương đối. Cuối cùng, có thể có một số lượng lớn âm thanh phát cùng một lúc, tất cả đều cần phải kết hợp với nhau và hiển thị hài hoà với nhau mà không đưa ra các hình phạt về hiệu suất.

Âm thanh trò chơi trên web

Đối với các trò chơi đơn giản, chỉ cần sử dụng thẻ <audio> là đủ. Tuy nhiên, nhiều trình duyệt cung cấp cách triển khai kém, dẫn đến sự cố âm thanh và độ trễ cao. Hy vọng đây là vấn đề tạm thời, vì các nhà cung cấp đang nỗ lực để cải thiện các phương thức triển khai tương ứng. Để xem nhanh thông tin về trạng thái của thẻ <audio>, có một bộ kiểm thử tuyệt vời tại areweplayingyet.org.

Tuy nhiên, khi xem xét kỹ hơn thông số kỹ thuật của thẻ <audio>, bạn sẽ thấy rõ ràng rằng có nhiều việc đơn giản là không thể làm được với mã này. Điều này không có gì đáng ngạc nhiên vì nó được thiết kế để phát nội dung đa phương tiện. Có một số điểm hạn chế như sau:

  • Không thể áp dụng bộ lọc cho tín hiệu âm thanh
  • Không có cách nào để truy cập vào dữ liệu PCM thô
  • Không có khái niệm về vị trí và hướng của nguồn và người nghe
  • Không có thời gian chi tiết.

Trong phần còn lại của bài viết, tôi sẽ đi sâu vào một số chủ đề này trong bối cảnh âm thanh trong trò chơi được viết bằng API Web Audio. Để có phần giới thiệu ngắn gọn về API này, hãy xem hướng dẫn bắt đầu sử dụng.

Nhạc nền

Trò chơi thường có nhạc nền phát liên tục.

Bạn có thể cảm thấy rất khó chịu nếu vòng lặp ngắn và dễ dự đoán. Nếu người chơi bị mắc kẹt trong một khu vực hoặc một cấp độ và cùng một mẫu đó liên tục phát trong nền, thì bạn nên làm mờ dần bản nhạc để ngăn chặn sự thất vọng thêm nữa. Một chiến lược khác là kết hợp nhiều cường độ dần dần với nhau, tuỳ thuộc vào ngữ cảnh của trò chơi.

Ví dụ: nếu người chơi của bạn ở trong một khu vực có trận đấu trùm hoành tráng, thì bạn có thể có một vài kiểu kết hợp cảm xúc khác nhau, từ Khí quyển cho đến báo trước hoặc gay cấn. Phần mềm tổng hợp nhạc thường cho phép bạn xuất một số danh sách kết hợp (có cùng thời lượng) dựa trên một đoạn nhạc bằng cách chọn một tập hợp các bản nhạc để dùng khi xuất. Bằng cách đó, bạn sẽ có một số tính nhất quán nội bộ và tránh gặp phải các chuyển đổi gây khó chịu khi chuyển đổi mờ dần từ bản nhạc này sang bản nhạc khác.

Bãi đỗ xe

Sau đó, bằng Web Audio API, bạn có thể nhập tất cả các mẫu này bằng cách dùng một nội dung như lớp BufferLoader thông qua XHR (nội dung này đã được đề cập chi tiết trong bài viết giới thiệu về API Web âm thanh. Quá trình tải âm thanh sẽ mất nhiều thời gian, vì vậy, bạn phải tải các tài sản dùng trong trò chơi khi tải trang, khi bắt đầu cấp độ hoặc có thể tăng dần trong khi người chơi đang chơi.

Tiếp theo, bạn sẽ tạo một nguồn cho mỗi nút và một nút thu được cho mỗi nguồn rồi kết nối biểu đồ.

Sau khi thực hiện việc này, bạn có thể phát lại đồng thời tất cả các nguồn này trong một vòng lặp và vì tất cả các nguồn này đều có thời lượng như nhau nên Web Audio API sẽ đảm bảo rằng các nguồn này sẽ vẫn được căn chỉnh. Khi nhân vật đến gần hơn hoặc xa hơn từ trận đấu trùm cuối, trò chơi có thể thay đổi giá trị nhận được cho từng nút tương ứng trong chuỗi, bằng cách sử dụng thuật toán số tiền thu được như sau:

// 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;
}

Trong phương pháp trên, hai nguồn phát cùng một lúc và chúng tôi phân biệt giữa chúng bằng cách sử dụng đường cong công suất bằng nhau (như đã mô tả trong phần giới thiệu).

Ngày nay, nhiều nhà phát triển trò chơi sử dụng thẻ <audio> cho nhạc nền vì thẻ này rất phù hợp với việc phát trực tuyến nội dung. Giờ đây, bạn có thể đưa nội dung từ thẻ <audio> sang ngữ cảnh Web Audio.

Kỹ thuật này có thể hữu ích vì thẻ <audio> có thể hoạt động với nội dung phát trực tuyến, cho phép bạn phát nhạc nền ngay lập tức thay vì phải đợi toàn bộ nội dung tải xuống. Bằng cách đưa luồng vào API Web âm thanh, bạn có thể thao tác hoặc phân tích luồng. Ví dụ sau đây áp dụng bộ lọc thông thấp cho nhạc được phát thông qua thẻ <audio>:

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);

Để thảo luận đầy đủ hơn về cách tích hợp thẻ <audio> với Web Audio API, hãy xem bài viết ngắn này.

Hiệu ứng âm thanh

Các trò chơi thường phát lại hiệu ứng âm thanh để phản hồi hoạt động đầu vào của người dùng hoặc các thay đổi về trạng thái trò chơi. Tuy nhiên, giống như nhạc nền, hiệu ứng âm thanh có thể nhanh chóng gây khó chịu. Để tránh tình trạng này, bạn nên tạo một nhóm các âm thanh tương tự nhưng khác nhau. Điều này có thể khác nhau, từ những biến thể nhẹ của mẫu bước chân đến các biến thể lớn, như đã thấy trong loạt video Warcraft để phản hồi thao tác nhấp vào đơn vị.

Một tính năng chính khác của hiệu ứng âm thanh trong trò chơi là có thể có nhiều hiệu ứng âm thanh cùng lúc. Hãy tưởng tượng bạn đang ở giữa một trận đấu súng có nhiều diễn viên cùng bắn súng máy. Mỗi súng máy bắn ra nhiều lần trong mỗi giây, khiến hàng chục hiệu ứng âm thanh được phát cùng lúc. Phát lại âm thanh từ nhiều nguồn được xác định thời gian chính xác cùng lúc là một trong những điểm ưu tiên của API Web Audio.

Ví dụ sau đây tạo một viên đạn súng máy từ nhiều mẫu đạn riêng lẻ bằng cách tạo nhiều nguồn âm thanh có thời gian phát xen kẽ.

var time = context.currentTime;
for (var i = 0; i < rounds; i++) {
    var source = this.makeSource(this.buffers[M4A1]);
    source.noteOn(time + i - interval);
}

Nếu tất cả các loại súng máy trong trò chơi của bạn nghe giống hệt như vậy, thì điều đó sẽ khá nhàm chán. Tất nhiên, các âm thanh này sẽ thay đổi theo âm thanh dựa trên khoảng cách từ mục tiêu và vị trí tương đối (chúng tôi sẽ tìm hiểu thêm về nội dung này ở phần sau), nhưng ngay cả như vậy thì có thể vẫn chưa đủ. Thật may là API Web âm thanh cung cấp một cách để dễ dàng chỉnh sửa ví dụ trên theo hai cách:

  1. Với sự thay đổi nhỏ về thời gian giữa các lần bắn đạn
  2. Bằng cách thay đổiPlaybackRate của từng mẫu (đồng thời thay đổi cao độ) để mô phỏng tốt hơn tính ngẫu nhiên của thế giới thực.

Để xem ví dụ thực tế hơn về các kỹ thuật này đang hoạt động, hãy xem bản minh hoạ của Bảng gộp, trong đó sử dụng phương pháp lấy mẫu ngẫu nhiên và thay đổi tỷ lệ phát để có âm thanh va chạm bóng thú vị hơn.

Âm thanh theo vị trí 3D

Trò chơi thường được đặt trong một thế giới với một số thuộc tính hình học, ở dạng 2D hoặc 3D. Trong trường hợp này, âm thanh được định vị âm thanh nổi có thể làm tăng đáng kể độ sống động của trải nghiệm. Thật may là API Web âm thanh đi kèm với các tính năng âm thanh theo vị trí được tăng tốc phần cứng tích hợp sẵn rất dễ sử dụng. Nhân tiện, bạn nên đảm bảo mình có loa âm thanh nổi (tốt nhất là tai nghe) để ví dụ sau trở nên hợp lý.

Trong ví dụ trên, có một trình nghe (biểu tượng hình người) ở giữa canvas và chuột sẽ ảnh hưởng đến vị trí của nguồn (biểu tượng loa). Trên đây là một ví dụ đơn giản về cách sử dụng AudioPannerNode để có được loại hiệu ứng này. Ý tưởng cơ bản của mẫu ở trên là để phản hồi chuyển động của chuột bằng cách đặt vị trí của nguồn âm thanh như sau:

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();
    }
};

Những điều cần biết về cách xử lý không gian của Web Audio:

  • Theo mặc định, trình nghe sẽ ở gốc (0, 0, 0).
  • Các API vị trí Âm thanh web là không có đơn vị, vì vậy, tôi đã giới thiệu một hệ số nhân để làm cho bản minh hoạ âm thanh tốt hơn.
  • Âm thanh trên web sử dụng toạ độ y-is-up (ngược với hầu hết các hệ thống đồ hoạ máy tính). Đó là lý do tại sao tôi hoán đổi trục y trong đoạn mã ở trên

Nâng cao: nón âm thanh

Mô hình vị trí rất mạnh mẽ và khá tiên tiến, chủ yếu dựa trên OpenAL. Để biết thêm thông tin chi tiết, hãy xem phần 3 và 4 của thông số kỹ thuật được liên kết phía trên.

Mô hình vị trí

Có một AudioListener được đính kèm vào ngữ cảnh API Web Audio. Bạn có thể định cấu hình trong không gian thông qua vị trí và hướng. Mỗi nguồn có thể được truyền qua một AudioPannerNode giúp tạo không gian cho âm thanh đầu vào. Nút xoay có vị trí và hướng, cũng như mô hình khoảng cách và mô hình hướng.

Mô hình khoảng cách chỉ định mức đạt được tuỳ thuộc vào khoảng cách gần với nguồn, trong khi mô hình định hướng có thể được định cấu hình bằng cách chỉ định hình nón trong và ngoài giúp xác định mức tăng (thường là âm) nếu trình nghe nằm trong hình nón trong, giữa hình nón trong và ngoài hoặc bên ngoài hình nón ngoài.

var panner = context.createPanner();
panner.coneOuterGain = 0.5;
panner.coneOuterAngle = 180;
panner.coneInnerAngle = 0;

Mặc dù ví dụ của tôi ở dạng 2D, nhưng mô hình này sẽ dễ dàng khái quát hoá thành phương diện thứ ba. Để xem ví dụ về âm thanh tạo không gian trong mô hình 3D, hãy xem mẫu vị trí này. Ngoài vị trí, mô hình âm thanh của Web âm thanh cũng tuỳ ý đưa vào tốc độ cho sự dịch chuyển doppler. Ví dụ này cho thấy hiệu ứng doppler chi tiết hơn.

Để biết thêm thông tin về chủ đề này, hãy đọc hướng dẫn chi tiết này về [kết hợp âm thanh theo vị trí và WebGL][webgl].

Hiệu ứng và bộ lọc cho phòng

Trong thực tế, cách mà âm thanh được tiếp nhận chủ yếu phụ thuộc vào căn phòng mà âm thanh đó được nghe. Cùng một cánh cửa cót két sẽ phát ra rất khác khi ở dưới tầng hầm, so với một sảnh mở lớn. Các trò chơi có giá trị sản xuất cao sẽ muốn mô phỏng những hiệu ứng này, vì việc tạo một tập hợp mẫu riêng cho từng môi trường rất tốn kém và sẽ dẫn đến nhiều tài sản cũng như lượng dữ liệu trò chơi lớn hơn.

Nói một cách đơn giản, thuật ngữ âm thanh thể hiện sự khác biệt giữa âm thanh thô và âm thanh thực tế trong thực tế là phản ứng xung. Những phản hồi xung động này có thể được ghi lại một cách cẩn thận và trên thực tế, có các trang web lưu trữ nhiều tệp phản hồi xung nhịp được ghi lại trước này (lưu trữ dưới dạng âm thanh) để thuận tiện cho bạn.

Để biết thêm thông tin về cách tạo các phản hồi xung động từ một môi trường nhất định, hãy đọc phần "Thiết lập bản ghi" trong phần Chuyển đổi của thông số kỹ thuật API Web Audio.

Quan trọng hơn đối với mục đích của chúng ta, Web Audio API cung cấp một cách dễ dàng để áp dụng các phản hồi xung cho âm thanh bằng cách sử dụng ConvolverNode.

// 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);

Ngoài ra, hãy xem bản minh hoạ các hiệu ứng phòng này trên trang thông số kỹ thuật của Web Audio API, cũng như ví dụ này cho phép bạn kiểm soát quá trình kết hợp khô (thô) và ướt (xử lý thông qua convolver) theo tiêu chuẩn Jazz tuyệt vời.

Đếm ngược cuối cùng

Như vậy, bạn đã tạo được một trò chơi, định cấu hình âm thanh vị trí và giờ đây, bạn sẽ có một số lượng lớn AudioNode trong biểu đồ, tất cả đều phát đồng thời. Thật tuyệt, nhưng vẫn còn một điều khác cần cân nhắc:

Vì nhiều âm thanh xếp chồng lên nhau mà không chuẩn hoá, bạn có thể gặp phải trường hợp vượt quá ngưỡng khả năng của loa. Giống như hình ảnh vượt quá ranh giới canvas, âm thanh cũng có thể bị cắt nếu dạng sóng vượt quá ngưỡng tối đa, tạo ra hiện tượng méo hình riêng. Dạng sóng sẽ có dạng như sau:

Cắt

Sau đây là một ví dụ thực tế về tính năng cắt đoạn. Dạng sóng có vẻ không tốt:

Cắt

Điều quan trọng là phải theo dõi các âm thanh bị méo tiếng như lỗi trên, hoặc ngược lại, các bản phối bị quá mức khiến thính giả phải tăng âm lượng. Nếu rơi vào trường hợp này, bạn thực sự cần phải khắc phục!

Phát hiện đoạn video

Từ góc độ kỹ thuật, việc cắt đoạn xảy ra khi giá trị của tín hiệu trong bất kỳ kênh nào vượt quá phạm vi hợp lệ, cụ thể là từ -1 đến 1. Sau khi phát hiện vấn đề này, bạn nên cung cấp phản hồi bằng hình ảnh cho biết điều này đang xảy ra. Để làm việc này một cách đáng tin cậy, hãy đặt JavaScriptAudioNode vào biểu đồ của bạn. Biểu đồ âm thanh sẽ được thiết lập như sau:

// 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);

Ngoài ra, bạn có thể phát hiện tính năng cắt đoạn trong trình xử lý processAudio sau:

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;
    }
    }
}

Nhìn chung, hãy cẩn thận để không lạm dụng JavaScriptAudioNode vì lý do về hiệu suất. Trong trường hợp này, cách triển khai thay thế của chế độ đo sáng có thể thăm dò RealtimeAnalyserNode trong biểu đồ âm thanh cho getByteFrequencyData, tại thời điểm kết xuất, do requestAnimationFrame xác định. Phương pháp này hiệu quả hơn, nhưng bỏ lỡ hầu hết tín hiệu (bao gồm cả những vị trí có thể bị cắt bớt), vì quá trình kết xuất xảy ra tối đa 60 lần một giây, trong khi tín hiệu âm thanh thay đổi nhanh hơn nhiều.

Vì việc phát hiện đoạn video rất quan trọng, nên nhiều khả năng chúng ta sẽ thấy nút API Web âm thanh MeterNode tích hợp sẵn trong tương lai.

Ngăn cắt đoạn

Bằng cách điều chỉnh mức tăng âm trên AudioGainNode chính, bạn có thể giảm tần suất kết hợp đến mức ngăn chặn việc cắt xén. Tuy nhiên, trên thực tế, vì âm thanh chơi trong trò chơi có thể phụ thuộc vào nhiều yếu tố, nên có thể khó quyết định giá trị đạt được chính có thể ngăn việc cắt đoạn ở mọi trạng thái. Nhìn chung, bạn nên điều chỉnh các lợi ích để dự đoán trường hợp xấu nhất, nhưng đây không chỉ là một nghệ thuật hơn là khoa học.

Thêm một chút đường

Bộ nén thường được dùng trong quá trình sản xuất nhạc và trò chơi để kiểm soát tín hiệu và kiểm soát mức tăng đột biến về tín hiệu tổng thể. Chức năng này có sẵn trong môi trường Web Audio thông qua DynamicsCompressorNode. Bạn có thể chèn tính năng này vào biểu đồ âm thanh để mang lại âm thanh to hơn, phong phú hơn và đầy đủ hơn, đồng thời giúp bạn cắt bớt đoạn âm thanh. Trích dẫn trực tiếp thông số kỹ thuật, nút này

Nhìn chung, việc sử dụng tính năng nén động là một ý tưởng hay, đặc biệt là trong bối cảnh trò chơi, như đã thảo luận trước đó, bạn không biết chính xác âm thanh nào sẽ phát và thời điểm phát. Plink từ các phòng thí nghiệm của DinahMoe là một ví dụ hay của trường hợp này, vì âm thanh được phát lại hoàn toàn phụ thuộc vào bạn và những người tham gia khác. Bộ nén sẽ hữu ích trong hầu hết các trường hợp, ngoại trừ một số trường hợp hiếm gặp, khi đó, bạn đang phải xử lý các bản nhạc đã được nâng cao kỹ lưỡng, vốn đã được điều chỉnh để cho ra âm thanh " vừa phải".

Việc triển khai việc này chỉ đơn giản là đưa DynamicsCompressorNode vào biểu đồ âm thanh, thường là nút cuối cùng trước đích đến.:

// Assume the output is all going through the mix node.
var compressor = context.createDynamicsCompressor();
mix.connect(compressor);
compressor.connect(context.destination);

Để biết thêm thông tin chi tiết về tính năng nén động lực, bài viết này trên Wikipedia cung cấp rất nhiều thông tin.

Tóm lại, hãy cẩn thận về việc cắt đoạn và ngăn chặn tình trạng này bằng cách chèn một nút lấy giá trị chính. Sau đó, thắt chặt toàn bộ quá trình kết hợp bằng cách sử dụng một nút máy nén động. Biểu đồ âm thanh có thể có dạng như sau:

Kết quả cuối cùng

Kết luận

Bài viết đó trình bày những khía cạnh quan trọng nhất trong việc phát triển âm thanh trong trò chơi bằng cách sử dụng API Web âm thanh. Với các kỹ thuật này, bạn có thể tạo ra trải nghiệm âm thanh thực sự hấp dẫn ngay trong trình duyệt. Trước khi đăng xuất, tôi có thể đưa ra một mẹo dành riêng cho trình duyệt: hãy nhớ tạm dừng âm thanh nếu thẻ chuyển sang chế độ nền bằng API khả năng hiển thị trang. Nếu không, bạn sẽ có thể gây khó chịu cho người dùng.

Để biết thêm thông tin về Âm thanh trên web, hãy xem bài viết giới thiệu về cách bắt đầu sử dụng. Nếu bạn có thắc mắc, hãy xem câu hỏi đó đã có câu trả lời trong Câu hỏi thường gặp về âm thanh trên web. Cuối cùng, nếu bạn có thêm câu hỏi, hãy đặt câu hỏi trên StackOverflow bằng cách sử dụng thẻ web-audio.

Trước khi ký kết, hôm nay tôi xin chia sẻ một số cách sử dụng tuyệt vời của Web Audio API trong các trò chơi thực tế: