AI (o3-mini-high) が一発ノーミスで実現！ 〜美しい日本の打ち上げ花火の表現〜

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82835/9d493c71-de9c-3a23-dfe1-0c53abaeb6f3.png)

先日、「**ChatGPT o3-mini-high**」 を用いて、以下のプロンプトに基づいた打ち上げ花火のプログラムを作成しました。  
**使用したプロンプトは記事末尾に記載**してありますが、冠・冠菊、牡丹、菊の3種類の花火を、画面上で順番に打ち上げ、爆発後に美しい残像を残す表現を実現しています。

特筆すべきは、複雑なパーティクルの挙動や残像表現などを、瞬時にミスなく高いクオリティで実装できた点です。  
実際、コードは **一発ノーミス** で完成し、まるで実際の夜空に咲く花火のような映像を p5.js の力で表現できています。

また、作成したコードは [p5.js エディタ](https://editor.p5js.org/) 上でそのまま実行可能です。  
下記のコードをコピーして、エディタに貼り付けるだけで、素晴らしい花火演出が楽しめます！

---

## プログラムの概要

- **打ち上げ花火の種類**  
  1. **冠・冠菊（かむろ・かむろぎく）**  
     - 金色を基調とし、長い尾が垂れるような軌跡と、（冠菊の場合は）中心からの放射状の輝きを表現  
  2. **牡丹（ぼたん）**  
     - 球状に広がる爆発と、赤・緑・青などの明るい色が柔らかく混ざり合う華やかな演出  
  3. **菊（きく）**  
     - 中心から放射状に伸びる光の枝を、グラデーション効果と残像を伴って表現

- **アニメーションの流れ**  
  画面下部から花火のロケットが打ち上げられ、上空で各種パーティクル群へと炸裂。  
  毎フレームの背景描画により、前フレームの残像が徐々に消えていく余韻を美しく表現しています。

---

## コード

以下が、ChatGPT o3-mini-high によって作成されたソースコードです。

```js
// グローバル変数
let fireworks = [];
let particles = [];
let gravity;

let nextFireworkTime = 0;
let fireworkTypeIndex = 0;
let fireworkTypes = ["kamuro", "botan", "kiku"]; // 順番に：冠・冠菊、牡丹、菊

function setup() {
  createCanvas(windowWidth, windowHeight);
  // RGBA モード。アルファ値は 0〜255
  colorMode(RGB, 255, 255, 255, 255);
  gravity = createVector(0, 0.2);
  background(0);
}

function draw() {
  // 背景を半透明の黒で塗りつつ、残像効果を演出
  background(0, 0, 0, 50);

  // 一定間隔で新たなロケット（花火）を打ち上げる
  if (millis() > nextFireworkTime) {
    let type = fireworkTypes[fireworkTypeIndex];
    fireworkTypeIndex = (fireworkTypeIndex + 1) % fireworkTypes.length;
    fireworks.push(new Firework(type));
    nextFireworkTime = millis() + random(800, 1500);
  }

  // ロケットの更新・描画
  for (let i = fireworks.length - 1; i >= 0; i--) {
    fireworks[i].update();
    fireworks[i].show();
    if (fireworks[i].exploded) {
      fireworks.splice(i, 1);
    }
  }

  // パーティクルの更新・描画
  for (let i = particles.length - 1; i >= 0; i--) {
    particles[i].update();
    particles[i].show();
    if (particles[i].isFinished()) {
      particles.splice(i, 1);
    }
  }
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

/* ----- Firework（ロケット）クラス ----- */
class Firework {
  constructor(type) {
    this.type = type; // "kamuro", "botan", "kiku"
    // 画面下部の 20～80% の横位置から打ち上げる
    this.pos = createVector(random(width * 0.2, width * 0.8), height);
    // 上向きの初速（打ち上げ速度）
    this.vel = createVector(0, random(-10, -15));
    this.exploded = false;
  }

  update() {
    if (!this.exploded) {
      this.vel.add(gravity);
      this.pos.add(this.vel);
      // 速度がゼロに近づいたら上空到達とし、爆発させる
      if (this.vel.y >= 0) {
        this.explode();
        this.exploded = true;
      }
    }
  }

  show() {
    if (!this.exploded) {
      stroke(255);
      strokeWeight(4);
      point(this.pos.x, this.pos.y);
    }
  }

  explode() {
    if (this.type === "kamuro") {
      // 【冠・冠菊】
      // ここでは 50% の確率で「冠」か「冠菊」を表現（冠菊は中心からの放射状の強い輝きを追加）
      let variant = random() < 0.5 ? "kamuro" : "kamuro_giku";
      // 長い尾を再現するための粒子を生成（尾の演出）
      for (let i = 0; i < 50; i++) {
        particles.push(new KamuroParticle(this.pos.copy(), variant));
      }
      // 冠菊の場合、中心から放射状に輝くスパークを追加
      if (variant === "kamuro_giku") {
        for (let i = 0; i < 30; i++) {
          particles.push(new KamuroSpark(this.pos.copy()));
        }
      }
    } else if (this.type === "botan") {
      // 【牡丹】
      // 球状に広がる明るい花火。色は赤・緑・青などをランダムに混ぜる
      for (let i = 0; i < 100; i++) {
        particles.push(new BotanParticle(this.pos.copy()));
      }
    } else if (this.type === "kiku") {
      // 【菊】
      // 中心から放射状に長い光の枝（ビーム）を生成。全体としてグラデーションの効果あり
      let numBeams = 20;
      for (let i = 0; i < numBeams; i++) {
        particles.push(new KikuParticle(this.pos.copy(), i, numBeams));
      }
    }
  }
}

/* ----- 【冠・冠菊】 用パーティクルクラス ----- */
class KamuroParticle {
  constructor(pos, variant) {
    this.pos = pos;
    // ほぼ全方向に拡散させつつも、初速は控えめ
    let angle = random(TWO_PI);
    let speed = random(1, 3);
    this.vel = p5.Vector.fromAngle(angle).mult(speed);
    this.lifespan = 255;
    // 軌跡を記録して、長い尾（残像）を表現
    this.trail = [];
    this.variant = variant; // "kamuro" または "kamuro_giku"
  }

  update() {
    // 軌跡配列に現在位置を追加（最新10点程度保持）
    this.trail.push(this.pos.copy());
    if (this.trail.length > 10) {
      this.trail.splice(0, 1);
    }
    // 重力の一部を加えることで、下向きに垂れる効果
    this.vel.add(p5.Vector.mult(gravity, 0.5));
    this.pos.add(this.vel);
    this.lifespan -= 2;
  }

  show() {
    noFill();
    let alpha = this.lifespan;
    // 金色（ゴールド）の輝き。尾は線で描画
    stroke(255, 215, 0, alpha);
    strokeWeight(2);
    beginShape();
    for (let v of this.trail) {
      vertex(v.x, v.y);
    }
    endShape();
  }

  isFinished() {
    return this.lifespan < 0;
  }
}

// 【冠菊】向け追加パーティクル：中心から放射状に強い輝きを演出
class KamuroSpark {
  constructor(pos) {
    this.pos = pos;
    // 高速で拡散
    let angle = random(TWO_PI);
    let speed = random(2, 5);
    this.vel = p5.Vector.fromAngle(angle).mult(speed);
    this.lifespan = 255;
    this.trail = [];
  }

  update() {
    this.trail.push(this.pos.copy());
    if (this.trail.length > 5) {
      this.trail.splice(0, 1);
    }
    // やや軽めの重力効果
    this.vel.add(p5.Vector.mult(gravity, 0.3));
    this.pos.add(this.vel);
    this.lifespan -= 4;
  }

  show() {
    noFill();
    let alpha = this.lifespan;
    // 輝く金色に近い色で描画
    stroke(255, 223, 0, alpha);
    strokeWeight(2);
    beginShape();
    for (let v of this.trail) {
      vertex(v.x, v.y);
    }
    endShape();
  }

  isFinished() {
    return this.lifespan < 0;
  }
}

/* ----- 【牡丹】 用パーティクルクラス ----- */
class BotanParticle {
  constructor(pos) {
    this.pos = pos;
    // 球状に広がるため、全方向に初速を与える
    let angle = random(TWO_PI);
    let speed = random(2, 6);
    this.vel = p5.Vector.fromAngle(angle).mult(speed);
    this.acc = createVector(0, 0.05); // やや重力の影響
    this.lifespan = 255;
    // 明るく華やかな色。各粒子ごとにランダムな色を設定
    this.col = color(random(150, 255), random(150, 255), random(150, 255));
  }

  update() {
    this.vel.add(this.acc);
    this.pos.add(this.vel);
    this.lifespan -= 4;
  }

  show() {
    noStroke();
    fill(red(this.col), green(this.col), blue(this.col), this.lifespan);
    ellipse(this.pos.x, this.pos.y, 4);
  }

  isFinished() {
    return this.lifespan < 0;
  }
}

/* ----- 【菊】 用パーティクル（ビーム）クラス ----- */
class KikuParticle {
  // 引数 index と total を用いて、放射状に均等な角度を割り当てる
  constructor(pos, index, total) {
    this.startPos = pos.copy();
    this.pos = pos.copy();
    let baseAngle = map(index, 0, total, 0, TWO_PI);
    // 微妙な角度の揺らぎを追加
    let offset = random(-PI / 16, PI / 16);
    this.angle = baseAngle + offset;
    this.speed = random(3, 6);
    this.vel = p5.Vector.fromAngle(this.angle).mult(this.speed);
    this.lifespan = 255;
    // ビームの色は、始点は鮮やかで、先端に向かって消えていくグラデーションを演出
    this.startColor = color(random(100, 255), random(100, 255), random(100, 255));
    this.endColor = color(0);
    this.trail = [this.startPos.copy()];
  }

  update() {
    this.pos.add(this.vel);
    this.trail.push(this.pos.copy());
    if (this.trail.length > 20) {
      this.trail.splice(0, 1);
    }
    this.lifespan -= 3;
  }

  show() {
    // 軌跡の各セグメントを、先端に向かって色が変化するように描画
    for (let i = 0; i < this.trail.length - 1; i++) {
      let t = map(i, 0, this.trail.length - 1, 0, 1);
      let c = lerpColor(this.startColor, this.endColor, t);
      stroke(red(c), green(c), blue(c), this.lifespan);
      strokeWeight(3);
      line(this.trail[i].x, this.trail[i].y, this.trail[i + 1].x, this.trail[i + 1].y);
    }
  }

  isFinished() {
    return this.lifespan < 0;
  }
}
```

## 実行方法

1. [p5.js エディタ](https://editor.p5js.org/) を開き、新規スケッチを作成します。  
2. 上記のコードをエディタに貼り付け、**実行ボタン** をクリックするだけで、夜空に美しい花火が次々と打ち上がる演出をお楽しみいただけます。

#### 花火の数を増やすアプローチ

現状のコードでは、花火の打ち上げタイミングは以下のように設定されています。

```js
nextFireworkTime = millis() + random(800, 1500);
```
花火の数を増やすためのアプローチは主に2点あります：

1. 打ち上げ間隔の短縮
上記の数値を ```random(200, 500)``` のように短く設定することで、より頻繁に花火を打ち上げることが可能です。

2. 同時打ち上げの実施
1フレーム内で複数の花火を生成する処理を追加する方法です。
例えば、以下のようにループを使って複数回 ```fireworks.push(new Firework(type));``` を実行すれば、同時に複数のロケットが打ち上がります。

また、パーティクルの数を増やすことで、爆発後の演出の密度を高めることも可能です。
ぜひ、お好みに合わせてパラメータを調整してみてください。


---

## 使用したプロンプト

以下は、今回のプログラム作成に際して使用したプロンプトの全文です。

```
p5.js を使用して、日本の打ち上げ花火を再現するプログラムを作成してください。

以下の三種類の花火を順番に打ち上げ、爆発後に美しく消えていく演出を実装してください。
1. 冠（かむろ）・冠菊（かむろぎく）
2. 牡丹（ぼたん）
3. 菊（きく）

#### 表現上の要件
1. **冠・冠菊（かむろ・かむろぎく）**
   - 金色を基調とした、尾が長く垂れるような光の軌跡を表現してください。
   - 一定時間経過後、光が徐々に消えつつも、余韻としてかすかに残るような演出にしてください。
   - 冠菊の場合は、金色の尾に加えて中心から放射状にやや強い輝きが広がるように見せてください。

2. **牡丹（ぼたん）**
   - 打ち上がった後、球状に大きく広がるように表現してください。
   - 発色は明るめで、赤・緑・青など複数色が柔らかく混ざり合うようにして、華やかさを際立たせてください。
   - 爆発の瞬間にインパクトが出るよう、発色のタイミングを工夫してください。

3. **菊（きく）**
   - 中心から放射状に長い光の筋（枝）が伸びていくような形を再現してください。
   - 多色を使いつつも、全体としてまとまりのあるグラデーションを意識してください。
   - 爆発後は、ゆっくりと消えていく光の軌跡に残像があるように見せ、余韻を演出してください。

#### アニメーションの流れ
- 画面下部から花火が打ち上がるように描写するか、または画面中央で打ち上がっている状態からスタートしてもかまいません。
- 花火が空中で炸裂し、各種類ごとに異なる形状・発色・消え方を実装してください。
- 連続で複数の花火が打ち上がるようにし、夜空全体が華やかになるような演出を目指してください。

#### 実装上のヒント・注意点
- **ランダム性**: 打ち上げる位置やタイミング、色合いなどにランダム性を加えることで、花火らしい自然な変化を演出できます。
- **速度・消滅**: 粒子（パーティクル）の速度やアルファ値の減衰を調整して、リアルな消え方を実現してください。
- **色彩・輝度**: 花火の種類ごとに色や輝度の変化を工夫し、夜空に映えるようにしてください。
- **背景描画**: 毎フレーム背景を塗りつぶすタイミングやアルファ値を調整して、残像効果や余韻を表現してください。
- **フルスクリーン**: 必要に応じて createCanvas(windowWidth, windowHeight) を使い、全画面を使ったダイナミックな演出に挑戦してもかまいません。

以上の要件を満たしながら、クリエイティブで美しい日本の打ち上げ花火を p5.js で再現してください。

```

## まとめ
今回のプロジェクトでは、ChatGPT o3-mini-high によって瞬時に、しかも一発ノーミスで高品質なコードが生成されました。
複雑なパーティクルの挙動や残像の表現、そして各花火の個性が際立つ演出など、細部にわたるアニメーションがAIの力で完璧に実装されている点は驚嘆すべきです。

p5.js の表現力と AI のコード生成クオリティの融合により、まるで実際の夜空に咲く花火のような美しい映像が実現できることを実感しました。
ぜひ、皆さんもこのコードを試して、自分だけのアレンジで素晴らしい花火演出を楽しんでください！

---
このように、AIの力を借りることで開発効率が飛躍的に向上し、複雑な表現も一発ノーミスで実現できる可能性を改めて感じさせられます。