ㅇ안녕하세요.
오늘은 벽돌깨기 게임 만들기 마지막 시간입니다.
이전 시간에 canvas에 원하는 도형을 그리고, 각 도형이 서로 닿았을 때 원하는 대로 동작하도록 처리하는 방법에 대해서 배웠습니다.
이번에는 실제 게임처럼 동작하도록 몇가지 기능을 추가해서 게임을 마무리 해보겠습니다.
이전 코드를 보고 싶으시면 아래 링크를 참고해주세요.
2024.08.12 - [JavaScript] - [JavaScript] 벽돌깨기 게임 만들기 (1)
2024.08.12 - [JavaScript] - [JavaScript] 벽돌깨기 게임 만들기 (2) 공 튕기기
1. index.html 수정
먼저 index.html을 수정해서 시작하기, 다시 시작 버튼을 만들어줍니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>벽돌 깨기 게임</title>
<style>
canvas {
background: #eee;
display: block;
margin: 0 auto;
border: 1px solid #000;
}
#startButton, #restartButton {
display: block;
margin: 10px auto;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
#restartButton {
display: none; /* 처음에는 숨김 처리 */
}
</style>
</head>
<body>
<canvas id="myCanvas" width="480" height="320"></canvas>
<button id="startButton">게임 시작</button>
<button id="restartButton">다시 시작</button>
<script src="game.js"></script>
</body>
</html>
2. 게임 시작 기능 구현
다음으로 game.js 에서 게임시작 기능을 구현합니다.
게임시작 버튼을 클릭하면 캔버스 화면 중앙에 3, 2, 1 숫자 카운트가 뜬 후 게임이 시작되도록 해보겠습니다.
이를 위해서 기존에 변수를 초기화했던 부분을 정리해서 init()로 메서드화 해주었습니다.
// 사용하는 변수 정리
let x, y, dx, dy, ballRadius, paddleHeight, paddleWidth, paddleX;
let brickRowCount, brickColumnCount, brickWidth, brickHeight, brickPadding, brickOffsetTop, brickOffsetLeft;
let bricks, gameInterval, gameStarted = false;
// 게임 시작 버튼과 다시 시작 버튼 요소 가져오기
const startButton = document.getElementById("startButton");
const restartButton = document.getElementById("restartButton");
// 게임 초기화 함수
function init() {
// 공, 패들, 벽돌 등 게임 상태 초기화
x = canvas.width / 2;
y = canvas.height - 30;
dx = 2;
dy = -2;
ballRadius = 10;
paddleHeight = 10;
paddleWidth = 75;
paddleX = (canvas.width - paddleWidth) / 2;
brickRowCount = 3;
brickColumnCount = 5;
brickWidth = 75;
brickHeight = 20;
brickPadding = 10;
brickOffsetTop = 30;
brickOffsetLeft = 30;
bricks = [];
for(let c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for(let r = 0; r < brickRowCount; r++) {
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
gameStarted = false; // 게임 시작 상태 초기화
}
// 게임 시작 함수
function startGame() {
startButton.style.display = "none"; // 게임 시작 버튼 숨기기
restartButton.style.display = "none"; // 다시 시작 버튼 숨기기
let countdown = 3; // 3초 카운트다운
gameInterval = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = "24px Arial";
ctx.fillStyle = "#0095DD";
ctx.textAlign = "center";
ctx.fillText(countdown, canvas.width / 2, canvas.height / 2);
countdown--;
if(countdown < 0) {
clearInterval(gameInterval);
gameInterval = setInterval(draw, 10); // 게임 시작
}
}, 1000);
}
// 게임 시작 버튼 클릭 이벤트 리스너
startButton.addEventListener("click", () => {
init();
startGame();
});
3. 게임 오버 기능 구현
다음으로 공이 바닥에 닿았을 때 게임이 종료되면서, 게임 오버 메시지가 출력되도록 하겠습니다.
이를 위해 gameOver() 함수를 추가하고, 공이 바닥에 닿을 때 이 함수를 호출합니다.
// 게임 오버 함수
function gameOver() {
clearInterval(gameInterval); // 게임 루프 종료
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = "24px Arial";
ctx.fillStyle = "#FF0000"; // 빨간색으로 "Game Over" 표시
ctx.textAlign = "center";
ctx.fillText("Game Over", canvas.width / 2, canvas.height / 2 - 20);
restartButton.style.display = "block"; // "다시 시작" 버튼 표시
}
// draw 함수에 게임 오버 처리 추가
function draw() {
//...(생략)...
if(y + dy > canvas.height - ballRadius) {
gameOver(); // 공이 바닥에 닿으면 게임 오버
return;
}
//...(생략)...
}
4. 게임 클리어 처리
모든 벽돌을 깨뜨리면 게임이 클리어되도록 하고, "Game Clear" 메시지를 표시하겠습니다. 이를 위해 벽돌이 모두 사라졌는지를 확인하는 checkBricks() 함수를 사용합니다.
// 벽돌이 모두 사라졌는지 확인하는 함수
function checkBricks() {
for(let c = 0; c < brickColumnCount; c++) {
for(let r = 0; r < brickRowCount; r++) {
if(bricks[c][r].status == 1) {
return false; // 벽돌이 남아 있으면 false 반환
}
}
}
return true; // 모든 벽돌이 사라졌으면 true 반환
}
// 게임 클리어 함수
function gameClear() {
clearInterval(gameInterval); // 게임 루프 종료
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = "24px Arial";
ctx.fillStyle = "#0095DD"; // 파란색으로 "Game Clear" 표시
ctx.textAlign = "center";
ctx.fillText("Game Clear!", canvas.width / 2, canvas.height / 2);
restartButton.style.display = "block"; // "다시 시작" 버튼 표시
}
// draw 함수에 게임 클리어 처리 추가
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
drawBall();
drawPaddle();
collisionDetection();
if(checkBricks()) {
gameClear(); // 모든 벽돌이 깨지면 게임 클리어
return;
}
if(x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if(y + dy > canvas.height - ballRadius) {
gameOver(); // 공이 바닥에 닿으면 게임 오버
return;
}
if(y + dy < ballRadius || (y + dy > canvas.height - paddleHeight - ballRadius && x > paddleX && x < paddleX + paddleWidth)) {
dy = -dy;
}
x += dx;
y += dy;
requestAnimationFrame(draw);
}
5. 다시 시작 버튼 구현
마지막으로 게임 오버 또는 게임 클리어 후 "다시 시작" 버튼을 눌러서 게임을 초기화하고 다시 시작할 수 있도록 구현합니다.
// 다시 시작 버튼 클릭 이벤트 리스너
restartButton.addEventListener("click", () => {
init(); // 게임 상태 초기화
startGame(); // 게임 시작
});
여기까지 진행하면 벽돌깨기 게임이 완성됩니다.
6. 전체 코드
index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>벽돌 깨기 게임</title>
<style>
canvas {
background: #eee;
display: block;
margin: 0 auto;
border: 1px solid #000;
}
#startButton, #restartButton {
display: block;
margin: 10px auto;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
#restartButton {
display: none; /* 처음에는 숨김 처리 */
}
</style>
</head>
<body>
<canvas id="myCanvas" width="480" height="320"></canvas>
<button id="startButton">게임 시작</button>
<button id="restartButton">다시 시작</button>
<script src="game.js"></script>
</body>
</html>
game.js
// 캔버스와 2D 컨텍스트 가져오기
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
let x, y, dx, dy, ballRadius, paddleHeight, paddleWidth, paddleX;
let brickRowCount, brickColumnCount, brickWidth, brickHeight, brickPadding, brickOffsetTop, brickOffsetLeft;
let bricks, gameInterval, gameStarted = false;
const startButton = document.getElementById("startButton");
const restartButton = document.getElementById("restartButton");
function init() {
// 공의 위치와 이동 속도 설정
x = canvas.width / 2;
y = canvas.height - 30;
dx = 2; // 공의 x축 이동 속도
dy = -2; // 공의 y축 이동 속도
ballRadius = 10;
// 패들 설정
paddleHeight = 10;
paddleWidth = 75;
paddleX = (canvas.width - paddleWidth) / 2;
// 벽돌 설정
brickRowCount = 3;
brickColumnCount = 5;
brickWidth = 75;
brickHeight = 20;
brickPadding = 10;
brickOffsetTop = 30;
brickOffsetLeft = 30;
bricks = [];
for(let c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for(let r = 0; r < brickRowCount; r++) {
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
gameStarted = false; // 게임이 아직 시작되지 않음
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function drawBricks() {
for(let c = 0; c < brickColumnCount; c++) {
for(let r = 0; r < brickRowCount; r++) {
if(bricks[c][r].status == 1) {
let brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
let brickY = (r * (brickHeight + brickPadding)) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
}
}
}
function collisionDetection() {
for(let c = 0; c < brickColumnCount; c++) {
for(let r = 0; r < brickRowCount; r++) {
let b = bricks[c][r];
if(b.status == 1) {
if(x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
dy = -dy;
b.status = 0;
}
}
}
}
}
function mouseMoveHandler(e) {
let relativeX = e.clientX - canvas.offsetLeft;
if(relativeX > 0 && relativeX < canvas.width) {
paddleX = relativeX - paddleWidth / 2;
}
}
document.addEventListener("mousemove", mouseMoveHandler, false);
function checkBricks() {
for(let c = 0; c < brickColumnCount; c++) {
for(let r = 0; r < brickRowCount; r++) {
if(bricks[c][r].status == 1) {
return false;
}
}
}
return true;
}
function gameClear() {
clearInterval(gameInterval);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = "24px Arial";
ctx.fillStyle = "#0095DD";
ctx.textAlign = "center";
ctx.fillText("Game Clear!", canvas.width / 2, canvas.height / 2);
restartButton.style.display = "block";
}
function gameOver() {
clearInterval(gameInterval);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = "24px Arial";
ctx.fillStyle = "#FF0000";
ctx.textAlign = "center";
ctx.fillText("Game Over", canvas.width / 2, canvas.height / 2 - 20);
restartButton.style.display = "block";
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
drawBall();
drawPaddle();
collisionDetection();
if(checkBricks()) {
gameClear();
return;
}
if(x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if(y + dy > canvas.height - ballRadius) {
gameOver();
return;
}
if(y + dy < ballRadius || (y + dy > canvas.height - paddleHeight - ballRadius && x > paddleX && x < paddleX + paddleWidth)) {
dy = -dy;
}
x += dx;
y += dy;
}
function startGame() {
startButton.style.display = "none";
restartButton.style.display = "none";
let countdown = 3;
gameInterval = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = "24px Arial";
ctx.fillStyle = "#0095DD";
ctx.textAlign = "center";
ctx.fillText(countdown, canvas.width / 2, canvas.height / 2);
countdown--;
if(countdown < 0) {
clearInterval(gameInterval);
gameInterval = setInterval(draw, 10); // 게임 시작
}
}, 1000);
}
startButton.addEventListener("click", () => {
init();
startGame();
});
restartButton.addEventListener("click", () => {
init();
startGame();
});
'JavaScript' 카테고리의 다른 글
[JavaScript] 벽돌깨기 게임 발전시키기 - 목숨 기능 추가하기 (0) | 2024.08.28 |
---|---|
[JavaScript] 간단한 테트리스 게임 만들기 (0) | 2024.08.24 |
[JavaScript] 벽돌깨기 게임 만들기 (2) 공 튕기기 (0) | 2024.08.18 |
[JavaScript] 사칙연산 계산기 만들기 (0) | 2024.08.16 |
[JavaScript] 벽돌깨기 게임 만들기 (1) <canvas> 에 도형 그리기 (0) | 2024.08.12 |