魚の群れシミュレーション制作記 – 自然な動きと美しい見た目を目指して【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割はパラメータの微調整でした。速度、距離、影響力…すべての数値が最終的な見た目に大きく影響します。
ユーザー体験への配慮
技術的に面白いだけでなく、見ていて楽しい、触って楽しいものにするのが大切だと感じました。魚やサメの数を変更できるボタンも、そんな思いから追加しました。
このシミュレーション制作を通じて、プログラミングの技術面だけでなく、自然界の美しさやユーザー体験の大切さも学べました。皆さんも何か作る時の参考になれば嬉しいです。
コメント