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

Canvas을 사용하여 벽돌깨기 게임 만들기7 (충돌 감지)

by 억만장작 2021. 7. 18.

충돌 감지 함수

우리는 공이 벽돌에 충돌했는지 확인해야 한다. 하지만 canvas에는 이런 기능이 없기 때문에 모든 벽돌을 순회하면서 부딪혔는지 확인하는 함수가 필요하다.

function collisionDetection() {
	for (let c = 0; c < brickColumnCount; c++) {
		for (let r = 0; r < brickRowCount; r++) {
			let b = bricks[c][r]
		}
	}
}

이중 반복문을 통해 bricks를 모두 순회하자

 

벽돌의 좌표는 왼쪽 위 모서리의 좌표이다. 이를 인지한 상태로 다음 4가지 조건을 만족해야 한다는 사실을 생각하자.

  1. 공의 x 좌표는 벽돌의 x 좌표보다 커야 한다.
  2. 공의 x 좌표는 벽돌의 x 좌표 + 가로 길이보다 작아야 한다.
  3. 공의 y 좌표는 벽돌의 y 좌표보다 커야 한다.
  4. 공의 y좌표는 벽돌의 y 좌표 + 높이보다 작아야 한다.
function collisionDetection() {
	for (let c = 0; c < brickColumnCount; c++) {
		for (let r = 0; r < brickRowCount; r++) {
			let b = bricks[c][r]
			if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
				dy = -dy
			}
		}
	}
}

각 조건을 충돌확인함수에 넣어준다. 여기서는 벽돌에 부딪히면 항상 y방향만 바뀌게 했다. (옆 모서리에 부딪혀도 옆으로 튕기지 않는다는 말이다.)

 

충돌 후에 벽돌을 사라지게 만들기

충돌 후에 벽돌이 사라지게 하기 위해서는 벽돌의 상태를 저장하는 변수가 있어야 한다. 이를 추가하자.

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} // 수정
	}
}

벽돌을 초기화 하는 부분에서 객체에 status를 추가해줬다 status가 1이면 벽돌이 존재한다는 뜻이다. 만약 status가 0이면 이미 벽돌이 부딪혔다는 뜻이다.

 

벽돌을 그리는 함수에서 조건을 넣어 status가 0이면 그리지 않도록 하자

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

 

 

충돌 감지 활성화하기

// draw()의 drawPaddle()아래에 호출
collisionDetection()

 

최종 코드

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

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()
	collisionDetection()
	// 공 체크
	if (y + dy < ballRadius) {
		dy = -dy
	} else if (y + dy > canvas.height - ballRadius) {
		alert("GAME OVER")
		document.location.reload()
		clearInterval(tmp)
	} 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
	y += dy * ballSpeed
}
document.addEventListener("keydown", keyDownHandler, false)
document.addEventListener("keyup", keyUpHandler, false)

let tmp = setInterval(draw, 10)

 

결과물

 

댓글