О чем этот пример
Управление аудио — ключевой элемент игровой атмосферы. В Phaser звук можно не просто воспроизводить, но и тонко настраивать, синхронизируя его с визуальными эффектами. Этот пример демонстрирует, как связать скорость анимации спрайта с параметрами звукового трека, а также как управлять глобальными и локальными настройками аудио через панель dat.GUI. Вы научитесь контролировать громкость, тон, скорость воспроизведения и режим mute, создавая динамичную и отзывчивую игровую среду.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
soundRight;
soundLeft;
horseRight;
horseLeft;
i = 0;
horseFrames = [];
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.bitmapFont('atari-classic', 'assets/fonts/bitmap/atari-classic.png', 'assets/fonts/bitmap/atari-classic.xml');
for (let i = 0; i < 12; i++)
{
this.horseFrames.push({
key: `horse${(`0${i}`).slice(-2)}`,
frame: '__BASE'
});
}
// Loading horse animation
for (let i = 0; i < this.horseFrames.length; i++)
{
this.load.image(this.horseFrames[i].key, `assets/animations/horse/frame_${(`0${i}`).slice(-2)}_delay-0.05s.png`);
}
// Loading music
this.load.audio('left', [
'assets/audio/Rossini - William Tell Overture (8 Bits Version)/left.ogg',
'assets/audio/Rossini - William Tell Overture (8 Bits Version)/left.mp3'
]);
this.load.audio('right', [
'assets/audio/Rossini - William Tell Overture (8 Bits Version)/right.ogg',
'assets/audio/Rossini - William Tell Overture (8 Bits Version)/right.mp3'
]);
}
create ()
{
this.anims.create({
key: 'horse',
frames: this.horseFrames,
frameRate: 20,
repeat: -1
});
this.horseLeft = this.add.sprite(200, 300, 'horse09');
this.horseLeft.setScale(400 / 480);
this.horseRight = this.add.sprite(600, 300, 'horse10');
this.horseRight.setScale(400 / 480);
this.soundLeft = this.sound.add('left');
this.soundLeft.play({
loop: true
});
this.soundRight = this.sound.add('right');
this.soundRight.play({
loop: true
});
if (this.sound.locked)
{
const text = this.add.bitmapText(400, 50, 'atari-classic', 'Tap to start', 40);
text.x -= Math.round(text.width / 2);
text.y -= Math.round(text.height / 2);
this.sound.once('unlocked', (soundManager) =>
{
text.visible = false;
this.start();
});
}
else
{
this.start();
}
}
update ()
{
this.horseLeft.anims.timeScale = this.soundLeft.totalRate;
this.horseRight.anims.timeScale = this.soundRight.totalRate;
this.horseLeft.setAlpha(this.sound.volume * this.soundLeft.volume);
this.horseRight.setAlpha(this.sound.volume * this.soundRight.volume);
this.horseLeft.visible = !this.sound.mute && !this.soundLeft.mute;
this.horseRight.visible = !this.sound.mute && !this.soundRight.mute;
}
start ()
{
this.horseLeft.play('horse');
this.horseRight.play('horse');
const gui = new dat.GUI();
const sm = gui.addFolder('Sound Manager');
sm.add(this.sound, 'mute').listen();
sm.add(this.sound, 'volume', 0, 1).listen();
sm.add(this.sound, 'rate', 0.5, 2).listen();
sm.add(this.sound, 'detune', -1200, 1200).step(50).listen();
sm.open();
const sl = gui.addFolder('Left');
sl.add(this.soundLeft, 'mute').listen();
sl.add(this.soundLeft, 'volume', 0, 1).listen();
sl.add(this.soundLeft, 'rate', 0.5, 2).listen();
sl.add(this.soundLeft, 'detune', -1200, 1200).step(50).listen();
sl.open();
const sr = gui.addFolder('Right');
sr.add(this.soundRight, 'mute').listen();
sr.add(this.soundRight, 'volume', 0, 1).listen();
sr.add(this.soundRight, 'rate', 0.5, 2).listen();
sr.add(this.soundRight, 'detune', -1200, 1200).step(50).listen();
sr.open();
}
}
/**
* @author Pavle Goloskokovic <pgoloskokovic@gmail.com> (http://prunegames.com)
*
* Images by Walter Newton (http://walternewton.tumblr.com/post/58684376490/like-a-train-in-the-night)
* Music by Classical 8 Bit (http://classical8bit.tumblr.com/) / CC BY
*/
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example,
pixelArt: true,
audio: {
disableWebAudio: true
}
};
for (; this.i < 12; this.i++)
{
this.horseFrames.push({
key: `horse${(`0${this.i}`).slice(-2)}`,
frame: '__BASE'
});
}
const game = new Phaser.Game(config);
Загрузка ресурсов: анимация и стереозвук
В методе preload() происходит подготовка всех необходимых ресурсов. Загружается растровый шрифт для текста, 12 кадров анимации лошади (каждый как отдельное изображение) и два аудиофайла, представляющих левый и правый каналы стереозаписи. Обратите внимание на формирование массива horseFrames: для каждого кадра создается объект с ключом и базовым фреймом, что впоследствии используется при создании анимации.
for (let i = 0; i < 12; i++)
{
this.horseFrames.push({
key: `horse${(`0${i}`).slice(-2)}`,
frame: '__BASE'
});
}
Аудио загружается с поддержкой нескольких форматов (OGG и MP3) для кросс-браузерной совместимости. Это особенно важно, так как в конфигурации отключен Web Audio API (disableWebAudio: true), и используется HTML5 Audio.
this.load.audio('left', [
'assets/audio/Rossini - William Tell Overture (8 Bits Version)/left.ogg',
'assets/audio/Rossini - William Tell Overture (8 Bits Version)/left.mp3'
]);
Создание сцены: анимация, звук и обработка блокировки
В create() создается анимация horse из загруженных кадров с частотой 20 кадров в секунд и бесконечным повтором. Добавляются два спрайта лошадей, которым задается начальный масштаб.
this.anims.create({
key: 'horse',
frames: this.horseFrames,
frameRate: 20,
repeat: -1
});
Затем создаются и сразу начинают воспроизводиться в цикле два звуковых объекта: soundLeft и soundRight.
this.soundLeft = this.sound.add('left');
this.soundLeft.play({
loop: true
});
Важный момент — обработка блокировки аудио в браузерах. Если звук заблокирован (автозапуск запрещен), выводится текст с предложением коснуться экрана. После разблокировки (событие unlocked) вызывается метод start(), который запускает анимацию и панель управления. Если блокировки нет, start() вызывается сразу.
if (this.sound.locked)
{
// ... создание текста ...
this.sound.once('unlocked', (soundManager) =>
{
text.visible = false;
this.start();
});
}
Динамическая связь: метод update()
Сердце примера — метод update(), который выполняется каждый кадр. Здесь происходит синхронизация анимации со звуком и управление видимостью спрайтов.
Скорость анимации каждой лошади (anims.timeScale) привязывается к итоговой скорости воспроизведения соответствующего звука. Итоговая скорость (totalRate) вычисляется с учетом и глобальной скорости (sound.rate), и локальной (soundLeft.rate или soundRight.rate).
this.horseLeft.anims.timeScale = this.soundLeft.totalRate;
this.horseRight.anims.timeScale = this.soundRight.totalRate;
Прозрачность (alpha) спрайта вычисляется как произведение глобальной громкости (sound.volume) и громкости конкретного звука. Это позволяет визуализировать как общее, так и индивидуальное управление громкостью.
this.horseLeft.setAlpha(this.sound.volume * this.soundLeft.volume);
Видимость спрайта (visible) определяется комбинацией глобального и локального режима mute. Лошадь исчезает, если звук заглушен на любом из уровней.
this.horseLeft.visible = !this.sound.mute && !this.soundLeft.mute;
Панель управления: dat.GUI
Метод start() создает интерактивную панель управления с помощью библиотеки dat.GUI. Панель разделена на три группы: общий менеджер звука (Sound Manager), левый канал (Left) и правый канал (Right).
Для каждого объекта настраиваются контролы для свойств mute, volume, rate и detune. Флаг listen() заставляет GUI автоматически обновлять отображение при изменении значения из кода (например, в update()).
const sm = gui.addFolder('Sound Manager');
sm.add(this.sound, 'mute').listen();
sm.add(this.sound, 'volume', 0, 1).listen();
sm.add(this.sound, 'rate', 0.5, 2).listen();
sm.add(this.sound, 'detune', -1200, 1200).step(50).listen();
detune изменяет высоту тона в центах (100 центов = 1 полутон). Параметр step(50) задает шаг изменения на полусемитон. Это мощный инструмент для создания звуковых эффектов, таких как замедление или ускорение времени в игре.
Конфигурация: использование HTML5 Audio
Ключевой момент конфигурации игры — явное указание использовать HTML5 Audio вместо Web Audio API. Это делается через свойство audio в объекте конфига.
const config = {
// ... другие параметры ...
audio: {
disableWebAudio: true
}
};
Такой подход может быть полезен для специфических случаев совместимости или контроля над аудиопотоком. Важно помнить, что HTML5 Audio имеет свои ограничения (например, по количеству одновременно воспроизводимых звуков) по сравнению с Web Audio API.
Что попробовать дальше
Пример наглядно показывает, как в Phaser звуковая система глубоко интегрирована в игровой цикл. Вы можете управлять аудиопараметрами на лету и мгновенно отражать эти изменения в графике, создавая целостный опыт. Для экспериментов попробуйте
- Связать
detuneс цветовым фильтром спрайта - Заменить плавное изменение прозрачности на дискретное мигание при определенных значениях громкости
- Использовать
totalRateдля управления скоростью частиц, испускаемых от анимированного объекта
