今年は各地で盛大な花火大会が行われています。コロナ後、比較的に自由な雰囲気の中で行われる花火大会を楽しみにしている方も多いことでしょう。しかし、忙しくて行けないとか、雨で中止になってしまうこともあります。そこで、AIを使ってブラウザに花火を打ち上げてみましょう。

  • Canvas APIを使って花火を描画しているところ

    Canvas APIを使って花火を描画しているところ

Canvas APIで花火を描画しよう

手軽に花火を描画するプログラムを作ろうと思ったら、JavaScriptを使ってブラウザ上に描画するのがオススメです。前回 紹介したように、Canvas APIを使うと、ブラウザ上に絵や図形を描画できます。しかも、スマートフォンのブラウザを含め、大半のブラウザで同じように動かせます。

今回も、このCanvas APIを使って花火を打ち上げるプログラムを作ります。なお、せっかく自分でプログラムを作るのですから、ぜひとも、自分でプログラムを改良して、思った色の花火にしたいと思うことでしょう。会話AIのChatGPTを活用して、花火をカスタマイズする方法も一緒に紹介します。

花火のプログラム

それでは、最初に花火をブラウザに描画するプログラムから見ていきましょう。次のようになります。90行以下なので、全てを目で追っても追い切れる長さになっています。なお、念のためプログラムの全体は、こちらのGistに配置してあります。

<!DOCTYPE html><html><head>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <body style="background-color: black;">
    <!-- 花火を描画するキャンバス -------(*1) -->
    <canvas id="fireworks" width="400" height="400"
      style="border: 1px solid silver;"></canvas>
  </body>
  <script>
    // HTMLの<canvas>要素を取得 --- (*2)
    const canvas = document.getElementById('fireworks');
    const ctx = canvas.getContext('2d');
    // 花火のパーティクルを格納する配列
    let fireworks = [];
    // パーティクルのクラス --- (*3)
    class Particle {
      constructor(x, y) { // 初期値を決定 --- (*4)
        this.x = x;
        this.y = y;
        this.color = 'red';
        this.radius = 2;
        this.speed = Math.random() * 3 + 1;
        this.angle = Math.random() * Math.PI * 2;
        this.vx = Math.cos(this.angle) * this.speed;
        this.vy = Math.sin(this.angle) * this.speed;
        this.gravity = 0.05;
        this.opacity = 1;
      }
      update(deltaTime) { // 位置や透明度を更新 --- (*5)
        this.x += this.vx * deltaTime;
        this.y += this.vy * deltaTime;
        this.vy += this.gravity * deltaTime;
        this.opacity -= 0.01 * deltaTime;
        this.radius -= 0.05 * deltaTime;
        // 半径が0未満にならないように制限
        if (this.radius < 0) { this.radius = 0; }
      }
      draw() { // Canvas APIを使って描画 --- (*6)
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
        ctx.fillStyle = this.color;
        ctx.globalAlpha = this.opacity;
        ctx.fill();
        ctx.closePath();
      }
    }
    let lastTime = 0;
    // フレームを更新する関数 --- (*7)
    function update() {
      // 経過時間を計算
      const time = performance.now();
      const deltaTime = (time - lastTime) / 100;
      lastTime = time;
      // キャンバスをクリア
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      // パーティクルを更新して描画 --- (*8)
      for (let i = 0; i < fireworks.length; i++) {
        fireworks[i].update(deltaTime);
        fireworks[i].draw();
        // 無効なパーティクルを削除 --- (*9)
        if (fireworks[i].opacity <= 0 || fireworks[i].radius <= 0) {
          fireworks.splice(i, 1);
          i--;
        }
      }
      // 次のフレームを再描画 --- (*10)
      requestAnimationFrame(update);
    }
    // 花火を(cx, cy)に打ち上げる --- (*11)
    function launchFireworks(cx, cy) {
      const colors = ['orange', 'red', 'yellow'];
      for (let i = 0; i < 100; i++) { // 100個のパーティクル
        const particle = new Particle(cx, cy);
        particle.color = colors[Math.floor(Math.random() * colors.length)];
        particle.radius = Math.random() * 2 + 1;
        fireworks.push(particle);
      }
    }
    // 初期化して花火を打ち上げる --- (*12)
    lastTime = performance.now();
    update();
    setInterval(() => {
      const cx = Math.random() * canvas.width;
      const cy = Math.random() * canvas.height;
      launchFireworks(cx, cy);
    }, 300); // 打ち上げ間隔を指定 --- (*13)
  </script>
</html>

上記のHTML/JavaScriptのコードをメモ帳などに貼り付けて、「fireworks.html」という名前で保存しましょう。そして、ブラウザを起動して、HTMLファイルをブラウザにドラッグ&ドロップします。すると、花火が打ち上がります。

  • ブラウザにHTMLファイルをドラッグ&ドロップしたところ

    ブラウザにHTMLファイルをドラッグ&ドロップしたところ

プログラムを確認してみましょう。(*1)ではHTMLのコードで、キャンバスを幅400、高さ400のサイズで作成します。なお、外枠が分かりやすいようにstyle属性で灰色の線を描画しています。このwidth属性とheight属性の値を大きくすると、大きな画面に花火が打ち上がります。 また、id属性にfireworksを指定している点に留意してください。

そして、(*2)以降では、JavaScriptのプログラムを記述します。上記(*1)のキャンバスにはfireworksというid属性を指定したので、getElementByIdメソッドを使って、キャンバスのオブジェクトを取得して変数canvasに代入します。そして、描画用のオブジェクトからgetContextメソッドを使って取得し、変数ctxに代入します。

(*3)では花火の火の1つを表すParticle(粒子)というクラスを定義します。簡単に言うと、クラスはオブジェクトの設計図です。クラスはオブジェクトのひな形とも言うべき存在で、実際にオブジェクトを生成してから利用します。ここで定義したParticleクラスは後述の(*11)で花火を描画するオブジェクトとして生成して利用します。

(*4)ではParticleの各プロパティを初期化します。(*5)では与えられた引数deltaTimeに合わせてParticleの位置や透明度を計算して更新する処理を記述します。(*6)ではキャンバスに火花を描画します。

それから、(*7)では、ブラウザの描画イベントに応じて繰り返し呼び出されるupdate関数を定義します。なお、ブラウザは実行機種によって描画速度が異なるため、performance.nowメソッドを利用して、経過時間を計測して、どの機種でも同じ速度で描画されるようにタイミングを調整します。(*8)ではパーティクルの位置を調整して描画を行います。(*9)では、既に消えた火花のオブジェクトを削除します。そして、(*10)では、次のブラウザの描画タイミングでも、update関数を実行するように指定します。

(*11)では座標(cx, cy)に花火を打ち上げます。具体的には、100個のパーティクルを作って、動かすように指定します。

(*12)では、タイマーを初期化して、300ミリ秒ごとに花火を打ち上げるlaunchFireworks関数を呼び出します。(*13)では打ち上げ間隔をミリ秒で指定します。

AIを使って花火をカスタマイズしよう

プログラムのだいたいの造りが分かったら、次に、花火をカスタマイズしましょう。今回、具体的に花火の打ち上げ処理を行っているのでは、プログラムの(*11)にあるlaunchFireworks関数です。この関数とどのように改良するのかの2点をChatGPTに与えてみましょう。すると、思ったよりも良い感じに関数をカスタマイズしてくれます。

例えば、次のようなプロンプト(入力文)をChatGPTに与えてみましょう。

以下は花火を打ち上げるJavaScriptのプログラムです。
もっと、さわやかな感じに修正してください。

```
    // 花火を(cx, cy)に打ち上げる
    function launchFireworks(cx, cy) {
      const colors = ['orange', 'red', 'yellow'];
      for (let i = 0; i < 100; i++) { // 100個のパーティクル
        const particle = new Particle(cx, cy);
        particle.color = colors[Math.floor(Math.random() * colors.length)];
        particle.radius = Math.random() * 2 + 1;
        fireworks.push(particle);
      }
    }
```

すると、次のようにプログラムを修正してくれます。なお、ChatGPTは毎回異なる回答を返すため、必ず以下と同じになるわけではありませんが、似たようなプログラムを生成してくれることでしょう。

// ChatGPTが改良した花火を打ち上げる関数

この記事は
Members+会員の方のみ御覧いただけます

ログイン/無料会員登録

会員サービスの詳細はこちら