我正在尝试使用 nw.js(node.js + Chromium 页面)开发一个简单的游戏。
<canvas width="1200" height="800" id="main"></canvas>
<script>
var Mouse = {x: 0, y: 0, fire: false};
(async function() {
"use strict";
const reload = 25;
var ireload = 0;
const audioCtx = new AudioContext();
let fire = await fetch('shotgun.mp3');
let bgMusic = await fetch('hard.mp3');
fire = await fire.arrayBuffer();
bgMusic = await bgMusic.arrayBuffer();
const bgMdecoded = await audioCtx.decodeAudioData(bgMusic);
const fireDecoded = await audioCtx.decodeAudioData(fire);
const bgM = audioCtx.createBufferSource();
bgM.buffer = bgMdecoded;
bgM.loop = true;
bgM.connect(audioCtx.destination)
bgM.start(0);
let shot = audioCtx.createBufferSource();
shot.buffer = fireDecoded;
shot.connect(audioCtx.destination);
document.getElementById('main').onmousedown = function(e) {
Mouse.x = e.layerX;
Mouse.y = e.layerY;
Mouse.fire = true;
}
function main(tick) {
var dt = lastTick - tick;
lastTick = tick;
///take fire
if(--ireload < 0 && Mouse.fire) {
ireload = reload;
shot.start(0);
shot = audioCtx.createBufferSource();
shot.buffer = fireDecoded;
shot.connect(audioCtx.destination);
Mouse.fire = false;
}
/* moving objects, rendering on thread with offscreen canvas */
requestAnimationFrame(main);
}
let lastTick = performance.now();
main(lastTick);
})();
</script>
我已经将代码剥离到最小的工作示例。
问题在于射击,每次我开火(///开火)时,游戏都会降低 FPS。在 Kaiido 示例 (https://jsfiddle.net/sLpx6b3v/) 中也发生了完全相同的情况。这很好用,长时间使用它,但多次播放多种声音(游戏是射击游戏),会导致帧率下降,并且一段时间后 GC 会出现问题。
不到一年的游戏笔记本电脑从 60fps 下降到大约 40fps,在 Kaidos 示例中下降到大约 44fps。
声音可以解决什么问题?
期望的行为是无延迟/无 gc/无因声音引起的掉帧。背景中的那个效果很好。 我会尝试 AudioWorklet,但很难创建一个并处理瞬间的声音(可能是另一个问题)。
答案 0 :(得分:0)
可以重用缓冲区,有点hackish。
先创建
const audioCtx = new AudioContext();
然后像往常一样获取资源:
let fire = await fetch('shotgun.mp3');
fire = await fire.arrayBuffer();
fire = await audioCtx.decodeAudioData(fire);
const shot = audioCtx.createBufferSource();
shot.buffer = fire;
shot.loopEnd = 0.00001; //some small value to make it unplayable
shot.start(0);
然后,在活动期间(我的情况是鼠标向下):
shot.loopEnd = 1; //that restarts sound and plays in a loop.
接下来,播放完毕,重新设置
shot.loopEnd = 0.00001;
就我而言,我在 requestAnimationFrame 中停止它
<canvas width="1200" height="800" id="main"></canvas>
<script>
var Mouse = {x: 0, y: 0, fire: false};
(async function() {
"use strict";
const reload = 25;
var ireload = 0;
const audioCtx = new AudioContext();
let fire = await fetch('shotgun.mp3');
let bgMusic = await fetch('hard.mp3');
fire = await fire.arrayBuffer();
bgMusic = await bgMusic.arrayBuffer();
const bgMdecoded = await audioCtx.decodeAudioData(bgMusic);
const fireDecoded = await audioCtx.decodeAudioData(fire);
const bgM = audioCtx.createBufferSource();
bgM.buffer = bgMdecoded;
bgM.loop = true;
bgM.connect(audioCtx.destination)
bgM.start(0);
let shot = audioCtx.createBufferSource();
shot.buffer = fireDecoded;
shot.connect(audioCtx.destination);
shot.loopEnd = 0.00001; //some small value to make it unplayable
shot.start(0);
document.getElementById('main').onmousedown = function(e) {
Mouse.x = e.layerX;
Mouse.y = e.layerY;
Mouse.fire = true;
}
function main(tick) {
var dt = lastTick - tick;
lastTick = tick;
///take fire
//asuming 60fps, which is true in my case, I stop it after a second
if(reload < -35) {
shot.loopEnd = 0.00001;
}
if(--ireload < 0 && Mouse.fire) {
ireload = reload;
shot.loopEnd = 1; //that restarts sound and plays in a loop.
Mouse.fire = false;
}
/* moving objects, rendering on thread with offscreen canvas */
requestAnimationFrame(main);
}
let lastTick = performance.now();
main(lastTick);
})();
</script>
关于 GC 的说明,它确实可以快速处理音频缓冲区,但我已经检查过,GC 仅在存在分配和内存重新分配时触发。垃圾收集器会中断所有脚本执行,因此会出现卡顿、延迟。
我将内存池与这个技巧结合使用,在初始化时分配池,然后只重用对象,并且在第二次扫描后实际上没有 GC,它运行一次,在初始化后第二次启动,在优化后减少未使用的内存。之后,根本没有GC。使用类型化数组和 worker 提供了真正高性能的组合,具有 60 fps、清晰的声音和完全没有延迟。
您可能认为锁定 GC 是个坏主意。也许你是对的,但毕竟,仅仅因为有 GC 而浪费资源似乎也不是一个好主意。
经过测试,AudioWorklets 似乎按预期工作,但它们很笨重、难以维护并消耗大量资源,编写简单地将输入复制到输出的处理器违背了它的目的。 PostMessaging 系统确实是一个繁重的过程,你要么用标准方式连接并重新创建缓冲区,要么将其复制到 Worklet 空间并通过共享数组和原子操作手动管理。
您可能还对以下内容感兴趣:Writeup about WebAudio design 作者有相同的担忧并遇到完全相同的问题,引用
<块引用>我知道我在这里打了一场艰苦的战斗,但 GC 不是我们想要的 实时音频播放时需要。
保留一个 AudioBuffers 池似乎可行,尽管在我自己的测试中 在主要 GC 擦除之前,我仍然看到应用程序随着时间的推移缓慢增长到 12MB, 根据 Chrome 分析器。
和 Writeup about GC,其中描述了 JavaScript 中的内存泄漏。引用:
<块引用>考虑以下场景:
在这种情况下,大多数 GC 将不会运行任何进一步的收集传递。 换句话说,即使有无法访问的引用可用 对于收藏,收藏家不要求这些。这些不是 严格泄漏,但仍然会导致内存使用量高于平常。