見出し画像

【p5.js】ブロック崩しゲームでプログラミングの魅力を体感しよう

こんにちは!
今回はp5.jsを利用して、お手軽にブロック崩しゲームを作る方法を紹介します。
初級者にとって身近なゲームを通じたプログラミング学習は、理解を深める大きなステップとなります。 今回紹介するコードは、クラシックゲームの1つである「ブロック崩し」を実装したものです。壁やボール、プレイヤーのパドル、そして自機が当たるべきブロックをオブジェクトとして作成しています。
最終的にはこんなゲームが完成します。

このコードでは、p5.jsだけでなく、p5.play.jsという物理エンジンも使用しています。物理エンジンを使わないと、衝突判定や物理演算のコーディングが必要となり、初級者にとってはゲーム制作の敷居が高くなってしまいます。物理エンジンの有効性は、こうした難解さを軽減し、初級者でもゲームの魅力を最大限に発揮できる作品を作る助けとなります。簡潔なコードで複雑な物理現象を再現するのが物理エンジンの魅力です。このシンプルでわかりやすいコードから始めて、自分だけのオリジナルゲームを作る一歩を踏み出しましょう。これからのゲーム制作の旅が、一層楽しく充実したものになるよう、活用いただければ幸いです。本記事ではプログラムのポイントを解説していきます。(プログラム全体は一番下にあります。)


物理エンジンのセットアップ:2行コピペするだけ

まずは開発環境をセットアップしていきます。開発環境にはp5.js Web Editorを使います。p5.js Web Editorは、p5.jsのオンラインプラットフォームで、ブラウザだけでコーディングや実行ができます。導入が簡単でセットアップ不要、即時プレビュー機能付きで作品の素早いテストや共有が可能です。
p5.js Web Editorを起動し、index.htmlを表示します。
そのheadタグ内に2行追加します。

<script src="https://p5play.org/v3/p5play.js"></script> <!-- ★追加 -->
<script src="https://p5play.org/v3/planck.min.js"></script> <!-- ★追加 -->    

追加すると、headタグ全体では次のようになります。

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.8.0/p5.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.8.0/addons/p5.sound.min.js"></script>
  <!-- Version 3 of p5play -->
  <script src="https://p5play.org/v3/p5play.js"></script> <!-- ★追加 -->
  <script src="https://p5play.org/v3/planck.min.js"></script> <!-- ★追加 -->
  <link rel="stylesheet" type="text/css" href="style.css">
  <meta charset="utf-8" />
</head>

これで物理エンジンの設定は終わりです。

ステージの設定:舞台は準備OK?

ゲームにおいて最初に必要なのは、プレイヤーが活動できる「ステージ」の設定です。この設定を行うことでゲームの境界線が確定し、ボールが画面外に出ないようにできます。今回は壁を4つ(左・右・上・下)設定します。下の壁はボールが触れたときにゲームオーバーとなる特別な壁です。
壁の作成にはSpriteクラスを使います。Spriteは物理エンジンであるp5play.jsのクラスです。このクラスを使うと物理空間に四角い物体を簡単に作ることができます。

const wallLeft = new Sprite(5, 200, 10, 400, "s");
setupSprite(wallLeft);
const wallRight = new Sprite(400-5, 200, 10, 400, "s");
setupSprite(wallRight);
const wallTop = new Sprite(200, 5, 400, 10, "s");
setupSprite(wallTop);
const wallBottom = new Sprite(200, 400 + 5, 400, 10, "s");
setupSprite(wallBottom);

ボールが踊りだす

次にステージ上で動くボールを設定します。ボールは定数ballSpeedで設定した速度で動き、その初期方向は変数ballDirectionで設定します。ゲーム開始時は真下に動いてほしいので、90度(PI/2)に設定します。

const ballSpeed = 3;
var ballDirection = Math.PI / 2;// rad
const ball = new Sprite(200, 200, 16, 16);
setupSprite(ball);
ball.vel.x = ballSpeed * Math.cos(ballDirection);
ball.vel.y = ballSpeed * Math.sin(ballDirection);

プレイヤーの誕生:動かせると楽しい

ここでは操作するプレイヤーを作成します。キーボードの左右の矢印を使ってプレイヤーを動かせるようにします。これでプレイヤーとゲームのインタラクションが始まります。

const player = new Sprite(200, 360, 64, 16, "s")
setupSprite(player);
player.moveSpeed = 3;
player.update = function() {
    if(keyIsDown(RIGHT_ARROW)) {
      player.x += player.moveSpeed;
    }
    if(keyIsDown(LEFT_ARROW)) {
      player.x += -player.moveSpeed;
    }
}


衝突の実装:物理エンジンをフル活用

ブロックやプレイヤーとボールが衝突したときの動作を設定します。ここで物理エンジンが大活躍で、ブロックが消えたり、ボールが反射したりする挙動をシミュレートします。衝突にはSprite#colliding()を使います。
たとえば、画面下側の壁とボールが衝突したらゲームオーバーにしたいので、このように書きます。

if(wallBottom.colliding(ball)) {
  ball.remove();
}

collidingメソッドは引数で指定したスプライトと衝突するとtrueになります。そしてボールを消すにはSprite#remove()を呼び出せばOKです。
このように物理エンジンで用意されたSpriteクラスを使うとゲームに必要な機能を簡単に作れます。

プレイヤーとボールの衝突では、ボールの飛ぶ方向を変更します。

if(player.colliding(ball)) {
  const diffX = ball.x - player.x;
  // -32 --> -150deg --> - PI * 3 / 4
  //  32 --> -30deg  --> - PI * 1 / 4
  const diffRate = (diffX + 32) / 64;
  ballDirection = - ((1.0 - diffRate) * Math.PI * 2 / 4 + Math.PI * 1 / 4);
  ball.vel.x = ballSpeed * Math.cos(ballDirection);
  ball.vel.y = ballSpeed * Math.sin(ballDirection);
}

このプログラムでは、プレイヤーの左端(-32px)に当たったらボールは-135度の方向に飛び、右端(32px)に当たったら-45度の方向に飛ぶように計算しています。

ブロックの壁:中央にたくさん並べよう

最後に、ブロックを画面に並べてゲームを完全なものにします。7x7のブロックを配置し、ゲームの目標を形作ります。
7x7のブロックが画面中央に来るように座標を調整します。

  const blocks = [];
  for(let y = 0; y < 7; y++) {
    for(let x = 0; x < 7; x++) {
      const block = new Sprite(x * 32 + 104, y * 16 + 64, 32, 16, 's');
      setupSprite(block);
      blocks.push(block);
    }
  }

おしまい

以上で基本的なブロック崩しのゲームが完成します。細部の調整や装飾エフェクトの追加は自由に行って、自分だけのゲームを完成させましょう!

以下は、プログラム全体です。

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.8.0/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.8.0/addons/p5.sound.min.js"></script>
    <!-- Version 3 of p5play -->
    <script src="https://p5play.org/v3/p5play.js"></script>
    <script src="https://p5play.org/v3/planck.min.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="utf-8" />

  </head>
  <body>
    <main>
    </main>
    <script src="sketch.js"></script>
  </body>
</html>


sketch.js

function setup() {
  createCanvas(400, 400);
  
  const wallLeft = new Sprite(5, 200, 10, 400, "s");
  setupSprite(wallLeft);
  const wallRight = new Sprite(400-5, 200, 10, 400, "s");
  setupSprite(wallRight);
  const wallTop = new Sprite(200, 5, 400, 10, "s");
  setupSprite(wallTop);
  const wallBottom = new Sprite(200, 400 + 5, 400, 10, "s");
  setupSprite(wallBottom);
  
  const ballSpeed = 3;
  var ballDirection = Math.PI / 2;// rad
  const ball = new Sprite(200, 200, 16, 16);
  setupSprite(ball);
  ball.vel.x = ballSpeed * Math.cos(ballDirection);
  ball.vel.y = ballSpeed * Math.sin(ballDirection);
  
  
  const player = new Sprite(200, 360, 64, 16, "s")
  setupSprite(player);
  player.moveSpeed = 3;
  player.update = function() {
    if(keyIsDown(RIGHT_ARROW)) {
      player.x += player.moveSpeed;
    }
    if(keyIsDown(LEFT_ARROW)) {
      player.x += -player.moveSpeed;
    }
    
    blocks.forEach(block => {
      if(block.colliding(ball)) {
        block.remove();
      }
    })
    blocks.filter(block => !block.removed)
    
    if(wallBottom.colliding(ball)) {
      ball.remove();
    }
    
    if(player.colliding(ball)) {
      const diffX = ball.x - player.x;
      // -32 --> -150deg --> - PI * 3 / 4
      //  32 --> -30deg  --> - PI * 1 / 4
      const diffRate = (diffX + 32) / 64;
      ballDirection = - ((1.0 - diffRate) * Math.PI * 2 / 4 + Math.PI * 1 / 4);
      ball.vel.x = ballSpeed * Math.cos(ballDirection);
      ball.vel.y = ballSpeed * Math.sin(ballDirection);
    }
  }
  
  const blocks = [];
  for(let y = 0; y < 7; y++) {
    for(let x = 0; x < 7; x++) {
      const block = new Sprite(x * 32 + 104, y * 16 + 64, 32, 16, 's');
      setupSprite(block);
      blocks.push(block);
    }
  }
  
}

function setupSprite(sprite) {
  sprite.bounciness = 1;
  sprite.friction = 0;
  sprite.rotationLock = true;
}

function draw() {
  background(220);
}


この記事が気に入ったらサポートをしてみませんか?