[Udemy]20 Web Projects With Vanilla JavaScript -Project#17

HTML&CSS&Javascript · 2021. 1. 1. 18:23

유데미에서 Brad Traversy의 20 Web Projects With Vanilla JavaScript

를 하나씩 직접 코딩해보면서 정리하기 위한 글이다.

 

 

데모 페이지

vanillawebprojects.com/projects/breakout-game/

코드 링크

github.com/rshak8912/20-Web-Projects



 

 

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="style.css" />
    <title>Breakout!</title>
</head>
<body>
    <h1>벽돌깨기!</h1>
    <button id="rules-btn" class="btn rules-btn">Show Rules</button>
    <div id="rules" class="rules">
        <h2>How To Play:</h2>
        <p>
            Use your right and left keys to move the paddle to bounce the ball up and break the blocks.
        </p>
        <p>If you miss the ball, your score and the blocks will reset.</p>
        <button id="close-btn" class="btn">Close</button>
    </div>
    <canvas id="canvas" width="800" height="600"></canvas>
    <script src="script.js"></script>
</body>
</html>

style.css

* {
    box-sizing: border-box;
}

body {
    background-color: #0095dd;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-family: Arial, Helvetica, sans-serif;
    min-height: 100vh;
    margin: 0;

}

h1 {
    font-size: 45px;
    color: #fff;

}

canvas {
    background: #f0f0f0;
    display: block;
    border-radius: 5px;
}

.btn {
    cursor: pointer;
    border: 0;
    padding: 10px 20px;
    background: #000;
    color: #fff;
    border-radius: 5px;
}

.btn:focus {
    outline: 0;
}

.btn:hover {
    background: #222;
}

.btn:active {
    transform: scale(0.98);
}

.rules-btn {
    position: absolute;
    top: 30px;
    left: 30px;
}

.rules {
    position: absolute;
    top: 0;
    left: 0;
    background: #333;
    color: #fff;
    min-height: 100vh;
    width: 400px;
    padding: 20px;
    line-height: 1.5;
    transform: translateX(-400px);
    transition: transform 1s ease-in-out;
}

.rules.show {
    transform: translateX(0);
}

script.js

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');

let score = 0;

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

function drawBall() {
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.size, 0, Math.PI * 2);
    ctx.fillStyle = ball.visible ? '#0095dd' : 'transparent';
    ctx.fill();
    ctx.closePath();
}

function drawPaddle() {
    ctx.beginPath();
    ctx.rect(paddle.x, paddle.y, paddle.w, paddle.h);
    ctx.fillStyle = paddle.visible ? '#0095dd' : 'transparent';
    ctx.fill();
    ctx.closePath();
}

function drawScore() {
    ctx.font = '20px Arial';
    ctx.fillText(`Score: ${score}`, canvas.width - 100, 30);
}

function drawBricks() {
    bricks.forEach(column => {
        column.forEach(brick => {
            ctx.beginPath();
            ctx.rect(brick.x, brick.y, brick.w, brick.h);
            ctx.fillStyle = brick.visible ? '#0095dd' : 'transparent';
            ctx.fill();
            ctx.closePath();
        });
    });
}

function movePaddle() {
    paddle.x += paddle.dx;

    // Wall detection
    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 = -ball.speed;
    }

    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) {
        showAllBricks();
        score = 0;
    }
}

function increaseScore() {
    score++;

    if (score % (brickRowCount * brickColumnCount) === 0) {

        ball.visible = false;
        paddle.visible = false;

        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 draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    drawBall();
    drawPaddle();
    drawScore();
    drawBricks();
}

function update() {
    movePaddle();
    moveBall();

    draw();

    requestAnimationFrame(update);
}

update();

const keyDown = (e) => {
    if (e.key === 'Right' || e.key === 'ArrowRight') {
        paddle.dx = paddle.speed;
    } else if (e.key === 'Left' || e.key === 'ArrowLeft') {
        paddle.dx = -paddle.speed;
    }
}

const keyUp = (e) => {
    if (
        e.key === 'Right' ||
        e.key === 'ArrowRight' ||
        e.key === 'Left' ||
        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'));

 


강의를 통해 배운 점(생각, 내용 정리)

  • 어렸을 때 하던 벽돌깨기 게임을 직접 만들어 볼 수 있어서 의미 있는 시간이었다.
  • keyup, keydown 이벤트의 사용