🔨 벽돌깨기 게임(Breakout)
- 마우스로 패들 조작
- 공이 벽돌과 충돌하면 점수 획득
- 공이 패들에 닿지 않고 떨어지면 게임 종료
- 게임 가능 횟수 설정
기존의 HTML은 매우 정적인 느낌이라면, canvas는 이러한 정적인 느낌을 동적으로 느끼게 해줄 수 있는 HTML5의 커다란 기능 중 하나이다.자바스크립트를 통해 그림을 그리고 애니메이션과 이벤트 처리 등을 구현할 수 있다.
canvas 참고 사항
html에 canvas를 생성한다.
<canvas id="drawing" width="480" height="720"></canvas>
css에 canvas 스타일을 설정한다.
js로 공을 그려준다.
<canvas>
요소는 getContext()
메서드를 이용해서, 랜더링 컨텍스트와 (렌더링 컨텍스트의) 그리기 함수들을 사용할 수 있다. 2D 그래픽의 경우, CanvasRenderingContext2D
을 얻기위해 "2d"로 지정한다.
beginPath()
와 closePath()
메소드 사이에 모든 명령어가 들어간다. fillStyle
은 fill()
메소드에서 칠해질 색상 값을 갖게 된다. arc()
메소드로 원을 그려줄 수 있다. 파라미터는 총 6개로 원 중심을 가리키는 x좌표, y좌표, 반지름, 시작 각도, 끝 각도, 시계방향(false)/반시계방향(true)으로 그리기이다. 1라디안 은 180/Math.PI이므로 360도는 Math.PI *2이다.
const ctx = canvas.getContext("2d");
ctx.beginPath();
//x,y, 반지름,시작,끝 각도
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
ctx.closePath();
공을 연속적으로 다음 공간에 그려내고 흔적을 지우면 마치 영화의 프레임처럼 움직이는 공을 만들 수 있다.
주기마다 무한히 작동하는 setInterval
함수를 이용하여 공을 연속적으로 그려낼 수 있다. 공을 중간에 그리고 싶다면 x좌표를 cansvas.width/2로 설정한다. y좌표는 canvas 가장 상단의 위치가 0이고 canvas 가장 하단의 위치가 canvas.height-ballRadius인 것을 기준으로 설정한다. 가장 하단에서 ballRadius를 빼준 이유는 y좌표는 원의 중심이므로 원의 반지름의 값을 빼주어야 완전한 원을 볼 수 있기 때문이다.
setInterval함수가 공을 그리는 함수를 호출할 때마다 원의 중심을 가리키는 x좌표와 y좌표의 값을 변경하여 다음 공이 그려질 위치를 바꾸어 준다. 변경될 x좌표와 y좌표의 값을 dx, dy로 선언하여 이동할 위치의 값을 할당한다. dx, dy의 크기로 속도를 조절하고
canvas의 내용들을 지워주기 위한 메소드인 clearRect()
을 활용하여 이전 위치에 그려진 공을 삭제한다. 메소드의 파라미터로 사각형의 좌표를 표현하고 만들어진 사각형 안의 canvas내용들을 지워준다. 파라미터는 직사각형의 좌상단 모서리를 표시할 x, y좌표와 직사각형의 우하단 모서리를 표시할 x, y좌표 총 4개가 필요하다.
//canvas 전체 내용을 지운다.
ctx.clearRect(0,0,cavas.width,canvas.height)
상하좌우로 벽에 닿은 공은 튕겨져 나간다. 이것을 구현하기 위해 앞서 공을 무한히 그려내며 고민했던 공의 위치값을 다시 생각해본다. 4개의 벽에 닿으면 본래 이동하면 반대 방향으로 튕겨져 나갈 것인데 왼쪽과 오른쪽 벽에 부딪히면 x좌표의 이동 방향이 달라질 것이고 위쪽과 아래쪽 벽에 부딪히면 y좌표의 이동 방향이 달라지게 된다. 벽에 닿는다는 조건은 원의 반지름 값을 활용하여 설정한다. x+dx와 y+dy가 원의 반지름의 값을 가지면 가장 왼쪽의 벽과 가장 위쪽의 벽에 위치하게 된다.(0이 아닌 이유는 x와 y가 원의 중심 좌표를 가리키기 때문에 원의 반지름이 잘리게 된다.) 마찬가지로 x+dx와 y+dy가 canvas.width-ballRadius, canvas.height-ballRadius 값을 가지면 가장 오른쪽의 벽과 가장 아래쪽의 벽에 위치하게 된다.
collision이라는 변수를 할당하여 벽에 닿으면 1이 되고 collsion이 1이면 원의 색깔이 랜덤하게 변한다. 원의 색깔을 칠해주면 collision은 다시 0이 된다. 랜덤한 색상을 뽑아내는 것은 Math.random 함수를 이용한다. Math random()은 초기 범위 0~1사이에서 부동소수점의 난수를 생성한다. 최대 범위를 조절하려면 곱하기 기호(*
)를 이용해 최대값을 설정해줄 수 있다. rgb의 값은 0 to 255이므로 rgb 각각의 값에 255를 곱해 0~255사이에서 무작위로 할당해주면 공이 벽에 닿아 튕겨져 나갈 때마다 공의 색깔을 랜덤하게 바꿔줄 수 있다.
GAME OVER라는 알림창을 띄우고 공을 계속 그려주는 함수를 호출하는 타이머를 제거하여 게임을 종료한다. Location.reload() 메서드는 새로고침 버튼처럼 현재 리소스를 다시 불러온다.
가장 바닥이 아닌 패들에 닿으면 공이 튕겨져 나온다. 이 때 공의 위치의 x좌표, y좌표의 범위를 설정한다. x좌표는 paddle의 넓이, y좌표는 canvas.height에서 paddleHeight와 원의 반지름을 뺀 값이다.
const canvas = document.getElementById("drawing");
const ctx = canvas.getContext("2d");
const ballRadius = 10;
const paddleHeight = 12;
const paddleWidth = 120;
class DrawObject {
constructor() {
this.ballX = canvas.width / 2;
this.ballY = canvas.height - paddleHeight - ballRadius / 2;
this.paddleX = canvas.width / 2 - paddleWidth / 2;
}
DrawBall() {
ctx.beginPath();
ctx.arc(this.ballX, this.ballY, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#ff7675";
ctx.fill();
ctx.closePath();
}
DrawPaddle() {
ctx.beginPath();
ctx.rect(
this.paddleX,
canvas.height - paddleHeight,
paddleWidth,
paddleHeight
);
ctx.fillStyle = "#2d3436";
ctx.fill();
ctx.closePath();
}
}
class DrawCanvas {
constructor(drawObject, controlPaddle) {
this.dx = 4;
this.dy = -4;
this.drawObject = drawObject;
this.controlPaddle = controlPaddle;
}
init() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
BounceBall() {
this.init();
//좌우벽
if (
this.drawObject.ballX + this.dx < ballRadius ||
this.drawObject.ballX + this.dx > canvas.width - ballRadius
) {
this.dx = -this.dx;
}
//상
if (this.drawObject.ballY + this.dy < ballRadius) {
this.dy = -this.dy;
}
//하 - 바닥
else if (this.drawObject.ballY + this.dy > canvas.height - ballRadius) {
alert("GAME OVER");
document.location.reload();
clearInterval(timer);
}
//하 - 패들
else if (
this.drawObject.ballY + this.dy >
canvas.height - paddleHeight - this.dy &&
this.drawObject.ballY + this.dy < canvas.height - paddleHeight + this.dy
) {
if (
this.drawObject.ballX > this.drawObject.paddleX &&
this.drawObject.ballX < this.drawObject.paddleX + paddleWidth
) {
this.dy = -this.dy;
}
}
this.drawObject.ballX += this.dx;
this.drawObject.ballY += this.dy;
this.Draw();
}
Draw() {
this.drawObject.DrawBall();
this.drawObject.DrawPaddle();
}
}
const drawObject = new DrawObject();
const drawCanvas = new DrawCanvas(drawObject);
function play() {
drawCanvas.BounceBall();
}
const timer = setInterval(play, 10)
[참고 사이트]
https://curryyou.tistory.com/323
https://developer.mozilla.org/ko/docs/Games/Tutorials/2D_breakout_game_Phaser