[javascript] 벽돌깨기 게임 리뷰

토닉·2021년 3월 21일
0

게임을 만들게 된 동기

wecode 부트캠프 시작 전 사전스터디에서 2주차 과제로 javascript에 대해서 공부하라고 하셨습니다. 백엔드로 마음을 정한 사람들은 바로 python을 해도 된다고 하셨지만 아예 모르는 것보단 한번은 경험해보는게 나중에 웹에 대해 이해하기 편할것 같았습니다. 생활코딩, 드림코딩 by 엘리 등등 정말 좋은 강의들이 많았지만 저는 무언가를 바로 만들고 싶어 mozila에 잇는 javascript로 벽돌깨기 게임만들기를 하기로 결정했습니다.

https://developer.mozilla.org/ko/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript

튜토리얼의 10단계 과정을 통해 결과물이 나왔을 때 뿌듯했습니다. 튜토리얼의 설명은 정말 쉽게 되어있어서 javascript가 처음인 저도 금방 따라할 수 있었습니다. 단계마다 어렵지 않은 과제가 하나씩 있어 혼자서 충분히 응용도 할 수 있었습니다. 10단계 과정속에서 제가 배운 문법과 유용한 팁들을 적어보았습니다.

게임 소개

떨어지는 공을 사용자가 조작할 수 있는 패들로 튕겨서 위에 있는 벽돌을 모두 부시면 되는 게임입니다.

목표설정

  1. 배경,벽돌, 공, 패들 그리기(canvas를 이용하여)
  2. 남은 벽돌 갯수, 사용자의 생명 text로 입력
  3. 공의 움직임 설정
  4. 패들 조작
  5. 벽돌에 공이 닿으면 부서짐

추가 설정

  • 벽돌의 무작위 생성
  • 패들에 공이 닿는 면적에 따라 공의 방향, 속도 변화

1. 배경, 벽돌, 공, 패들 그리기

우선 html 파일에 원하는 위치에 캔버스 태그를 넣어주고 크기를 지정해줍니다.(그림 그리기 전 도화지를 준비한다고 생각하면 됩니다)

<body>
	<canvas id="myCanvas" width="600" height="400"></canvas>
    <script>
    	var canvas = document.getElementById("myCanvas");
	var ctx = canvas.getContext("2d");
    	// javascript 코드 입력
    </script>
</body>

canvas를 참조하는 변수(var canvas)와 canvas에 그림을 그릴 수 있게 해주는 2D rendering context를 변수(var ctx)에 저장해줍니다.

도화지는 준비가 끝났으니 이제 그림을 그려야겠죠?
모든 그림은 beginPath()와 closePath() 메소드 사이에 입력하면 됩니다.

 // 공 그리기 //
var x = canvas.width/2;
var y = canvas.height-30;
var dx = 2;
var dy = -2;
var ballRadius = 10;
var color = "#0095DD";
function drawBall(color) {
    ctx.beginPath();
    ctx.arc(x, y, ballRadius, 0, Math.PI*2); //(x좌표,y좌표,원 반지름, 시작각도, 끝각도, 그리는 방향)
    ctx.fillStyle = color;
    ctx.fill();
    ctx.closePath();
}
// 패들 그리기 //
var paddleHeight = 10;
var paddleWidth = 80;
var paddleX = (canvas.width-paddleWidth)/2;
function drawPaddle() { 
    ctx.beginPath();
    ctx.rect(paddleX, canvas.height-paddleHeight, paddleWidth, paddleWidth); // (from x, from y, to x, to y 좌표)
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
}
// 배경 그리기 //
function drawBackground() {
    ctx.fillStyle = '#CA9B89';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
}

원을 그리고 싶다면 arc , 사각형을 그리고 싶다면 rect 메소드를 사용한 후 fillStyle에 색깔을 정하고 fill로 그림을 그리면 됩니다. 위 코드만 작성한다면 동적인 움직임을 표현하지 못해요.

canvas의 x,y 좌표

  • 캔버스에 가장 왼쪽, 위쪽 꼭짓점의 좌표는 (0,0)이다.
  • 사각형을 그릴 때 시작점도 왼쪽, 위쪽을 기준으로 시작한다.
  • 원의 좌표는 원의 중심이다.

벽돌은 객체로 생성하여 x좌표, y좌표 그리고 벽돌마다 여러번 부숴야 없어지는 형태로 목숨(status)을 0~2 까지 만들었어요.

// 벽돌 객체 생성
var bricks = [];
for(var c=0; c<brickColumnCount; c++) {
    bricks[c] = [];
    for(var r=0; r<brickRowCount; r++) {
        bricks[c][r] = { x: 0, y: 0, status: Math.floor(Math.random() * 3) };
        totalBricks += bricks[c][r].status;
    }
}
// 벽돌 그리기
function drawBricks() {
    for(var c=0; c<brickColumnCount; c++) {
        for(var r=0; r<brickRowCount; r++) {
            if(bricks[c][r].status >= 1) { // 벽돌의 목숨이 1 이상일 때
                var brickX = (c*(brickWidth+brickPadding))+brickOffsetLeft;
                var brickY = (r*(brickHeight+brickPadding))+brickOffsetTop;
                bricks[c][r].x = brickX;
                bricks[c][r].y = brickY;
                ctx.beginPath();
                ctx.rect(brickX, brickY, brickWidth, brickHeight);
                if(bricks[c][r].status == 2) { // 벽돌의 목숨이 2일 때
                    ctx.fillStyle = "black";
                }else{ // 벽돌의 목숨이 1일 때
                    ctx.fillStyle = "gray";
                }               
                ctx.fill();
                ctx.closePath();
            }
        }
    }
}

* 객체란?
키와 값으로 구성된 프로퍼티의 집합
프로퍼티: {key1:value1}

var car = {name:"Ferrari", color:"red", wheels:4};
// 너 차 무슨색이야?
console.log(car.color);
// 빨간색이야
red

프로퍼티의 값에는 함수도 넣을 수 있고 이 함수를 메소드라고 부릅니다. 제한되어 있는 함수)

var car = {
	name:"Ferrari", 
    	color:"red", 
    	wheels:4,
        run: function() {
        	console.log("brrrrrr");
        }
        };
// 시동걸어 볼게!
car.run();
// brrrrrr

빈 객체를 먼저 생성한 후 프로퍼티를 추가 할 수 있습니다.

var car = new Object();
car.name = "Maserati";
car.color = "black";
car.run = function() {console.log("booooom");};
// 시동걸어 볼게!
car.run();
// booooom

객체는 이렇게 생성할 수 있지만 많은 개체를 생성하려면 위와 같은 방법은 불편할 수 있습니다. 객체를 생성하는 함수를 만들면 보다 쉽게 여러개의 객체를 생성할 수 있습니다. 이것을 생성자 함수라고 합니다.

function Car(name,color){ // 첫 글자는 대문자
  this.name = name;
  this.color = color;
  this.run = function() {
    console.log("booooom");
  };
}

var myCar = new Car("Ferrari", "red");
var yourCar = new Car("Maserati", "black");

// 내차는 페라리야
console.log(myCar.name);
// 너의 차는 무슨 차야?
console.log(yourCar.name);

2. 남은 벽돌 갯수, 사용자의 생명 text로 입력

text는 도형을 넣는 것과는 달리 fillText 라는 메소드로 넣을 수 있어요. 폰트, 글씨 색, 넣을 위치만 입력해주면 됩니다.

function drawLeftoverBricks() {
    ctx.font = "16px Arial"; // 폰트 크기, 종류
    ctx.fillStyle = "#0095DD"; // 글씨 색
    ctx.fillText("남은 갯수: "+totalBricks, 8, 20);  // text, x 좌표, y 좌표  
}

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

3. 공의 움직임 설정

clearRect와 requestAnimationFrame() 을 사용하여 구현할 수 있어요. draw라는 함수를 호출할 때마다 지우고 다시 그리는 과정을 반복할거에요. clearRect는 지우개, requestAnimationFrame()는 반복적으로 함수를 실행하게 해줍니다.

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height); //(from x, from y,to x, to y)
    drawBackground() 
    drawBricks();
    drawBall(color);
    drawPaddle();
    drawLeftoverBricks();
    drawLives();
    collisionDetection();
    
    // 움직임을 구현할 코드 //
    
    requestAnimationFrame(draw);           
}

공의 동적 움직임 구현

dx, dy 변수를 만들어 공의 좌표를 바꿔줍니다. draw()함수가 다시 그릴 때마다 공이 좌표를 이동하여 공이 움직이는 것처럼 구현합니다.

var dx = 2;
var dy = -2;
function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height); //(from x, from y,to x, to y)
    drawBackground() 
    drawBricks();
    drawBall(color);
    drawPaddle();
    drawLeftoverBricks();
    drawLives();
    collisionDetection();
    
    /////////////////////////
    // 공이 캔버스 밖으로 나갈 때 조건 //
    // 공이 패들에 닿을 때 튕겨지는 조건 //
    // 공이 바닥에 떨어질 때 게임이 종료되는 조건 //
    // 패들 조작 //
    x += dx;
    y += dy;
    
   /////////////////////////
   
    requestAnimationFrame(draw);           
}

/// 사이에 있는 3가지 조건을 담은 코드입니다.

// 공의 좌표가 캔버스의 크기를 벗어날 때
if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
        dx = -dx;
        color = colors[Math.floor(Math.random() * 4)];
    }
    if(y + dy < ballRadius) {
        dy = -dy;
    } else if(y + dy > canvas.height-ballRadius-paddleHeight+5) {
    	// 공이 패들에 닿았을 때
        if(x > paddleX && x < paddleX + paddleWidth) {
            if(x < paddleX + 20){
                if (dx  > 0) {
                    dx = -dx;
                }
                dx = -4;
            }else if(x < paddleX + 40){
                if (dx  > 0) {
                    dx = -dx;
                }
                dx = -2;
            }
            else if(x < paddleX + 60){
                if (dx  < 0) {
                    dx = -dx;
                }
                dx = 2;
            }else{
                if (dx  < 0) {
                    dx = -dx;
                }
                dx = 4;
            }   
            dy = -dy;
        }
        else { // 게임종료 조건
            if(!lives) {
                alert("GAME OVER");
                document.location.reload();
                clearInterval(interval);
            }
            else {
                lives --;
                x = canvas.width/2;
                y = canvas.height-30;
                dx = 2;
                dy = -2;
                paddleX = (canvas.width-paddleWidth)/2;
            }
        }
    }

4. 패들 조작

addEventListener를 사용하여 키를 눌렀을 때 이벤트가 발생하도록 합니다.

var rightPressed = false;
var leftPressed = false;
// keydown 일 때 keyDownHandler 를 실행
document.addEventListener("keydown", keyDownHandler, false);
// keyup 일 때 keyUpHandler 를 실행
document.addEventListener("keyup", keyUpHandler, false);

// e.keyCode == 39 는 오른쪽 방향키, 37은 왼쪽 방향키
function keyDownHandler(e) {
    if(e.keyCode == 39) {
        rightPressed = true;
    }
    else if(e.keyCode == 37) {
        leftPressed = true;
    }
}

function keyUpHandler(e) {
    if(e.keyCode == 39) {
        rightPressed = false;
    }
    else if(e.keyCode == 37) {
        leftPressed = false;
    }
}


function draw(){
    ctx.clearRect(0, 0, canvas.width, canvas.height); // 지울 부분(from x, from y,to x, to y)
    drawBackground() 
    drawBricks();
    drawBall(color);
    drawPaddle();
    drawLeftoverBricks();
    drawLives();
    collisionDetection();
    
    // 조건 코드 //
    
    ///////////////
    // 키보드 조작
    if(rightPressed && paddleX < canvas.width-paddleWidth) {
        paddleX += 7;
    }
    else if(leftPressed && paddleX > 0) {
        paddleX -= 7;
    }
    ///////////////
    x += dx;
    y += dy; 
    requestAnimationFrame(draw);    
}

5. 공이 닿으면 없어지는 벽돌 생성

공의 좌표가 벽돌 영역에 들어왔을 때 벽돌의 state, 남은 벽돌 수(totalBricks)를 1씩 줄이고 totalBricks가 0 일 때 게임이 종료되는 문구가 나오게 했습니다.

function collisionDetection() {
    for(var c=0; c<brickColumnCount; c++) {
        for(var r=0; r<brickRowCount; r++) {
            var 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 -= 1;
                    totalBricks--;
                    if (totalBricks == 0) {
                        alert("축하드립니다. 통과하셨어요!!");
                        document.location.reload();
                        clearInterval(interval);
                    }
                }
            }
        }
    }
}

소스 코드
벽돌깨기 게임

profile
우아한테크코스 4기 교육생

0개의 댓글