## 1. 프로젝트 설정
우선, Vue CLI를 사용하여 새로운 Vue 프로젝트를 생성합니다.
```bash
vue create brick-breaker
cd brick-breaker
npm run serve
```
프로젝트가 생성되고 서버가 시작되면 브라우저에서 기본 페이지를 볼 수 있습니다.
## 2. 컴포넌트 구조 설계
우리는 단일 파일 컴포넌트 구조를 사용하여 앱을 구성할 것입니다. 먼저 `src/components` 디렉토리에 `Game.vue` 파일을 생성합니다. 이 컴포넌트는 게임의 기본 뷰가 될 것입니다.
**Game.vue**
```html
<template>
<div id="game" ref="gameContainer">
<canvas ref="gameCanvas"></canvas>
</div>
</template>
<script>
export default {
name: 'Game',
mounted() {
this.initializeGame();
},
methods: {
initializeGame() {
const canvas = this.$refs.gameCanvas;
const context = canvas.getContext('2d');
canvas.width = 480;
canvas.height = 320;
// 초기 공, 패들 및 벽돌 설정
// 예: 공의 위치, 패들을 그리기 위한 코드 추가
}
}
}
</script>
<style>
#game {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
canvas {
background-color: #eee;
}
</style>
```
## 3. 게임 로직 구현
공과 패들, 벽돌의 이동 및 충돌을 처리하는 게임 로직을 구현합니다. 여기서는 간단한 물리 법칙과 충돌 감지를 설정합니다.
**Example Code for Ball Movement**
```js
data() {
return {
ball: {
x: 240, // canvas의 가운데
y: 160,
dx: 2,
dy: -2,
radius: 10
}
};
},
methods: {
drawBall(context) {
context.beginPath();
context.arc(this.ball.x, this.ball.y, this.ball.radius, 0, Math.PI * 2);
context.fillStyle = '#0095DD';
context.fill();
context.closePath();
},
updateBall() {
// 충돌 확인 및 위치 업데이트
if (this.ball.x + this.ball.dx > 480 - this.ball.radius || this.ball.x + this.ball.dx < this.ball.radius) {
this.ball.dx = -this.ball.dx;
}
if (this.ball.y + this.ball.dy < this.ball.radius) {
this.ball.dy = -this.ball.dy;
} else if (this.ball.y + this.ball.dy > 320 - this.ball.radius) {
// 패들과의 충돌 또는 게임 오버 체크
}
this.ball.x += this.ball.dx;
this.ball.y += this.ball.dy;
}
}
```
## 4. 패들 동작 추가
사용자 입력을 받아 패들이 좌우로 움직이도록 구현합니다. 이는 키보드 이벤트를 사용하여 조작할 수 있습니다.
**Example Code for Paddle Movement**
```js
data() {
return {
paddle: {
width: 75,
height: 10,
x: (480 - 75) / 2,
speed: 7,
rightPressed: false,
leftPressed: false
}
};
},
mounted() {
document.addEventListener('keydown', this.keyDownHandler);
document.addEventListener('keyup', this.keyUpHandler);
},
methods: {
keyDownHandler(e) {
if (e.key === 'Right' || e.key === 'ArrowRight') {
this.paddle.rightPressed = true;
} else if (e.key === 'Left' || e.key === 'ArrowLeft') {
this.paddle.leftPressed = true;
}
},
keyUpHandler(e) {
if (e.key === 'Right' || e.key === 'ArrowRight') {
this.paddle.rightPressed = false;
} else if (e.key === 'Left' || e.key === 'ArrowLeft') {
this.paddle.leftPressed = false;
}
},
movePaddle() {
if (this.paddle.rightPressed && this.paddle.x < 480 - this.paddle.width) {
this.paddle.x += this.paddle.speed;
} else if (this.paddle.leftPressed && this.paddle.x > 0) {
this.paddle.x -= this.paddle.speed;
}
}
}
```
## 5. 벽돌 구현 및 충돌 감지
벽돌을 배열로 설정하여 화면에 나타낸 후, 공과의 충돌 여부를 감지합니다.
**Example Brick Setup and Collision Detection**
```js
data() {
return {
bricks: [],
brickRowCount: 3,
brickColumnCount: 5,
brickWidth: 75,
brickHeight: 20,
brickPadding: 10,
brickOffsetTop: 30,
brickOffsetLeft: 30
};
},
methods: {
initializeBricks() {
for (let c = 0; c < this.brickColumnCount; c++) {
this.bricks[c] = [];
for (let r = 0; r < this.brickRowCount; r++) {
this.bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
},
drawBricks(context) {
for (let c = 0; c < this.brickColumnCount; c++) {
for (let r = 0; r < this.brickRowCount; r++) {
if (this.bricks[c][r].status === 1) {
const brickX = c * (this.brickWidth + this.brickPadding) + this.brickOffsetLeft;
const brickY = r * (this.brickHeight + this.brickPadding) + this.brickOffsetTop;
this.bricks[c][r].x = brickX;
this.bricks[c][r].y = brickY;
context.beginPath();
context.rect(brickX, brickY, this.brickWidth, this.brickHeight);
context.fillStyle = '#0095DD';
context.fill();
context.closePath();
}
}
}
},
collisionDetection() {
for (let c = 0; c < this.brickColumnCount; c++) {
for (let r = 0; r < this.brickRowCount; r++) {
const b = this.bricks[c][r];
if (b.status === 1) {
if (this.ball.x > b.x && this.ball.x < b.x + this.brickWidth &&
this.ball.y > b.y && this.ball.y < b.y + this.brickHeight) {
this.ball.dy = -this.ball.dy;
b.status = 0; // 벽돌을 깨면 status를 0으로 변경
}
}
}
}
}
}
```
## 6. 게임 루프 설정
게임을 실제로 움직이게 하기 위해 애니메이션 루프를 설정합니다. `requestAnimationFrame`을 사용하여 매 프레임마다 캔버스를 갱신합니다.
**Example Animation Loop**
```js
mounted() {
this.initializeBricks();
this.initializeGame();
this.gameLoop();
},
methods: {
gameLoop() {
const canvas = this.$refs.gameCanvas;
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
this.drawBricks(context);
this.drawBall(context);
this.updateBall();
this.drawPaddle(context);
this.movePaddle();
this.collisionDetection();
requestAnimationFrame(this.gameLoop);
},
drawPaddle(context) {
context.beginPath();
context.rect(this.paddle.x, canvas.height - this.paddle.height, this.paddle.width, this.paddle.height);
context.fillStyle = "#0095DD";
context.fill();
context.closePath();
}
}
```
이렇게 각 단계별로 구현하면 Vue.js를 활용한 간단한 벽돌깨기 게임이 완성됩니다. 개발을 진행하며 Vue의 반응성 시스템과 HTML5 캔버스를 통한 그래픽 처리 방법에 대한 이해가 높아질 것입니다. 이를 게임의 기초로 삼아 자신의 아이디어를 추가하고, 더 다양한 기능과 시각 효과를 추가해보세요.