소개
캔버스와 HTML5를 사용하여 게임을 만들고 싶으신가요? 이 튜토리얼을 따르면 곧 학습을 시작할 수 있습니다.
본 가이드에서는 적어도 중급 이상의 자바스크립트에 대해 알고 있다고 가정합니다.
먼저 게임을 플레이하거나 도움말로 바로 이동하여 게임의 소스 코드를 확인할 수 있습니다.
캔버스 만들기
항목을 그리려면 캔버스를 만들어야 합니다. 이 가이드는 눈물 없는 가이드이므로 jQuery를 사용할 것입니다.
var CANVAS_WIDTH = 480;
var CANVAS_HEIGHT = 320;
var canvasElement = $("<canvas width='" + CANVAS_WIDTH +
"' height='" + CANVAS_HEIGHT + "'></canvas>");
var canvas = canvasElement.get(0).getContext("2d");
canvasElement.appendTo('body');
게임 루프
원활하고 지속적인 게임플레이의 모양을 시뮬레이션하기 위해 인간의 머리와 눈이 인지할 수 있는 속도보다 빨리 게임을 업데이트하고 화면을 다시 그리고 싶습니다.
var FPS = 30;
setInterval(function() {
update();
draw();
}, 1000/FPS);
지금은 업데이트 및 그리기 메서드를 비워 둘 수 있습니다. 알아야 할 중요한 점은 setInterval()
가 주기적으로 호출을 처리한다는 것입니다.
function update() { ... }
function draw() { ... }
Hello World
이제 게임 루프가 준비되었으므로 그리기 메서드를 업데이트하여 실제로 화면에 텍스트를 그려 보겠습니다.
function draw() {
canvas.fillStyle = "#000"; // Set color to black
canvas.fillText("Sup Bro!", 50, 50);
}
고정 텍스트에는 매우 유용하지만 이미 게임 루프가 설정되어 있으므로 매우 쉽게 움직일 수 있어야 합니다.
var textX = 50;
var textY = 50;
function update() {
textX += 1;
textY += 1;
}
function draw() {
canvas.fillStyle = "#000";
canvas.fillText("Sup Bro!", textX, textY);
}
이제 지휘봉을 빙글빙글 돌려 보세요. 따라가는 경우라면 이동하되 이전에 화면에 그려진 시간도 그대로 두어야 합니다. 잠시 시간을 내어 그 이유를 추측해 보세요. 이는 Google에서 화면을 지우지 않기 때문입니다. 이제 화면 삭제 코드를 그리기 메서드에 추가해 보겠습니다.
function draw() {
canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
canvas.fillStyle = "#000";
canvas.fillText("Sup Bro!", textX, textY);
}
이제 화면에서 텍스트를 이동했으니 실제 게임을 실행하는 단계 중 절반 정도가 된 것입니다. 컨트롤을 강화하고, 게임플레이를 개선하고, 그래픽을 보정하면 됩니다. 실제 게임의 7분의 1 정도일 수도 있지만 좋은 소식은 튜토리얼에 훨씬 더 많은 내용이 있다는 것입니다.
플레이어 만들기
플레이어 데이터를 보유하고 그리기와 같은 작업을 담당하는 객체를 만듭니다. 여기서는 모든 정보를 보유하는 간단한 객체 리터럴을 사용하여 플레이어 객체를 만듭니다.
var player = {
color: "#00A",
x: 220,
y: 270,
width: 32,
height: 32,
draw: function() {
canvas.fillStyle = this.color;
canvas.fillRect(this.x, this.y, this.width, this.height);
}
};
지금은 플레이어를 나타내기 위해 간단한 색상의 직사각형을 사용합니다. 게임을 그릴 때 캔버스를 지우고 플레이어를 그립니다.
function draw() {
canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
player.draw();
}
키보드 컨트롤
jQuery 핫키 사용
jQuery 핫키 플러그인을 사용하면 여러 브라우저에서 훨씬 쉽게 키를 처리할 수 있습니다.
해독할 수 없는 브라우저 간 keyCode
및 charCode
문제를 겪는 대신 다음과 같이 이벤트를 바인딩할 수 있습니다.
$(document).bind("keydown", "left", function() { ... });
어떤 키에 어떤 코드가 있는지 자세히 알아볼 필요가 없으므로 큰 도움이 됩니다. 단순히 플레이어가 위로 버튼을 누르면 뭔가를 하는 것처럼 할 수 있는 것이 좋습니다. jQuery 핫키가 이를 잘 처리해줍니다.
플레이어 움직임
JavaScript가 키보드 이벤트를 처리하는 방식은 전적으로 이벤트에 기반합니다. 즉, 키가 다운되었는지 확인하는 기본 제공 쿼리가 없으므로 자체 쿼리를 사용해야 합니다.
이벤트 기반의 키 처리 방식을 사용하면 안 되는 이유가 무엇인지 궁금하실 것입니다. 이는 키보드 반복 속도가 시스템마다 다르고 게임 루프의 타이밍에 구속되지 않기 때문입니다. 따라서 게임플레이가 시스템마다 크게 다를 수 있습니다. 일관된 환경을 만들려면 키보드 이벤트 감지를 게임 루프와 긴밀하게 통합하는 것이 중요합니다.
다행히 이벤트 쿼리를 사용할 수 있도록 16줄 JS 래퍼가 포함되어 있습니다. 이를 key_status.js라고 하며 keydown.left
등을 확인하여 언제든지 키의 상태를 쿼리할 수 있습니다.
이제 키가 다운되었는지 쿼리할 수 있으므로 이 간단한 업데이트 메서드를 사용하여 플레이어를 이동할 수 있습니다.
function update() {
if (keydown.left) {
player.x -= 2;
}
if (keydown.right) {
player.x += 2;
}
}
지휘봉을 빙글빙글 돌려 보세요.
플레이어는 화면 밖으로 이동할 수 있습니다. 플레이어의 위치를 고정하여 범위 내에 유지해 보겠습니다. 또한 플레이어가 약간 느린 것처럼 보이므로 속도를 높여보겠습니다.
function update() {
if (keydown.left) {
player.x -= 5;
}
if (keydown.right) {
player.x += 5;
}
player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}
입력을 더 쉽게 추가할 수 있으므로 일종의 발사체를 추가해 보겠습니다.
function update() {
if (keydown.space) {
player.shoot();
}
if (keydown.left) {
player.x -= 5;
}
if (keydown.right) {
player.x += 5;
}
player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}
player.shoot = function() {
console.log("Pew pew");
// :) Well at least adding the key binding was easy...
};
더 많은 게임 객체 추가
발사체
이제 실제로 발사체를 추가해 보겠습니다. 먼저 다음 위치에 모두 저장할 컬렉션이 필요합니다.
var playerBullets = [];
다음으로 글머리기호 인스턴스를 만들 생성자가 필요합니다.
function Bullet(I) {
I.active = true;
I.xVelocity = 0;
I.yVelocity = -I.speed;
I.width = 3;
I.height = 3;
I.color = "#000";
I.inBounds = function() {
return I.x >= 0 && I.x <= CANVAS_WIDTH &&
I.y >= 0 && I.y <= CANVAS_HEIGHT;
};
I.draw = function() {
canvas.fillStyle = this.color;
canvas.fillRect(this.x, this.y, this.width, this.height);
};
I.update = function() {
I.x += I.xVelocity;
I.y += I.yVelocity;
I.active = I.active && I.inBounds();
};
return I;
}
플레이어가 사격하면 글머리기호 인스턴스를 만들어 글머리기호 컬렉션에 추가해야 합니다.
player.shoot = function() {
var bulletPosition = this.midpoint();
playerBullets.push(Bullet({
speed: 5,
x: bulletPosition.x,
y: bulletPosition.y
}));
};
player.midpoint = function() {
return {
x: this.x + this.width/2,
y: this.y + this.height/2
};
};
이제 글머리기호 업데이트를 업데이트 단계 함수에 추가해야 합니다. 글머리기호 컬렉션이 무기한으로 채워지지 않도록 하기 위해 활성 글머리기호만 포함하도록 글머리기호 목록을 필터링합니다. 또한 적과 충돌한 탄환을 제거할 수 있습니다.
function update() {
...
playerBullets.forEach(function(bullet) {
bullet.update();
});
playerBullets = playerBullets.filter(function(bullet) {
return bullet.active;
});
}
마지막 단계는 글머리기호를 그리는 것입니다.
function draw() {
...
playerBullets.forEach(function(bullet) {
bullet.draw();
});
}
적
이제 글머리기호를 추가한 것과 거의 같은 방식으로 적을 추가할 차례입니다.
enemies = [];
function Enemy(I) {
I = I || {};
I.active = true;
I.age = Math.floor(Math.random() * 128);
I.color = "#A2B";
I.x = CANVAS_WIDTH / 4 + Math.random() * CANVAS_WIDTH / 2;
I.y = 0;
I.xVelocity = 0
I.yVelocity = 2;
I.width = 32;
I.height = 32;
I.inBounds = function() {
return I.x >= 0 && I.x <= CANVAS_WIDTH &&
I.y >= 0 && I.y <= CANVAS_HEIGHT;
};
I.draw = function() {
canvas.fillStyle = this.color;
canvas.fillRect(this.x, this.y, this.width, this.height);
};
I.update = function() {
I.x += I.xVelocity;
I.y += I.yVelocity;
I.xVelocity = 3 * Math.sin(I.age * Math.PI / 64);
I.age++;
I.active = I.active && I.inBounds();
};
return I;
};
function update() {
...
enemies.forEach(function(enemy) {
enemy.update();
});
enemies = enemies.filter(function(enemy) {
return enemy.active;
});
if(Math.random() < 0.1) {
enemies.push(Enemy());
}
};
function draw() {
...
enemies.forEach(function(enemy) {
enemy.draw();
});
}
이미지 로드 및 그리기
이 모든 상자가 날아다니는 것을 보는 것은 멋지지만 이러한 상자들의 이미지가 있으면 더 멋질 것입니다. 캔버스에 이미지를 로드하고 그리면 어색하기 쉽습니다. 이러한 고통과 고통을 방지하기 위해 간단한 유틸리티 클래스를 사용할 수 있습니다.
player.sprite = Sprite("player");
player.draw = function() {
this.sprite.draw(canvas, this.x, this.y);
};
function Enemy(I) {
...
I.sprite = Sprite("enemy");
I.draw = function() {
this.sprite.draw(canvas, this.x, this.y);
};
...
}
충돌 감지
이 모든 딜이 화면에서 여기저기 날아다니지만 서로 상호작용하지는 않습니다. 모든 것에 공격 시점을 알리려면 일종의 충돌 감지를 추가해야 합니다
간단한 직사각형 충돌 감지 알고리즘을 사용해 보겠습니다.
function collides(a, b) {
return a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y;
}
몇 가지 충돌 사항을 확인해 보겠습니다.
- 플레이어 글머리기호 => 적의 함선
- 플레이어 => 적의 함선
업데이트 메서드에서 호출할 수 있는 충돌을 처리하는 메서드를 만들어 보겠습니다.
function handleCollisions() {
playerBullets.forEach(function(bullet) {
enemies.forEach(function(enemy) {
if (collides(bullet, enemy)) {
enemy.explode();
bullet.active = false;
}
});
});
enemies.forEach(function(enemy) {
if (collides(enemy, player)) {
enemy.explode();
player.explode();
}
});
}
function update() {
...
handleCollisions();
}
이제 플레이어와 적에게 폭발 메서드를 추가해야 합니다. 그러면 제거하도록 신고되고 폭발을 일으킵니다.
function Enemy(I) {
...
I.explode = function() {
this.active = false;
// Extra Credit: Add an explosion graphic
};
return I;
};
player.explode = function() {
this.active = false;
// Extra Credit: Add an explosion graphic and then end the game
};
사운드
환경을 완성하기 위해 몇 가지 멋진 음향 효과를 추가하겠습니다. HTML5에서는 이미지와 마찬가지로 사운드를 사용하기가 다소 어려울 수 있지만, Google의 마법 같은 '눈물 없이(no-tears)' 형식인 Sound.js 덕분에 사운드를 매우 간단하게 만들 수 있습니다.
player.shoot = function() {
Sound.play("shoot");
...
}
function Enemy(I) {
...
I.explode = function() {
Sound.play("explode");
...
}
}
API는 이제 찢어지지 않지만 사운드를 추가하는 것이 현재 애플리케이션을 다운시키는 가장 빠른 방법입니다. 사운드가 잘리거나 브라우저 탭 전체가 삭제되는 경우가 드물지 않으므로 화장대를 준비합니다.
작별
정상적으로 작동하는 게임 데모를 확인해 보세요. 소스 코드를 ZIP 파일로 다운로드할 수도 있습니다.
자바스크립트 및 HTML5로 간단한 게임을 만드는 기본적인 방법을 배웠길 바랍니다. 적절한 수준의 추상화에서 프로그래밍하면 API의 더 어려운 부분에서 벗어날 수 있을 뿐만 아니라 향후 변화에 대비할 수 있습니다.