여러분이 사용하고 계신 브라우저는 HTML5를 지원하지 않기 때문에 몇몇 요소가 제대로 보이도록 JScript를 사용하고 있습니다. 하지만 여러분의 브라우저 설정에서 스크립트 기능이 꺼져있으므로, 현재 페이지를 제대로 확인하시려면 스크립트 기능을 켜주셔야 합니다. HTML 게임 - 사운드

HTML 게임 – 사운드

2일전 작성

HTML Canvas 게임 사운드

HTML Canvas 게임에서 사운드를 추가하는 방법을 알아본다. 배경음악, 효과음, 사운드 제어 등 다양한 오디오 기능을 통해 게임의 몰입감을 높이는 기법을 설명한다.

기본 사운드 추가

게임에 기본적인 사운드 효과를 추가해보자.

<canvas id="myCanvas1" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacle;
var mySound;
var myGameArea = {
canvas: document.getElementById("myCanvas1"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
function sound(src) {
this.sound = document.createElement("audio");
this.sound.src = src;
this.sound.setAttribute("preload", "auto");
this.sound.setAttribute("controls", "none");
this.sound.style.display = "none";
document.body.appendChild(this.sound);
this.play = function(){
this.sound.play();
}
this.stop = function(){
this.sound.pause();
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myObstacle = new component(10, 200, "green", 300, 120);
mySound = new sound("data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmM=");
myGameArea.start();
}
function updateGameArea() {
if (myGamePiece.crashWith(myObstacle)) {
mySound.play();
myGameArea.stop();
} else {
myGameArea.clear();
myObstacle.speedX = -1;
myObstacle.newPos();
myObstacle.update();
myGamePiece.speedX = 0;
myGamePiece.speedY = 0;
if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -1; }
if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 1; }
if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -1; }
if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 1; }
myGamePiece.newPos();
myGamePiece.update();
}
}
startGame();
</script>

sound 생성자 함수는 HTML Audio 요소를 생성하고 사운드 파일을 로드한다. 충돌이 발생했을 때 사운드가 재생된다.

배경음악 추가

게임에 배경음악을 추가하고 반복 재생하도록 설정해보자.

<canvas id="myCanvas2" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<button onclick="toggleMusic()">음악 토글</button>
<script>
var myGamePiece;
var myObstacle;
var myMusic;
var musicPlaying = false;
var myGameArea = {
canvas: document.getElementById("myCanvas2"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
function sound(src) {
this.sound = document.createElement("audio");
this.sound.src = src;
this.sound.setAttribute("preload", "auto");
this.sound.setAttribute("controls", "none");
this.sound.style.display = "none";
document.body.appendChild(this.sound);
this.play = function(){
this.sound.play();
}
this.stop = function(){
this.sound.pause();
}
this.loop = function(){
this.sound.loop = true;
this.sound.play();
}
this.setVolume = function(volume){
this.sound.volume = volume;
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myObstacle = new component(10, 200, "green", 300, 120);
myMusic = new sound("data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmM=");
myMusic.setVolume(0.3);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myObstacle.speedX = -1;
myObstacle.newPos();
myObstacle.update();
myGamePiece.speedX = 0;
myGamePiece.speedY = 0;
if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -1; }
if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 1; }
if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -1; }
if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 1; }
myGamePiece.newPos();
myGamePiece.update();
}
function toggleMusic() {
if (musicPlaying) {
myMusic.stop();
musicPlaying = false;
} else {
myMusic.loop();
musicPlaying = true;
}
}
startGame();
</script>

loop() 메서드는 배경음악을 반복 재생한다. setVolume() 메서드로 볼륨을 조절할 수 있으며, 토글 버튼으로 음악을 켜고 끌 수 있다.

다양한 효과음

게임의 다양한 상황에 맞는 효과음을 추가해보자.

<canvas id="myCanvas3" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacles = [];
var myBonuses = [];
var myScore;
var jumpSound;
var collectSound;
var crashSound;
var totalScore = 0;
var myGameArea = {
canvas: document.getElementById("myCanvas3"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
if (e.keyCode == 32) {
jumpSound.play();
}
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
function sound(src) {
this.sound = document.createElement("audio");
this.sound.src = src;
this.sound.setAttribute("preload", "auto");
this.sound.setAttribute("controls", "none");
this.sound.style.display = "none";
document.body.appendChild(this.sound);
this.play = function(){
this.sound.currentTime = 0;
this.sound.play();
}
this.stop = function(){
this.sound.pause();
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
if (this.type == "text") {
ctx.font = this.width + " " + this.height;
ctx.fillStyle = color;
ctx.fillText(this.text, this.x, this.y);
} else if (this.type == "bonus") {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x + this.width/2, this.y + this.height/2, this.width/2, 0, 2 * Math.PI);
ctx.fill();
} else {
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myScore = new component("20px", "Arial", "black", 280, 30, "text");
jumpSound = new sound("data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmM=");
collectSound = new sound("data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmM=");
crashSound = new sound("data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmM=");
myGameArea.start();
}
function updateGameArea() {
for (i = 0; i < myObstacles.length; i += 1) { if (myGamePiece.crashWith(myObstacles[i])) { crashSound.play(); myGameArea.stop(); return; } } for (i = myBonuses.length - 1; i >= 0; i -= 1) {
if (myGamePiece.crashWith(myBonuses[i])) {
collectSound.play();
totalScore += 50;
myBonuses.splice(i, 1);
}
}
myGameArea.clear();
myGameArea.frameNo += 1;
totalScore += 1;
if (myGameArea.frameNo == 1 || everyinterval(150)) {
var x = myGameArea.canvas.width;
var y = Math.floor(Math.random() * (myGameArea.canvas.height - 100));
var width = Math.floor(Math.random() * 20 + 10);
var height = Math.floor(Math.random() * 80 + 40);
myObstacles.push(new component(width, height, "green", x, y));
}
if (everyinterval(300)) {
var x = myGameArea.canvas.width;
var y = Math.floor(Math.random() * (myGameArea.canvas.height - 30));
myBonuses.push(new component(20, 20, "gold", x, y, "bonus"));
}
for (i = myObstacles.length - 1; i >= 0; i -= 1) {
myObstacles[i].speedX = -2;
myObstacles[i].newPos();
if (myObstacles[i].x + myObstacles[i].width < 0) { myObstacles.splice(i, 1); } else { myObstacles[i].update(); } } for (i = myBonuses.length - 1; i >= 0; i -= 1) {
myBonuses[i].speedX = -2;
myBonuses[i].newPos();
if (myBonuses[i].x + myBonuses[i].width < 0) { myBonuses.splice(i, 1); } else { myBonuses[i].update(); } } myGamePiece.speedX = 0; myGamePiece.speedY = 0; if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -2; } if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 2; } if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -2; } if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 2; } myGamePiece.newPos(); myGamePiece.update(); myScore.text = "점수: " + totalScore; myScore.update(); } function everyinterval(n) { if ((myGameArea.frameNo / n) % 1 == 0) {return true;} return false; } startGame(); </script>[/code]

각기 다른 상황에 맞는 효과음을 추가했다. 스페이스바를 누르면 점프 사운드, 보너스 수집 시 수집 사운드, 충돌 시 충돌 사운드가 재생된다.

사운드 풀링 시스템

같은 사운드를 빠르게 연속 재생할 수 있도록 사운드 풀링을 구현해보자.

[code lang="markup"]<canvas id="myCanvas4" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myBullets = [];
var shootSound;
var myGameArea = {
canvas: document.getElementById("myCanvas4"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function soundPool(src, poolSize) {
this.pool = [];
this.currentIndex = 0;
for (var i = 0; i < poolSize; i++) { var audio = document.createElement("audio"); audio.src = src; audio.setAttribute("preload", "auto"); audio.setAttribute("controls", "none"); audio.style.display = "none"; document.body.appendChild(audio); this.pool.push(audio); } this.play = function() { this.pool[this.currentIndex].currentTime = 0; this.pool[this.currentIndex].play(); this.currentIndex = (this.currentIndex + 1) % this.pool.length; } } function component(width, height, color, x, y, type) { this.type = type; this.width = width; this.height = height; this.speedX = 0; this.speedY = 0; this.x = x; this.y = y; this.active = true; this.update = function() { if (!this.active) return; ctx = myGameArea.context; ctx.fillStyle = color; ctx.fillRect(this.x, this.y, this.width, this.height); } this.newPos = function() { if (!this.active) return; this.x += this.speedX; this.y += this.speedY; if (this.type == "bullet" && (this.x > myGameArea.canvas.width || this.x < 0)) { this.active = false; } } } function startGame() { myGamePiece = new component(30, 30, "red", 225, 135); shootSound = new soundPool("data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmM=", 5); myGameArea.start(); } function updateGameArea() { myGameArea.clear(); myGameArea.frameNo += 1; myGamePiece.speedX = 0; myGamePiece.speedY = 0; if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -2; } if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 2; } if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -2; } if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 2; } if (myGameArea.key && myGameArea.key == 32 && myGameArea.frameNo % 10 == 0) { shootSound.play(); myBullets.push(new component(5, 10, "yellow", myGamePiece.x + myGamePiece.width/2, myGamePiece.y, "bullet")); myBullets[myBullets.length-1].speedY = -5; } myGamePiece.newPos(); myGamePiece.update(); for (var i = myBullets.length - 1; i >= 0; i--) {
myBullets[i].newPos();
if (!myBullets[i].active) {
myBullets.splice(i, 1);
} else {
myBullets[i].update();
}
}
}
startGame();
</script>

soundPool 클래스는 여러 개의 오디오 인스턴스를 관리하여 빠른 연속 재생을 가능하게 한다. 스페이스바를 연속으로 눌러 총알을 발사할 때마다 사운드가 재생된다.

사운드 페이드 효과

사운드에 페이드 인/아웃 효과를 추가해보자.

<canvas id="myCanvas5" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<button onclick="fadeInMusic()">음악 페이드 인</button>
<button onclick="fadeOutMusic()">음악 페이드 아웃</button>
<script>
var myGamePiece;
var myMusic;
var fadeInterval;
var myGameArea = {
canvas: document.getElementById("myCanvas5"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function sound(src) {
this.sound = document.createElement("audio");
this.sound.src = src;
this.sound.setAttribute("preload", "auto");
this.sound.setAttribute("controls", "none");
this.sound.style.display = "none";
this.sound.loop = true;
document.body.appendChild(this.sound);
this.play = function(){
this.sound.play();
}
this.stop = function(){
this.sound.pause();
}
this.setVolume = function(volume){
this.sound.volume = Math.max(0, Math.min(1, volume));
}
this.getVolume = function(){
return this.sound.volume;
}
this.fadeIn = function(duration) {
this.sound.volume = 0;
this.play();
var targetVolume = 0.5;
var steps = duration / 50;
var volumeStep = targetVolume / steps;
var currentStep = 0;
var fadeInterval = setInterval(() => {
if (currentStep >= steps) {
clearInterval(fadeInterval);
this.sound.volume = targetVolume;
} else {
this.sound.volume = currentStep * volumeStep;
currentStep++;
}
}, 50);
}
this.fadeOut = function(duration) {
var startVolume = this.sound.volume;
var steps = duration / 50;
var volumeStep = startVolume / steps;
var currentStep = 0;
var fadeInterval = setInterval(() => {
if (currentStep >= steps) {
clearInterval(fadeInterval);
this.sound.volume = 0;
this.stop();
} else {
this.sound.volume = startVolume - (currentStep * volumeStep);
currentStep++;
}
}, 50);
}
}
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 225, 135);
myMusic = new sound("data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmM=");
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.speedX = 1;
myGamePiece.newPos();
myGamePiece.update();
}
function fadeInMusic() {
myMusic.fadeIn(2000);
}
function fadeOutMusic() {
myMusic.fadeOut(2000);
}
startGame();
</script>

fadeIn()과 fadeOut() 메서드는 지정된 시간 동안 볼륨을 점진적으로 변경한다. 부드러운 음악 전환 효과를 만들 수 있다.

3D 오디오 효과

Web Audio API를 사용하여 3D 공간 오디오 효과를 구현해보자.

<canvas id="myCanvas6" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var mySoundSource;
var audioContext;
var panner;
var audioBuffer;
var myGameArea = {
canvas: document.getElementById("myCanvas6"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
initAudio();
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function initAudio() {
try {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
panner = audioContext.createPanner();
panner.panningModel = 'HRTF';
panner.distanceModel = 'inverse';
panner.refDistance = 1;
panner.maxDistance = 10000;
panner.rolloffFactor = 1;
panner.coneInnerAngle = 360;
panner.coneOuterAngle = 0;
panner.coneOuterGain = 0;
panner.connect(audioContext.destination);
} catch(e) {
console.log('Web Audio API is not supported in this browser');
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
if (this.type == "soundsource" && panner) {
var normalizedX = (this.x / myGameArea.canvas.width - 0.5) * 10;
var normalizedY = (this.y / myGameArea.canvas.height - 0.5) * 10;
panner.positionX.setValueAtTime(normalizedX, audioContext.currentTime);
panner.positionY.setValueAtTime(-normalizedY, audioContext.currentTime);
panner.positionZ.setValueAtTime(-1, audioContext.currentTime);
}
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
}
function playPositionalSound() {
if (audioContext && panner) {
var oscillator = audioContext.createOscillator();
var gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(panner);
oscillator.frequency.setValueAtTime(440, audioContext.currentTime);
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 1);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 1);
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 225, 135);
mySoundSource = new component(20, 20, "blue", 100, 100, "soundsource");
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.speedX = 0;
myGamePiece.speedY = 0;
if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -2; }
if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 2; }
if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -2; }
if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 2; }
if (myGameArea.key && myGameArea.key == 32) {
playPositionalSound();
}
mySoundSource.speedX = Math.sin(Date.now() * 0.001) * 2;
mySoundSource.speedY = Math.cos(Date.now() * 0.001) * 1.5;
myGamePiece.newPos();
mySoundSource.newPos();
myGamePiece.update();
mySoundSource.update();
}
startGame();
</script>

Web Audio API의 PannerNode를 사용하여 3D 공간 오디오를 구현했다. 파란색 사운드 소스의 위치에 따라 스테레오 패닝과 거리감이 조절된다.

💡 게임 사운드 시스템 설계 팁:
• 사운드 파일은 용량과 품질의 균형을 고려하여 선택하자. MP3, OGG, WAV 등 브라우저 호환성을 확인하자
• 사운드 풀링을 사용하면 빠른 연속 재생과 메모리 효율성을 얻을 수 있다
• 사용자 상호작용 후에 오디오를 재생해야 브라우저의 자동재생 정책을 준수할 수 있다
• 볼륨 조절과 음소거 기능을 제공하여 사용자 경험을 개선하자

사운드 매니저 시스템

모든 사운드를 통합 관리하는 사운드 매니저를 구현해보자.

<canvas id="myCanvas7" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<button onclick="soundManager.toggleMute()">음소거 토글</button>
<input type="range" min="0" max="100" value="50" onchange="soundManager.setMasterVolume(this.value/100)"> 볼륨
<script>
var myGamePiece;
var myObstacle;
var soundManager = {
sounds: {},
masterVolume: 0.5,
muted: false,
init: function() {
this.sounds.jump = this.createSound("data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmM=");
this.sounds.crash = this.createSound("data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmM=");
this.sounds.bgm = this.createSound("data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCz2Y3/DGdSEGLIXO8tiJNwgZaLvt559NEAxQp+PwtmM=");
this.sounds.bgm.loop = true;
},
createSound: function(src) {
var audio = document.createElement("audio");
audio.src = src;
audio.setAttribute("preload", "auto");
audio.setAttribute("controls", "none");
audio.style.display = "none";
document.body.appendChild(audio);
return audio;
},
play: function(soundName, volume) {
if (this.muted || !this.sounds[soundName]) return;
var sound = this.sounds[soundName];
sound.volume = (volume || 1) * this.masterVolume;
sound.currentTime = 0;
sound.play().catch(function(error) {
console.log('Audio play failed:', error);
});
},
playLoop: function(soundName, volume) {
if (this.muted || !this.sounds[soundName]) return;
var sound = this.sounds[soundName];
sound.volume = (volume || 1) * this.masterVolume;
sound.loop = true;
sound.play().catch(function(error) {
console.log('Audio play failed:', error);
});
},
stop: function(soundName) {
if (this.sounds[soundName]) {
this.sounds[soundName].pause();
this.sounds[soundName].currentTime = 0;
}
},
setMasterVolume: function(volume) {
this.masterVolume = Math.max(0, Math.min(1, volume));
for (var soundName in this.sounds) {
if (this.sounds[soundName].volume > 0) {
this.sounds[soundName].volume = this.masterVolume;
}
}
},
toggleMute: function() {
this.muted = !this.muted;
for (var soundName in this.sounds) {
this.sounds[soundName].muted = this.muted;
}
}
};
var myGameArea = {
canvas: document.getElementById("myCanvas7"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
if (e.keyCode == 32) {
soundManager.play('jump');
}
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function startGame() {
soundManager.init();
myGamePiece = new component(30, 30, "red", 10, 120);
myObstacle = new component(10, 200, "green", 300, 120);
myGameArea.start();
soundManager.playLoop('bgm', 0.3);
}
function updateGameArea() {
if (myGamePiece.crashWith(myObstacle)) {
soundManager.play('crash');
myGameArea.stop();
} else {
myGameArea.clear();
myObstacle.speedX = -1;
myObstacle.newPos();
myObstacle.update();
myGamePiece.speedX = 0;
myGamePiece.speedY = 0;
if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -1; }
if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 1; }
if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -1; }
if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 1; }
myGamePiece.newPos();
myGamePiece.update();
}
}
startGame();
</script>

사운드 매니저는 모든 사운드를 중앙에서 관리하고, 마스터 볼륨 조절, 음소거, 사운드 재생/정지 등의 기능을 제공한다. 이를 통해 일관된 오디오 경험을 제공할 수 있다.

HTML5 Canvas 게임에서 사운드는 플레이어의 몰입감과 게임 경험을 크게 향상시키는 중요한 요소이다. 기본적인 효과음부터 복잡한 3D 오디오까지 다양한 기법을 활용하여 매력적인 게임을 만들 수 있다.

참고
Mingg`s Diary
밍구
공부 목적 블로그