Canvas 와 Javascript를 사용해서 만든 벽돌깨기 게임입니다. MDN에 있는 벽돌깨기 게임 튜토리얼을 보고 만들어 봤습니다. 코드는 많이 바꿨습니다. 객체지향으로 했는데, 많이 어설픈 객체지향인 것 같습니다.
Game 버튼을 누르면 게임이 시작됩니다. 2가지 종류인데, 두번째가 벽돌이 더 많습니다. 좌우 방향키로 막대기를 움직이면 됩니다. (Canvas에 포커스가 있을 때만 방향키가 작동됩니다.)
<!DOCTYPE html>
<style>
#myCanvas {
border: 3px solid lightgray;
box-sizing: border-box;
max-width: 100%;
}
#myCanvas:focus {
border-color: dodgerblue;
outline: none;
}
.startBtn { padding: 5px 12px;}
</style>
<div style="margin: 30px 0;">
<button class="startBtn" onclick="startGame(0)">Game1</button>
<button class="startBtn" onclick="startGame(1)">Game2</button>
</div>
<div style="margin: 30px 0;">
<canvas id="myCanvas" width="500" height="500" tabindex="-1"></canvas>
</div>
<script>
function startGame(no) {
game = new Game(no);
canvas.focus();
}
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
const WIDTH = canvas.width;
const HEIGHT = canvas.height;
const BALL_RADIUS = 7;
const PADDLE_WIDTH = 80;
const PADDLE_HEIGHT = 15;
const PADDLE_X = (WIDTH - PADDLE_WIDTH) / 2;
const PADDLE_Y = HEIGHT - PADDLE_HEIGHT - 10;
const PADDLE_SPEED = 7;
const COLOR = "dodgerblue";
class Ball {
constructor(x, y, radius, speed, angle, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.speed = speed;
this.setAngle(angle);
this.color = color;
}
setAngle(angle) {
var radian = angle / 180 * Math.PI;
this.mx = this.speed * Math.cos(radian);
this.my = this.speed * -Math.sin(radian);
}
move(k) {
this.x += this.mx * k;
this.y += this.my * k;
}
get collideX() {
if (this.mx > 0) return this.x + this.radius;
else return this.x - this.radius;
}
get collideY() {
if (this.my > 0) return this.y + this.radius;
else return this.y - this.radius;
}
collideWall(left, top, right) {
if (this.mx < 0 && this.collideX < left) this.mx *= -1;
if (this.mx > 0 && this.collideX > right) this.mx *= -1;
if (this.my < 0 && this.collideY < top) this.my *= -1;
}
draw(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
}
class Paddle {
constructor(x, y, width, height, speed, color) {
this.x = x;
this.y = y;
this.width = width;
this.halfWidth = width / 2;
this.height = height;
this.speed = speed;
this.color = color;
}
get center() { return this.x + this.halfWidth; }
moveLeft(wallLeft) {
this.x -= this.speed;
if (this.x < wallLeft) this.x = wallLeft;
}
moveRight(wallRight) {
this.x += this.speed;
if (this.x + this.width > wallRight) this.x = wallRight - this.width;
}
collide(ball) {
var yCheck = () => this.y - ball.radius < ball.y &&
ball.y < this.y + ball.radius;
var xCheck = () => this.x < ball.x && ball.x < this.x + this.width;
if (ball.my > 0 && yCheck() && xCheck()) {
const hitPos = ball.x - this.center;
var angle = 80 - (hitPos / this.halfWidth * 60); // 20 ~ 80
if (hitPos < 0) angle += 20; // 100 ~ 160
ball.setAngle(angle);
}
}
draw(ctx) {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
ctx.closePath();
}
}
class Bricks {
constructor(rows, cols, x, y, width, height, color) {
this.rows = rows;
this.cols = cols;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.brickWidth = width / cols;
this.brickHeight = height / rows;
this.count = rows * cols;
this.color = color;
this.data = [];
for (var i = 0; i < rows; i++) {
var line = new Array(cols);
line.fill(1);
this.data.push(line);
}
}
collide(x, y) {
var row = Math.floor((y - this.y) / this.brickHeight);
var col = Math.floor((x - this.x) / this.brickWidth);
if (row < 0 || row >= this.rows) return false;
if (col < 0 || col >= this.cols) return false;
if (this.data[row][col]) {
this.data[row][col] = 0;
this.count--;
return true;
}
else return false;
}
draw(ctx) {
ctx.fillStyle = this.color;
ctx.strokeStyle = "lightgray";
for (var r = 0; r < this.rows; r++) {
for (var c = 0; c < this.cols; c++) {
if (!this.data[r][c]) continue;
var x = this.x + (this.brickWidth * c);
var y = this.y + (this.brickHeight * r);
ctx.beginPath();
ctx.fillRect(x, y, this.brickWidth, this.brickHeight);
ctx.strokeRect(x, y, this.brickWidth, this.brickHeight);
ctx.closePath();
}
}
}
}
var keys = { left: false, right: false };
canvas.addEventListener("keydown", function(ev) {
if (ev.code == "ArrowLeft") keys.left = true;
if (ev.code == "ArrowRight") keys.right = true;
if (ev.code != "F5") ev.preventDefault();
});
canvas.addEventListener("keyup", function(ev) {
if (ev.code == "ArrowLeft") keys.left = false;
if (ev.code == "ArrowRight") keys.right = false;
if (ev.code != "F5") ev.preventDefault();
})
class Game {
constructor(no) {
var ballSpeeds = [6, 7];
var brickSettings = [
[3, 5, 0, 50, WIDTH, 150, COLOR],
[7, 10, 0, 50, WIDTH, 150, COLOR]
];
this.state = "start";
this.timeCount = 0;
this.paddle = new Paddle(PADDLE_X, PADDLE_Y, PADDLE_WIDTH, PADDLE_HEIGHT,
PADDLE_SPEED, COLOR);
this.ball = new Ball(this.paddle.center, PADDLE_Y - BALL_RADIUS, BALL_RADIUS,
ballSpeeds[no], 75, COLOR);
this.bricks = new Bricks(...brickSettings[no]);
}
update() {
if (this.state == "start") {
this.timeCount++;
if (this.timeCount >= 100) this.state = "play";
return ;
}
if (this.state != "play") return;
if (keys.left) this.paddle.moveLeft(0);
if (keys.right) this.paddle.moveRight(WIDTH);
const DIV = 10;
for (var i = 0; i < DIV; i++) {
this.ball.move(1 / DIV);
this.ball.collideWall(0, 0, WIDTH);
this.paddle.collide(this.ball);
if (this.bricks.collide(this.ball.collideX, this.ball.y)) this.ball.mx *= -1;
if (this.bricks.collide(this.ball.x, this.ball.collideY)) this.ball.my *= -1;
}
if (this.ball.y > HEIGHT + 50) this.state = "end";
if (this.bricks.count == 0) this.state = "clear";
}
draw() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
this.bricks.draw(ctx);
this.paddle.draw(ctx);
this.ball.draw(ctx);
}
}
function drawText(text) {
ctx.font = "bold 70px arial";
ctx.fillStyle = "dodgerblue";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(text, WIDTH / 2, HEIGHT / 2);
}
var game = null;
function mainLoop() {
requestAnimationFrame(mainLoop);
if (game) {
game.update();
game.draw();
if (game.state == "end") drawText("END");
if (game.state == "clear") drawText("CLEAR");
}
else drawText("Breakout");
}
mainLoop();
</script>