
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 오디오까지 다양한 기법을 활용하여 매력적인 게임을 만들 수 있다.