본문 바로가기
JavaScript

[JavaScript] 벽돌깨기 게임 발전시키기 - 목숨 기능 추가하기

by teamnova 2024. 8. 28.
728x90

안녕하세요.

오늘은 이전에 만들었던 벽돌깨기 게임 예제를 더 발전시키기 위해서 목숨 기능을 추가해보겠습니다.

목숨은 플레이어가 실수로 공을 놓쳤을 때 줄어들며, 목숨이 0이 되면 게임이 종료됩니다.

 

 

 

기존의 코드는 아래 링크를 통해 확인해주세요.

2024.08.12 - [JavaScript] - [JavaScript] 벽돌깨기 게임 만들기 (3) 게임 시작, 게임 오버, 게임 클리어, 다시 시작 구현하기

 

[JavaScript] 벽돌깨기 게임 만들기 (3) 게임 시작, 게임 오버, 게임 클리어, 다시 시작 구현하기

ㅇ안녕하세요.오늘은 벽돌깨기 게임 만들기 마지막 시간입니다. 이전 시간에 canvas에 원하는 도형을 그리고, 각 도형이 서로 닿았을 때 원하는 대로 동작하도록 처리하는 방법에 대해서 배웠습

stickode.tistory.com

1.  목숨 변수 선언

먼저, 목숨을 저장할 변수를 추가합니다.

let lives;

 

이 변수를 init 함수에 추가하여, 게임을 시작할 때마다 목숨을 초기화합니다.

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 };
        }
    }

    lives = 3; // 목숨을 초기화합니다.
    gameStarted = false;
}

2. 남은 목숨을 화면에 표시하기

게임 도중 플레이어에게 남은 목숨의 수를 화면에 표시하여 얼마나 많은 기회를 남겨두고 있는지 알 수 있도록 하는 drawLives 함수를 만듭니다.

function drawLives() {
    ctx.font = "16px Arial";
    ctx.fillStyle = "#0095DD";
    ctx.fillText("Lives: " + lives, canvas.width - 65, 20);
}

 

이제 이 함수를 게임 루프(draw 함수)에서 호출하여, 매 프레임마다 남은 목숨을 표시하도록 합니다.

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawBricks();
    drawBall();
    drawPaddle();
    drawLives(); // 남은 목숨을 화면에 표시
    collisionDetection();

// (생략)
}

 

 

3. 목숨이 0이 되었을 때 게임 종료

draw 함수에서 공이 바닥에 닿을 때 목숨을 하나 줄이고, 목숨이 0이 되면 gameOver 함수를 호출하여 게임을 종료하도록 코드를 수정합니다.

 

// 기존 코드
if(y + dy > canvas.height - ballRadius) {
        gameOver();
        return;
    }


// 수정한 코드
if(y + dy > canvas.height - ballRadius) {
    lives--; // 목숨을 하나 줄입니다.

    if(!lives) {
        gameOver(); // 목숨이 0이면 게임 오버 처리
        return;
    } else {
        // 목숨이 남아 있으면 게임을 계속할 수 있도록 위치를 초기화합니다.
        x = canvas.width / 2;
        y = canvas.height - 30;
        dx = 2;
        dy = -2;
        paddleX = (canvas.width - paddleWidth) / 2;
    }
}

 

 

4. 전체 코드

// 캔버스와 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");

// 추가된 변수들
let lives;

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 };
        }
    }
    lives = 3; // 플레이어에게 3개의 목숨을 부여
    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 drawLives() {
    ctx.font = "16px Arial";
    ctx.fillStyle = "#0095DD";
    ctx.fillText("Lives: " + lives, canvas.width - 65, 20);
}

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawBricks();
    drawBall();
    drawPaddle();
    drawLives(); // 남은 목숨을 화면에 표시
    collisionDetection();

    if(checkBricks()) {
        gameClear();
        return;
    }

    if(x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
        dx = -dx;
    }

    if(y + dy > canvas.height - ballRadius) {
        lives--; // 목숨을 하나 줄입니다.
    
        if(!lives) {
            gameOver(); // 목숨이 0이면 게임 오버 처리
            return;
        } else {
            // 목숨이 남아 있으면 게임을 계속할 수 있도록 위치를 초기화합니다.
            x = canvas.width / 2;
            y = canvas.height - 30;
            dx = 2;
            dy = -2;
            paddleX = (canvas.width - paddleWidth) / 2;
        }
    }

    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();
});