본문 바로가기
개발 공부/canvas

Canvas을 사용하여 벽돌깨기 게임 만들기10 (개발 마무리)

by 억만장작 2021. 7. 18.

플레이어에게 생명을 부여하기

플레이어의 생명을 변수로 저장한다.

let lives = 3

 

남은 생명 수를 표시하는 drawLives()도 정의해준다.

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

이 부분은 drawScore()와 비슷하다.

 

바닥에 닿으면 바로 게임이 끝나는 방식이었지만 이제는 생명 수를 줄이고 생명이 없어지면 그 때 게임을 종료 시킬 것이다.

그렇게 하기 위해 draw()의 세줄을 수정하자

alert("GAME OVER")
document.location.reload()
clearInterval(tmp)

이 부분을 아래와 같이 바꾸자.

lives--
		if (!lives) {
			drawLives()
			alert("GAME OVER")
			document.location.reload()
			clearInterval(tmp)
		} else {
			x = canvas.width / 2
			y = canvas.height - 30
			dx = 1
			dy = -1
			paddleX = (canvas.wdth - paddleWidth) / 2
		}

게임이 종료되는 순간 lives가 0이 되어 있는 모습이 조금 더 자연스럽다고 생각하여 종료가 된다면 lives = 0을 그리고 종료하게 했다.

 

그리고 drawLives()를 draw()에 drawScore()아래에서 호출해주자

drawLives()

 

requestAnimationFrame()을 사용하여 랜더링 개선하기

requestAnimationFrame은 setInterval()로 구현한 고정된 프레임레이트보다 더 낫게 게임을 렌더링한다.

let tmp = setInterval(draw, 10)

draw()

로 대체하자

 

그리고 clearInterval(tmp)를 제거해주자

clearInterval(tmp) // 제거
이 draw() 함수는 현재 requestAnimationFrame() 루프 내에서 반복적으로 실행되고 있지만, 고정된 10ms 프레임 대신 프레임의 제어권을 브라우저에 다시 부여 합니다. 이는 프레임과 적절하게 일치하고 필요할 때만 모양을 만들 것입니다. 이것은 이전의 setInterval() 방법보다 더 효율적이고 부드럽게 애니메이션 루프를 만듭니다.

draw()의 가장 마지막에 requestAnimationFrame(draw)로 draw를 다시 호출한다.

requestAnimationFrame(draw)

최종 코드

const canvas = document.querySelector("#canvas")
const ctx = canvas.getContext("2d")

let ballRadius = 10
let x = canvas.width / 2
let y = canvas.height - 20
let dx = 1
let dy = -1
let ballSpeed = 1.5
// paddle
const paddleHeight = 10
const paddleWidth = 75
let paddleX = (canvas.width - paddleWidth) / 2
// event
let rightPressed = false
let leftPressed = false
// 벽돌
const brickRowCount = 3
const brickColumnCount = 5
const brickWidth = 75
const brickHeight = 20
const brickPadding = 10
const brickOffsetTop = 30
const brickOffsetLeft = 30

let score = 0
let lives = 3

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

function drawScore() {
	ctx.font = "16px Arilal"
	ctx.fillStyle = "#0095DD"
	ctx.fillText("Score: " + score, 8, 20)
}

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

function mouseMoveHandler(event) {
	let relativeX = event.clientX - canvas.offsetLeft
	if (relativeX > 0 && relativeX < canvas.width) {
		paddleX = relativeX - paddleWidth / 2
	}
}

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 && x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
				dy = -dy
				b.status = 0
				score++
				if (score === brickRowCount * brickColumnCount) {
					alert("YOU WIN, CONGRATURATIONS!")
					document.location.reload()
				}
			}
		}
	}
}

function drawBricks() {
	for (let c = 0; c < brickColumnCount; c++) {
		for(let r = 0; r < brickRowCount; r++) {
			if (bricks[c][r].status === 0)
				continue
			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 keyDownHandler(event) {
	if (event.keyCode === 39) {
		rightPressed = true
	}
	else if (event.keyCode === 37) {
		leftPressed = true
	}
}

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

function drawPaddle() {
	ctx.beginPath()
	ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight)
	ctx.fillStyle = "rgba(50, 20, 20, 1)"
	ctx.fill()
	ctx.closePath()
}

function drawBall() {
	ctx.beginPath()
	ctx.arc(x, y, ballRadius, 0, Math.PI * 2, false)
	ctx.fillStyle = "white"
	ctx.fill()
	ctx.strokeStyle = "gray"
	ctx.stroke()
	ctx.closePath()
}

function draw() {
	ctx.clearRect(0, 0, canvas.width, canvas.height)
	drawBricks()
	drawBall()
	drawPaddle()
	drawScore()
	drawLives()
	collisionDetection()
	// 공 체크
	if (y + dy < ballRadius) {
		dy = -dy
	} else if (y + dy > canvas.height - ballRadius) {
		lives--
		if (!lives) {
			drawLives()
			alert("GAME OVER")
			document.location.reload()
		} else {
			x = canvas.width / 2
			y = canvas.height - 30
			dx = 1
			dy = -1
			paddleX = (canvas.wdth - paddleWidth) / 2
		}
	} else if (y + dy > canvas.height - ballRadius - paddleHeight &&
				x + dx > paddleX && x + dx < paddleX + paddleWidth) {
		dy = -dy
	}
	if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
		dx = -dx
	}
	
	// 키 체크
	if (rightPressed && paddleX < canvas.width - paddleWidth) {
		paddleX += 7
	}
	if (leftPressed && paddleX > 0) {
		paddleX -= 7
	}
	// 공의 속도를 조절하는 곳.
	x += dx * (ballSpeed + 0.15 * score)
	y += dy * (ballSpeed + 0.15 * score)
	requestAnimationFrame(draw)
}
document.addEventListener("keydown", keyDownHandler, false)
document.addEventListener("keyup", keyUpHandler, false)
document.addEventListener("mousemove", mouseMoveHandler, false)

// 그냥 실행
draw()

결과물

댓글