作ったもの
動機
「ゲームを作る」ということが JavaScript
の熟達度合いを測る良い指標になると聞いたので、試しにブロック崩しを作ってみた。見た目は1979年に任天堂が発売したブロック崩しを参考にしている。当時のゲームのように、5種類作ることも考えたが、とりあえずシンプルなものだけ作成した。
コード
ゲームを作る上でのポイントは以下の関数で、これによってボールが動いているように見せています。
var canvas = document.getElementById("Canvas id");
var context = canvas.getContext("2d");
// これが重要。キャンバスに描画した指定範囲内の絵を削除している
context.clearRect(0, 0, canvas.width, canvas.height);
html
<div class="breakout">
<header>BREAKOUT</header>
<canvas id="canvas" width="480" height="500"></canvas>
<a id="start" onclick="gameButton();startTimer()" class="btn-circle-border-double">START</a>
<audio id="soundBrick" src="https://taira-komori.jpn.org/sfxr/sfxrse/02laser/shoot01.mp3" type="audio/mp3" preload="auto"></audio>
<audio id="soundBar" src="https://taira-komori.jpn.org/sfxr/sfxrse/02laser/shoot03.mp3" type="audio/mp3" preload="auto"></audio>
<audio id="soundFail" src="https://taira-komori.jpn.org/sfxr/sfxrse/04powerup/powerdown02.mp3" type="audio/mp3" preload="auto"></audio>
<audio id="soundUp" src="https://taira-komori.jpn.org/sfxr/sfxrse/04powerup/powerup05.mp3" type="audio/mp3" preload="auto"></audio>
</div>
javascript
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// パドルのパラメータ
var paddleHeight = 10;
var paddleWidth = 50;
var paddleOffsetBottom = 80;
var paddleX = (canvas.width-paddleWidth)/2;
// 入力を記録する変数
var rightPressed = false;
var leftPressed = false;
// ブロックのパラメータ
var brickColumnCount = 14;
var brickRowCount = 6;
var brickColors = ["#F39800", "#FFF100", "#8FC31F"];
var brickWidth = 25;
var brickHeight = 10;
var brickPadding = 5;
var brickOffsetTop = 70;
var brickOffsetLeft = 30;
// ボールのパラメータ
var ballRadius = 5;
var x = canvas.width/2;
var y = canvas.height-paddleOffsetBottom-brickHeight;
var dx = 3;
var dy = -3;
// スコアを記録する。
var score = 0;
var maxscore = 0;
for(var r=0; r<brickRowCount; r++) {
for(var c=0; c<brickColumnCount; c++) {
maxscore += 3-Math.floor(r/2);
}
}
// ライフを記録する。
var lives = 3;
// 経過時間を記録する関数
var start_time;// グローバル変数にしている。
var timer=0;
// 3桁の数字で表示するため
var addZero = function(value){
if (value<10) {
value = '00' + value;
}else if (value<100){
value = '0' + value;
}
return value;
}
// イベント開始の関数
function startTimer(){
start_time = new Date();
setInterval(goTimer, 10);
}
// この関数をループさせる。
function goTimer(){
var milli = new Date() - start_time;
var seconds = Math.floor(milli / 1000);
seconds = addZero(seconds);
timer = seconds;
}
// ブロックが当たった後に消えるようにする。
var bricks = [];
for(var r=0; r<brickRowCount; r++) {
bricks[r] = [];
for(var c=0; c<brickColumnCount; c++) {
// statusでブロックが崩されたかを記憶する。
bricks[r][c] = { x: 0, y: 0, status: 1 };
}
}
// ボタンの処理で制御する
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
// ボタンが押された時のイベント(変数をTrueにする)
function keyDownHandler(e) {
if(e.key == "Right" || e.key == "ArrowRight") {
rightPressed = true;
}
else if(e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = true;
}
}
// ボタンが押されなくなった時のイベント(変数をfalseに戻す)
function keyUpHandler(e) {
if(e.key == "Right" || e.key == "ArrowRight") {
rightPressed = false;
}
else if(e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = false;
}
}
// マウスの位置で制御する
document.addEventListener("mousemove", mouseMoveHandler, false);
// マウスが動いた時のイベント。マウスのx座標がキャンバスのx座標内にあれば、その位置に持ってくる。
function mouseMoveHandler(e) {
var relativeX = e.clientX - canvas.offsetLeft;
if(relativeX>0){
if(relativeX<canvas.width) {
paddleX = relativeX - paddleWidth/2;
}
}
}
// 音を鳴らす。
function sound(tag){
document.getElementById(tag).currentTime = 0;
document.getElementById(tag).play();
}
// 衝突を検知する。
function collisionDetection() {
for(var r=0; r<brickRowCount; r++) {
for(var c=0; c<brickColumnCount; c++) {
var b = bricks[r][c];
if(b.status == 1) { // ブロックが存在しているかを確認する。
if(x>b.x){
if (x<b.x+brickWidth){
if (y>b.y){
if (y<b.y+brickHeight){
dy = -dy;
if (Math.floor(Math.random() * Math.floor(8)) == 0){
sound("soundUp");
if (dy>0){
dy += 1;
}else{
dy -= 1;
}
}
b.status = 0;
score+=3-Math.floor(r/2);
sound("soundBrick");
// 全てのブロックを崩した場合
if(score == maxscore) {
alert("YOU WIN\nYour Time is " + timer);
document.location.reload();
}
}
}
}
}
}
}
}
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.closePath();
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height-paddleHeight-paddleOffsetBottom, paddleWidth, paddleHeight);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.closePath();
}
// ブロックを1つずつ描画する。
function drawBricks() {
for(var r=0; r<brickRowCount; r++) {
for(var c=0; c<brickColumnCount; c++) {
if(bricks[r][c].status == 1) {
var brickX = (c*(brickWidth+brickPadding))+brickOffsetLeft;
var brickY = (r*(brickHeight+brickPadding))+brickOffsetTop;
bricks[r][c].x = brickX;
bricks[r][c].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = brickColors[Math.floor(r/2)];
ctx.fill();
ctx.closePath();
}
}
}
}
function drawScore() {
ctx.font = "30px 'Comic Sans MS'";
ctx.fillStyle = "#ffffff";
ctx.fillText(score, 30, 40);
}
function drawTime() {
ctx.font = "30px 'Comic Sans MS'";
ctx.fillStyle = "#ffffff";
ctx.fillText(timer, canvas.width/2-30, 40);
}
function drawLives() {
ctx.font = "30px 'Comic Sans MS'";
ctx.fillStyle = "#ffffff";
ctx.fillText(lives, canvas.width-65, 40);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 毎フレームごとに削除
drawBricks();
drawBall();
drawPaddle();
drawScore();
drawTime();
drawLives();
collisionDetection();
// 左端と右端で弾ませる(ボールの半径を考える)
if(x + dx>canvas.width-ballRadius || x + dx<ballRadius) {
sound("soundBar");
dx = -dx;
}
// 上端で弾ませる(ボールの半径を考える)
if(y + dy<ballRadius) {
sound("soundBar");
dy = -dy;
}
// ボールがパドルの位置に到達した時
else if(y+dy>canvas.height-paddleOffsetBottom-ballRadius){
if (y+dy<canvas.height-paddleOffsetBottom-ballRadius+brickHeight) {
// ボールのx座標がパドル上にあれば、跳ね返る
if(x>paddleX){
if(x<paddleX + paddleWidth){
sound("soundBar");
dy = -dy;
}
}
}
// ボールが下端に到達した時
if(y + dy>canvas.height-ballRadius){
sound("soundFail");
lives--;
// 残機がなくなれば失敗する。
if(!lives) {
alert("YOU LOSE");
document.location.reload();
}
else {
x = canvas.width/2;
y = canvas.height-paddleOffsetBottom-brickHeight;
dx = 3;
dy = -3;
paddleX = (canvas.width-paddleWidth)/2;
}
}
}
// パドルの移動指定されたピクセルだけ動く
if(rightPressed){
if(paddleX<canvas.width-paddleWidth) {
paddleX += 5;
}
}
else if(leftPressed){
if (paddleX>0) {
paddleX -= 5;
}
}
x += dx;
y += dy;
requestAnimationFrame(draw);
}
function setCanvas() {
drawBricks();
drawBall();
drawPaddle();
drawScore();
drawTime();
drawLives();
}
// 初期状態を描いて待機
setCanvas();
function gameButton() {
draw();
document.getElementById("start").innerHTML = "SPEED UP";
document.getElementById("start").style.fontSize = "10px";
}
css
@import url('https://fonts.googleapis.com/css?family=Press+Start+2P&display=swap');
.breakout {
text-align: center;
margin: 0 auto;
font-family: 'Press Start 2P', cursive;
}
canvas {
background: #000;
display: block;
border: solid 1.0px #eeeeee;
margin: 0 auto;
}
.btn-circle-border-double {
display: inline-block;
text-decoration: none;
color: #000000;
width: 100px;
height: 100px;
line-height: 100px;
border-radius: 50%;
border: double 4px #000000;
text-align: center;
overflow: hidden;
transition: .6s;
}
.btn-circle-border-double:hover {
-webkit-transform: rotateY(360deg);
transform: rotateY(360deg);
}