魚の群れシミュレーション制作記 – 自然な動きと美しい見た目を目指して【JavaScript】

魚の群れシミュレーション制作記 - 自然な動きと美しい見た目を目指して【JavaScript】

魚の群れシミュレーション制作記 – 自然な動きと美しい見た目を目指して【JavaScript】

JavaScriptで魚の群れシミュレーション(Web水族館)を作った時に、特に意識したポイントや工夫した部分を振り返ってみます。技術的な部分から視覚的な表現まで、制作過程で学んだことをまとめました。

目次

自然な群れ行動を表現するために

3つの基本ルールの実装

魚の群れ行動を作る時、生物学の「ボイドモデル」を参考にしました。3つのシンプルなルールを組み合わせることで、自然な動きが生まれます。

1. 分離(Separation)- 近すぎる仲間から離れる

// 他の魚との距離が30ピクセル以内なら離れる
if (d < 30) {
    sep.x += this.pos.x - other.pos.x;
    sep.y += this.pos.y - other.pos.y;
}

2. 整列(Alignment)- 近くの仲間と同じ方向に泳ぐ

// 周囲40ピクセル以内の魚の速度を平均化
if (d > 0 && d < 40) {
    sum.add(other.vel);
}

3. 結合(Cohesion)- 仲間の中心に向かう

// 近くの魚の位置の中心点を目指す
if (d > 0 && d < 40) {
    sum.add(other.pos);
}

今回の実装で工夫したのは、各ルールの影響力を調整できるようにした点です。分離を1.5倍にして、魚同士がぶつからないようにしました。

捕食者回避の緊急性

サメが近づいた時の魚の反応にも力を入れました。

// サメから逃げる力を通常の3倍に設定
flee.mult(3);

通常の群れ行動よりも逃避行動を優先させることで、「危険を感じた時の慌てふためく様子」を表現しました。

美しい魚の形を描くために

シンプルな楕円から始める

最初は複雑な魚の形を描こうとしていましたが、シンプルな楕円から始めるのが正解でした。

// 魚の基本形状:楕円
ctx.ellipse(0, 0, this.size, this.size * 0.6, 0, 0, Math.PI * 2);

// 尻尾:三角形
ctx.moveTo(-this.size, 0);
ctx.lineTo(-this.size * 1.5, -this.size * 0.3);
ctx.lineTo(-this.size * 1.5, this.size * 0.3);

楕円の縦横比を0.6にしたのがポイントです。これで魚らしい(?)細長い体型になります。

色彩の工夫

魚の色にはHSL色空間を使いました。

this.color = 'hsl(' + random(180, 220) + ', 70%, 60%)';
  • 色相180-220: 青緑系の水中らしい色合い
  • 彩度70%: 鮮やかすぎず、自然な印象
  • 明度60%: 適度な明るさで視認性も確保

RGB値を直接指定するより、水中の生き物らしい統一感を意識。

滑らかな動きを実現するコツ

速度制限の重要性

最初につまづいたのは、魚が異常に速く泳いでしまうことでした。

// 速度制限をかける
var speed = Math.sqrt(this.vel.x * this.vel.x + this.vel.y * this.vel.y);
if (speed > 2) {
    this.vel.x = (this.vel.x / speed) * 2;
    this.vel.y = (this.vel.y / speed) * 2;
}

最大速度を2ピクセル/フレームに制限することで、ゆったりとした水中らしい動きになりました。

境界処理の自然さ

画面端での処理も工夫しました。壁で跳ね返るのではなく、反対側から現れるように。

// 画面端を超えたら反対側に移動
if (this.pos.x < 0) this.pos.x = canvas.width;
if (this.pos.x > canvas.width) this.pos.x = 0;

これで水槽ではなく、広い海の一部を覗いているような印象にしました。

臨場感を高める背景の工夫

グラデーション背景

水中の深度感を表現するため、縦方向のグラデーションを使いました。

var gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, 'rgb(0, 100, 150)'); // 上部:明るい青
gradient.addColorStop(1, 'rgb(0, 50, 100)');  // 下部:深い青

動く泡のエフェクト

静的な背景では物足りなかったので、ゆっくり上昇する泡を追加しました。

// 泡がゆっくり上昇する
var y = canvas.height - (frameCount * 0.5 + i * 80) % (canvas.height + 50);

frameCountを使って時間経過と共に位置を変更し、自然な泡の動きを表現しました。

パフォーマンスとの戦い

魚の数による負荷

最初は魚の数を増やすとカクカクになってしまいました。各魚が他のすべての魚との距離を計算するため、O(n²)の計算量になっていたからです。

現実的な解決策として

  • 最大500匹までに制限
  • 距離計算の最適化は行わず、シンプルな実装を維持

パフォーマンスと可読性のバランスを取りました。

制作を通じて学んだこと

シンプルさの力

複雑なアルゴリズムより、シンプルなルールの組み合わせが美しい結果を生むことを実感しました。魚の群れ行動も、3つの基本ルールだけで十分リアルに見えます。

数値調整の重要性

プログラムの9割はパラメータの微調整でした。速度、距離、影響力…すべての数値が最終的な見た目に大きく影響します。

ユーザー体験への配慮

技術的に面白いだけでなく、見ていて楽しい、触って楽しいものにするのが大切だと感じました。魚やサメの数を変更できるボタンも、そんな思いから追加しました。


このシミュレーション制作を通じて、プログラミングの技術面だけでなく、自然界の美しさやユーザー体験の大切さも学べました。皆さんも何か作る時の参考になれば嬉しいです。

Web水族館:魚っぽいやつとサメっぽいやつ【デジタル魚群】

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次