실행 화면
기능
- 캔버스로 공, 패들, 벽돌 생성하기
- 키보드 이벤트 핸들러로 패들 이동시키기
- 공 이동 애니메이션 구현
- 벽돌과 공이 부딪혔을 때 벽돌 보이지 않게 하고 score 올리기
- 공이 바닥과 닿았을 때 live 하나씩 줄기
- live가 다 줄었을 때 게임 오버, 게임 다시 시작
코드
const rulesBtn = document.getElementById("rules-btn");
const closeBtn = document.getElementById("close-btn");
const rules = document.getElementById("rules");
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const liveText = document.querySelector(".liveText");
const scoreText = document.querySelector(".scoreText");
let score = 0;
let live = 3;
const brickRowCount = 9;
const brickColumnCount = 5;
const delay = 500;
const ball = {
x: canvas.width / 2,
y: canvas.height / 2,
size: 10,
speed: 4,
dx: 4,
dy: -4,
visible: true,
};
const paddle = {
x: canvas.width / 2 - 40,
y: canvas.height - 20,
w: 80,
h: 10,
speed: 8,
dx: 0,
visible: true,
};
const brickInfo = {
w: 70,
h: 20,
padding: 10,
offsetX: 45,
offsetY: 60,
visible: true,
};
const bricks = [];
for (let i = 0; i < brickRowCount; i++) {
bricks[i] = [];
for (let j = 0; j < brickColumnCount; j++) {
const x = i * (brickInfo.w + brickInfo.padding) + brickInfo.offsetX;
const y = j * (brickInfo.h + brickInfo.padding) + brickInfo.offsetY;
bricks[i][j] = { x, y, ...brickInfo }; // x, y는 새로, 나머지는 기존의 오브젝트 그대로
}
}
console.log(bricks);
function drawBall() {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.size, 0, Math.PI * 2, true);
// arc(x, y, radius, startAngle, endAngle, anticlockwise)
// (x, y) 위치에 원점, 반지름 r, startAngle 에서 시작하여 endAngle 에서 끝나며 주어진 anticlockwise 방향으로 향하는 (기본값은 시계방향 회전) 호
ctx.fillStyle = ball.visible ? "white" : "transparent";
ctx.fill();
ctx.closePath();
}
function drawBricks() {
bricks.forEach((column) => {
column.forEach((brick) => {
ctx.beginPath();
ctx.rect(brick.x, brick.y, brick.w, brick.h);
ctx.fillStyle = brick.visible ? "white" : "transparent";
ctx.shadowColor = "green";
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fill();
ctx.closePath();
});
});
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddle.x, paddle.y, paddle.w, paddle.h);
ctx.fillStyle = paddle.visible ? "white" : "transparent";
ctx.fill();
}
function drawScore() {
scoreText.innerHTML = `SCORE: ${score}`;
}
function draw() {
// clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
drawPaddle();
drawBricks();
}
function movePaddle() {
paddle.x += paddle.dx; // paddle 위치를 업데이트
if (paddle.x + paddle.w > canvas.width) {
// 패들이 캔버스 오른쪽 끝에 있을 때
paddle.x = canvas.width - paddle.w;
}
if (paddle.x < 0) {
// 패들이 캔버스 왼쪽 끝에 있을 때
paddle.x = 0;
}
}
function moveBall() {
ball.x += ball.dx;
ball.y += ball.dy;
// 오른쪽, 왼쪽 벽 충돌
if (ball.x + ball.size > canvas.width || ball.x - ball.size < 0) {
ball.dx *= -1;
}
// 위쪽, 아래쪽 벽 충돌
if (ball.y + ball.size > canvas.height || ball.y - ball.size < 0) {
ball.dy *= -1;
}
// 패들과 충돌
if (
ball.x - ball.size > paddle.x &&
ball.x + ball.size < paddle.x + paddle.w &&
ball.y + ball.size > paddle.y
) {
ball.dy *= -1;
}
// 벽돌과 충돌
bricks.forEach((column) => {
column.forEach((brick) => {
if (brick.visible) {
if (
ball.x - ball.size > brick.x &&
ball.x + ball.size < brick.x + brick.w &&
ball.y + ball.size > brick.y &&
ball.y - ball.size < brick.y + brick.h
) {
ball.dy *= -1;
brick.visible = false;
increaseScore();
}
}
});
});
// 바닥과 부딪혔을 때 게임 짐
if (ball.y + ball.size > canvas.height) {
live--;
drawLive();
}
if (live === 0) {
showAllBricks();
score = 0;
live = 3;
drawScore();
drawLive();
}
}
function drawLive() {
liveText.innerHTML = `LIVE: ${live}`;
}
function increaseScore() {
score++;
drawScore();
// 벽돌을 다 깼을 때
if (score % (brickRowCount * brickColumnCount) === 0) {
ball.visible = false;
paddle.visible = false;
// 0.5초 이후에 다시 게임 시작
setTimeout(function () {
showAllBricks();
score = 0;
paddle.x = canvas.width / 2 - 40;
paddle.y = canvas.height - 20;
ball.x = canvas.width / 2;
ball.y = canvas.height / 2;
ball.visible = true;
paddle.visible = true;
}, delay);
}
}
function showAllBricks() {
bricks.forEach((column) => {
column.forEach((brick) => (brick.visible = true));
});
}
// 캔버스 업데이트하고 애니메이션 그리기
function update() {
movePaddle();
moveBall();
draw();
requestAnimationFrame(update);
// 브라우저에게 원하는 애니메이션을 알리고
// 다음 리페이트가 진행되기전까지 해당 애니메이션을 업데이트하는 함수를 호출
// 다음 리페인트에서 그 다음 프레임을 애니메이트하려면 콜백 루틴이 반드시 스스로 requestAnimationFrame()을 호출해야함.
}
function keyDown(e) {
if (e.key === "ArrowRight") {
paddle.dx = paddle.speed;
} else if (e.key === "ArrowLeft") {
paddle.dx = -paddle.speed;
}
}
function keyUp(e) {
if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
paddle.dx = 0;
}
}
document.addEventListener("keydown", keyDown);
document.addEventListener("keyup", keyUp);
rulesBtn.addEventListener("click", () => rules.classList.add("show"));
closeBtn.addEventListener("click", () => rules.classList.remove("show"));
function init() {
update();
drawLive();
drawScore();
}
init();
캔버스를 이용해 그리고 requestAnimationFrame()을 이용해 애니메이션을 구현하는 것이 아직 낯설어
조금 어려움이 있었다.
자바스크립트로 게임을 구현한다면 캔버스를 잘 사용할 수 있어야 하겠다는 생각이 든다.
벽돌 색상을 랜덤하게, 난이도에 따라 공의 속도 조절 등 더 많은 것을 구현하고 싶었으나 시간상 더 하지 못해서 아쉬움이 남는다.
'FRONT > JAVASCRIPT' 카테고리의 다른 글
[Javascript] Rest API란? (0) | 2022.02.08 |
---|---|
[TS] TypeScript란? (테스트/설치) (0) | 2022.01.20 |
[Javascript] 애니메이션 구현 - requestAnimationFrame (0) | 2021.08.15 |
[JS] 바닐라 자바스크립트 무한 스크롤링(infinite scrolling) (0) | 2021.08.12 |
[JS] 바닐라 자바스크립트 타이핑 게임 (0) | 2021.08.09 |