見出し画像

Unity触り始めたけど、全然分からんからJavaScriptで修行する ~ブロック崩しゲーム編~


挨拶的なの

お久しぶりです。もーふです。最近はJavaScriptでブロック崩しゲームとか作ってました。

コードは一番下に備忘録を兼ねて載せていますので、近況報告はいらねえという方は以下の目次からコードのところに飛んでください。また参考にしたブロック崩しゲームのサイト以下のサイトです。なおchatGPTの力を大いに借りてます。

参考にしたサイト




話を戻します。なんでブロック崩しゲームを作っていたかというと、Unityインストールして、ぼちぼちやってましたが全然分からなかったからなんですね。個人的にはマリオみたいな横スクロールの2Dアクションのゲームを作りたかったのですが…そこまでレベルが高いのは無理と分かりました。

そこでもっと簡単なゲームを作ろうと思いまして、Unityは途中で投げて、勉強中だったJavaScriptの方でゲームを作りました。

ちなみにUnityをやり始めた記事は下のリンクです。インストールから手こずってる様子が見られます。

↓ 前回の記事


そんなことよりワートリを読もう


話は変わりますが、今月のジャンプSQのワールドトリガーの246話と247話をお読みになりましたでしょうか?

ヒュースが若村に実力の話をするところですね。かなり厳しい言葉でしたが、それとともに力強いエールを感じました。

自分も壁に対して一段一段刻んで登っていって、出来るようになったという無数の事実を自信に変えていきたいと思いました。

まあとりあえずジャンプSQ2024年11月号を読んでください。この記事最後まで読むよりもむしろ該当号買ってワールドトリガー読んでください。


ブロック崩しゲームについて

話は戻してまして、自分のゲーム作りですね。とりあえず初めに「JavaScript ゲーム」あたりで、Google検索をしてどんなゲーム作るか探したと思います。そして見つけたのが先ほどのリンクです。ブロック崩しゲームの製作についてとても分かりやすいのでこちらを見てください。


この記事を参考にしてコードを書いたら、ブロック崩しゲームが出来ます。

完成させた時少し物足りなかったので主に以下の機能を追加してみました。


スタート画面

スタート画面。押したらカウントダウンが始まる。


3ラウンド制

プレイ画面。ラウンド3まである。


他にもブロックの種類を変えて、2回当たったら壊れるとか壊したらライフを回復するとかの実装を考えてましたが、今回は断念しました。

というよりはブロック崩しゲーム触るのに単純に飽きたからです。そのような機能の実装はまたいつかの機会にできればいいと思います。

そもそも初めてのゲーム作りで自分なりの欲しい機能追加してみるという経験ができました。

とはいえchatGPTに泣きつくことn回。ほぼchatGPTが書いたと言っても過言ではないかもしれません。自分が質問してchatGPTがコード書くなら自分はいらないのでは…?と思うこともありながら作っていました。

とりあえずゲームを作った経験を得たということで、Unityに戻ろう…の前にまだまだ修行してみたいと思います。まだまだ2Dアクションを作るには理解が足lりない気がしたので。


次はシューティングゲーム作ってみたい

次はシューティングゲームを作ろうと思います。というか今まさに作り中です。現在自機と敵、弾の基本的な構造はかけたので、今後敵の動きや追加、自機の強化などできたらいいなと思います。

シューティングゲームにした理由は、ブロック崩しゲームのパドルの移動を自機の移動、ボールの移動を敵やショットの移動、衝突判定はそのまま使用と…ゲームの構造的に似た機能かなと思ったからです。

また完成したらnoteにでも備忘録兼ねて載せたいと思います。


備忘録のコード

載せておきますが、動くか分かりませんのでご了承お願いします。

また注意点としてchatGPTの力を大いに借りています。
コメントはたまにそのままにしてコードだけ変えた時があるので、コメントの意味がない時があるかもしれません。そして長いと思います。

読んでくれた方はありがとうございました。良かったら他の記事も見てください。


//html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale-1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>ブロック崩し</title>
    
</head>

    <style>
      * {
        padding: 0;
        margin: 0;
      }
      canvas {
        background-color:rgb(216, 208, 208);  
        display: block;
        margin: 0 auto;
      }
      
    </style>

<body>
<h1 class ="gametitle">ブロック崩しゲーム</h1>

    <canvas id ="myCanvas" width="480" height="320"></canvas>
  </div>
<script src="js/game.js">
   
</script> 


</body>
</html>
//js
var canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
const  ballRadius = 10;

let x = canvas.width / 2;
let y =canvas.height - 30;
let dx = 2
let dy = -2

const paddleHeight = 10;
const paddleWidth = 75;
let maxSpeed = 3.5
let speed = 0; // 現在の速度を保持する変数
const SPEED_INCREASE_FACTOR = 1.1

let rightPressed = false;
let leftPressed = false;

let paddleX =(canvas.width - paddleWidth) / 2;
let textColor =" rgb(112, 162, 172)";
let ballColor = " rgb(112, 162, 172)";

const paddleSpeed = 7;

var brickRowCount = 3;
var brickColumnCount = 5;
var brickWidth = 75;
var brickHeight = 20;
var brickPadding = 10;
var brickOffsetTop = 30;
var brickOffsetLeft = 30;
var scoreDisplayHeight = 50;

let score = 0;
let roundScore = 0; 
let requiredScore = brickRowCount * brickColumnCount; 
let lives = 3;


let isGameRunning = false;   //ゲーム開始を制御するフラグ
let isCountingDown = false;  // カウントダウン中かどうかのフラグ
let countdown = 3;
let currentRound = 1; // 現在のラウンドを管理する変数

document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
document.addEventListener("mousemove", mouseMoveHandler, false);

function drawStartMessage() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);  // 画面をクリア
  ctx.font = "26px Arial";
  ctx.fillStyle = "rgb(112, 162, 172)";
  ctx.textAlign = "center";
  ctx.fillText("クリック or Enterキーでゲーム開始", canvas.width / 2, canvas.height / 2);
}

function startCountdown(){
    isCountingDown = true;
    countdown = 3;
    let countdownInterval = setInterval(function() {
      // カウントダウンをキャンバスに表示
      ctx.clearRect(0, 0, canvas.width, canvas.height);  // 画面をクリア
      ctx.font = "30px Arial";
      ctx.fillStyle = "rgb(112, 162, 172)";
      ctx.textAlign = "center";
      ctx.fillText(countdown, canvas.width / 2, canvas.height / 2);

      countdown--;  // カウントダウンを減らす

      if (countdown < 0) {
          clearInterval(countdownInterval);// カウントダウン終了
          isCountingDown = false;   
          isGameRunning = true;              // ゲーム開始
          draw();                            // ゲームループを再開
      }
  }, 1000);  // 1秒ごとにカウントダウンを減らす
}

function handleGameStart() {
  if (!isGameRunning && !isCountingDown) {
    startCountdown();
  }
}

document.addEventListener("click", handleGameStart);
document.addEventListener("keydown", function(e) {
  if (e.key === "Enter") {
    handleGameStart();
  }
});

function keyDownHandler(e) {
    if (e.key === "Right" || e.key === "ArrowRight") {
      rightPressed = true;
    } else if (e.key === "Left" || e.key === "ArrowLeft") {
      leftPressed = true;
    }
  }
  
function keyUpHandler(e) {
    if (e.key === "Right" || e.key === "ArrowRight") {
      rightPressed = false;
    } else if (e.key === "Left" || e.key === "ArrowLeft") {
      leftPressed = false;
    }
  }

  function mouseMoveHandler(e) {
    const relativeX = e.clientX - canvas.offsetLeft; // マウスのX座標を取得
    // 新しいX座標を計算
    let newPaddleX = relativeX - paddleWidth / 2;

    // 左端の制限
    if (newPaddleX < 0) {
        newPaddleX = 0; // 左端に合わせる
    }

    // 右端の制限
    if (newPaddleX + paddleWidth > canvas.width) {
        newPaddleX = canvas.width - paddleWidth; // 右端に合わせる
    }

    // 新しいパドルのX座標を設定
    paddleX = newPaddleX;
}

function drawBall(){
    ctx.beginPath();
    ctx.arc(x,y,ballRadius,0,Math.PI*2);
    ctx.fillStyle = ballColor;
    ctx.fill();
    ctx.closePath();
}

var bricks = [];
for (var c = 0; c < brickColumnCount; c++) {
    bricks[c] = [];
    for (var r = 0; r < brickRowCount; r++) {
        bricks[c][r] = { x: 0, y: 0, status: 1 };
    }
}

function drawBricks() {
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      if (bricks[c][r].status === 1) {
        const brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
        const brickY = r * (brickHeight + brickPadding) + brickOffsetTop + scoreDisplayHeight;
        bricks[c][r].x = brickX;
        bricks[c][r].y = brickY;
        ctx.beginPath();
        ctx.rect(brickX, brickY, brickWidth, brickHeight);
        ctx.fillStyle = ballColor;
        ctx.fill();
        ctx.closePath();
      }
    }
  }
}

function collisionDetection() {
  for (let c = 0; c < brickColumnCount; c++) {
      for (let r = 0; r < brickRowCount; r++) {
      const b = bricks[c][r];
      if (b.status === 1) {
        if (
          x > b.x &&
          x < b.x + brickWidth &&
          y > b.y &&
          y < b.y + brickHeight
          ) {
          dy = -dy;
          b.status = 0;
          score++;
          roundScore++
          
          if (roundScore === requiredScore) {
      
             if (currentRound === 3) {
              alert("YOU WIN, CONGRATULATIONS!"); // すべてのラウンドをクリア
              document.location.reload(); // ゲームをリロード
              } else {
                nextRound();  // 次のラウンドへ移行
              }
            }
          }
         }
      }
  }
}

function resetRound() {
  roundScore = 0;            // ラウンドスコアをリセット
  requiredScore = brickRowCount * brickColumnCount;  // 必要なスコアを設定
  resetBricks();             // ブロックをリセット
  resetBall();               // ボールの位置をリセット
}
function  nextRound(){
  isGameRunning = false;  // ゲームを一時停止
  currentRound++;
  resetRound();
  startCountdown();  // カウントダウンを開始
  
  //ラウンドごとにmaxSpeedを変更
  if (currentRound === 2) {
   maxSpeed = 4.5;
 } else if (currentRound === 3) {
   maxSpeed = 5.5;
 }
}

function resetBricks(){
  bricks = [];  // ブロック配列をリセット
  for (let c = 0; c < brickColumnCount; c++) {
    bricks[c] = [];
    for (let r = 0; r < brickRowCount; r++) {
      bricks[c][r] = { x: c * (brickWidth + 10), y: r * (brickHeight + 10), status: 1 };  // 新しいブロックを生成
      }
    }
  }


function resetBall(){
  x = canvas.width / 2;  // ボールを中央にリセット
  y = canvas.height - 30;  // ボールをパドルの上にリセット
  dx = 2;  // ボールの水平速度をリセット
  dy = -2;  // ボールの垂直速度をリセット
  }     // ボールの位置をリセット

function drawScore() {
  ctx.font = "16px Arial";
  ctx.fillStyle = textColor;
  ctx.textAlign = "left";
  ctx.fillText(`Score: ${score}`, 10, 20);
  ctx.ite
}

function drawSpeed(){
 // 現在の速度を計算
 speed = Math.sqrt(dx * dx + dy * dy);

 // 速度を表示
 ctx.fillStyle = textColor; // テキストの色
 ctx.font = '16px Arial'; // フォントの設定
 ctx.textAline = "raight";
 ctx.fillText(`Speed: ${speed.toFixed(2)}`, 300, 20); // 速度を表示
}

function drawPaddle(){
    ctx.beginPath()
    ctx.rect(paddleX,canvas.height - paddleHeight,paddleWidth,paddleHeight);
    ctx.fillStyle = ballColor;
    ctx.fill();
    ctx.closePath();
}

function drawScoreDisplay() {
  // スコア表示エリアを描画
  ctx.fillStyle = "#FFFFFF"; // 背景色
  ctx.fillRect(0, 0, canvas.width, scoreDisplayHeight); // スコア表示エリアを描画

  // 区切り線を描画
  ctx.beginPath();
  ctx.moveTo(0, scoreDisplayHeight); // 区切り線の開始位置
  ctx.lineTo(canvas.width, scoreDisplayHeight); // 区切り線の終了位置
  ctx.strokeStyle = textColor; // 線の色
  ctx.lineWidth = 2; // 線の太さ
  ctx.stroke(); // 線を描画
}
function drawLives(){
  ctx.font = "16px Arial";
  ctx.fillStyle = textColor;
  ctx.textAlign = "left";  
  ctx.fillText(`lives : ${lives}`,10,40);
}



function draw(){
  if (!isGameRunning||isCountingDown) {
    return;  // ゲームが停止中かカウントダウン中なら描画しない
  }
  //描画コード
ctx.clearRect(0,0,canvas.width,canvas.height);
drawScoreDisplay();
drawBricks();
drawBall();
drawPaddle();
drawScore();
drawSpeed();
drawLives();

collisionDetection();

  if(y - ballRadius < scoreDisplayHeight) {
   y =scoreDisplayHeight + ballRadius;
   dy =-dy
  }

  if(x + dx >canvas.width -ballRadius || x + dx < ballRadius ) {
    dx = -dx
  }

  if (y + dy < ballRadius  ){
    dy = -dy;
    } else if(y +dy > canvas.height - ballRadius) {
        if (x > paddleX && x <paddleX + paddleWidth) {
            dy = -dy;
        } lives--;
        if (lives===0) {
          alert("GAME OVER");
          document.location.reload();
          clearInterval(interval); // クロームがゲームを終了するのに必要
        } else {
          isGameRunning = false;  // ゲームを一時停止
          startCountdown();     
          x = canvas.width / 2;
          y = canvas.height - 30;
          dx = 2;
          dy = -2;
          paddleX = (canvas.width - paddleWidth) / 2;
        }
    }

    if (rightPressed) {
      paddleX += paddleSpeed;
      if (paddleX + paddleWidth > canvas.width) {
          paddleX = canvas.width - paddleWidth;
          // パドルの右端をキャンバスの右端に合わせる
         }
    } else if (leftPressed) {
      paddleX -= paddleSpeed;
      if (paddleX < 0) {
          paddleX = 0; // パドルの左端をキャンバスの左端に合わせる
        }
      }
 
  // パドルとの衝突検出
  if (y + ballRadius >= canvas.height - paddleHeight && 
    x >= paddleX && 
    x <= paddleX + paddleWidth) {
    dy = -dy; // ボールの方向を反転
    
    // ボールの当たった位置を基に新しいdxを計算
    let paddleCenter = paddleX + paddleWidth / 2;
    let ballPosition = x - paddleCenter;

    dx = (ballPosition / (paddleWidth / 2)) * maxSpeed; // 反射角を計算

     // ボールの速度を増加させる
     dx *= SPEED_INCREASE_FACTOR; // 水平方向の速度を増加
     dy *= SPEED_INCREASE_FACTOR; // 垂直方向の速度を増加
     
 // 速度が上限を超えないように制限
 const speed = Math.sqrt(dx * dx + dy * dy);
 if (speed > maxSpeed) {
     const scale = maxSpeed / speed;
     dx *= scale;
     dy *= scale;
     }   
    }
    x += dx;
    y += dy;
    requestAnimationFrame(draw);
  }

window.onload = function() {
  drawStartMessage();  // クリックを待つメッセージを表示
   };
   
draw();


いいなと思ったら応援しよう!