▍AI Dev Lab
FlockDrift

FlockDrift ができるまで — Boids の 3 規則で群れを作る

群れのシミュレーターの開発ログ。 分離・整列・結合の 3 規則だけで鳥や魚の群れが生まれる Boids を実装し、 捕食者からの逃避を足して ムクドリ・魚群・蛍・蚊柱 の 5 プリセットに仕上げるまでを記録しています。

FlockDrift は、鳥や魚の群れを再現するシミュレーターです。中心となる指令役はいません。個体が近くの仲間だけを見て動く 3 つの規則から、全体のうねりが勝手に生まれます。Craig Reynolds が 1986 年に発表した Boids の実装です。

#🎯 群れには「群れを作る意思」がない

ムクドリの大群がうねる映像を見ると、誰かが全体を指揮しているように見えます。でも実際には、鳥は近くの数羽しか見ていません。

Boids はそれを 3 つの規則で表します。

  1. 分離(Separation) — 近すぎる仲間から離れる
  2. 整列(Alignment) — 近くの仲間と同じ方向に揃える
  3. 結合(Cohesion) — 近くの仲間の中心へ寄る

全体を見る処理はどこにもありません。 それでも渦を巻き、分裂し、また合流します。作っていて一番驚いたのはここで、規則を書き終えて動かした瞬間にもう群れになっていました。

#🏗️ 3 規則を 1 ループで書く

各個体について、視界内の仲間を集めながら 3 つの合計を同時に作ります。

ts
for (let j = 0; j < boids.length; j++) {
  if (i === j) continue
  const dx = o.x - b.x, dy = o.y - b.y
  const d2 = dx * dx + dy * dy
  if (d2 > perc2 || d2 === 0) continue
  n++
  aliX += o.vx; aliY += o.vy      // 整列用: 速度の合計

  cohX += o.x;  cohY += o.y       // 結合用: 位置の合計

  if (d2 < sep2) {
    sepX -= dx / d2               // 分離用: 近いほど強く反発

    sepY -= dy / d2
  }
}

3 つの規則で別々にループを回さないのがポイントです。視界判定は距離計算が必要で、そこが一番重い。1 回の走査で 3 つぶんの材料を集めれば、計算量が 1/3 になります。

距離は二乗のまま比較しています。Math.sqrt は視界内と分かった相手にしか使いません。個体数 N に対して N² 回まわるループなので、平方根を省くだけで体感が変わります。

#⚖️ 分離だけ「距離で割る」

3 規則のうち、分離だけ扱いが違います。

ts
sepX -= dx / d2

距離の二乗で割っているので、近いほど反発が急激に強くなります。整列と結合は視界内なら一律に平均を取るのに対し、分離だけは至近距離で強く効く。

これが無いと個体が同じ点に潰れます。逆に分離を線形にすると、今度は群れがほどけて散ります。衝突回避だけは非線形、というのが Boids の効きどころでした。

#🎚️ 係数の大きさが 3 桁違う

3 つの規則に掛ける係数を見ると、桁がばらばらです。

ts
b.vx += ((aliX / n) - b.vx) * 0.05   * p.alignment   // 整列

b.vx += ((cohX / n) - b.x)  * 0.0008 * p.cohesion    // 結合

b.vx += sepX                * 0.9    * p.separation  // 分離

理由は掛かる値の単位が違うからです。

  • 整列は 速度の差(数 px/frame)
  • 結合は 位置の差(数百 px)
  • 分離は 1/距離 の合計(小さい値)

そのままだと結合だけが極端に強くなります。だから結合には 0.0008、分離には 0.9 と、値域を揃えるための係数が入ります。ユーザーに見せるスライダー(0〜1)とは別に、内部の正規化係数を持つ構造にしました。

この分離があると、スライダーをどう動かしても群れが破綻しません。つまみを触って壊れるシミュレーターは、この正規化をサボっている場合が多いです。

#🦅 捕食者を入れると群れが「割れる」

マウスに追従する捕食者を足しました。個体は捕食者から一定距離内に入ると、反対方向へ強く加速します。

ts
const flee2 = (p.perception * 2.2) * (p.perception * 2.2)
if (d2 < flee2 && d2 > 0) {
  const d = Math.sqrt(d2)
  b.vx += (dx / d) * 0.9
  b.vy += (dy / d) * 0.9
}

逃避の視界を通常の2.2 倍にしているのがポイントで、仲間より捕食者を先に見つけないと逃げ遅れます。

これを入れると、群れが避けて割れ、通過後にまた合流する ── 実際の鳥の群れで見る動きが出ます。規則を 1 つ足しただけで、行動のレパートリーが一段増えるのが面白いところでした。

#🐟 プリセットで「何の群れか」を変える

ムクドリ・魚群・蛍・蚊柱の 4 つは、パラメータの組み合わせだけで作り分けています。

プリセット特徴
ムクドリ視界が広く、整列が強い。 大きなうねり
魚群分離が強く、密度が高い。 急な方向転換
蛍速度が遅く、ゆらぎが大きい。 ばらけて漂う
蚊柱結合が非常に強く、狭い範囲に密集

同じ 3 規則でも、係数の比率だけで生き物が変わります。

#😓 苦労したところ

画面端の扱い。 端で反転させると不自然に跳ね返り、ラップ(反対側から出す)にすると群れが分断されます。最終的に、端から一定距離(margin 40px)に入ったら中央へ向かう力を弱く足す方式にしました。壁ではなく「戻りたくなる領域」を作る発想です。

個体数と速度の両立。 O(N²) なので数を増やすと落ちます。視界内かどうかの判定を最初に置いて早期 continue し、平方根を避け、1 ループにまとめた ── この 3 つで数百体まで 60fps を保てました。空間分割は入れていません。

「群れているか」の判定基準がない。 物理シミュレーションと違って、正解の数値がありません。エネルギー保存のような検算ができないので、見て判断するしかない題材でした。だからこそプリセットを用意して、「この設定ならこう見える」という基準点を置いています。

#🔭 今後の拡張

  • 空間分割(グリッドや四分木)で数千体
  • 障害物を置いて避けさせる
  • 3 次元化
  • 個体ごとに視野角を持たせる(現在は全方位。 実際の鳥は後ろが見えない)

#💡 このサービスから言えること

創発を扱うプログラムは、規則の少なさそのものが価値です。 全体を制御するコードを 1 行も書かずに群れが出る、という事実が面白い。制御を足したくなったら、それは Boids ではない別のものになります。

実装面では、単位の違う量を足し合わせるときの正規化が全部でした。3 規則の係数が 0.0008 から 0.9 まで散らばっているのは行儀が悪く見えますが、そこを揃えたからスライダーで壊れない挙動になっています。

[ ./next_action ]

読んだら、 FlockDrift を実際に動かす。

この開発ログは FlockDrift をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ShadowTwin

立体パズルの別解を作る:同じ影と最少の積み木

ShadowTwinの開発記録。3方向の図から別々の最少配置を2つ探す仕組み、5問の独立全探索、スマホの横並び比較、保存・再編集と入力検査を解説します。実照明の計算や能力診断ではありません。

read log →
SlitCard

スリットアニメーションを作る:4コマの合成と透明原稿の設計

SlitCardの開発記録。4コマの帯を交互に合成し、すき間と黒帯で別のコマを見せる仕組みを解説。同縮尺の台紙・透明シートSVG、50mm確認線、手動プレビュー・初期非表示、再編集と実出力検査を整理します。

read log →
WindowLight

日当たりシミュレーションを作る:窓の光を床へ投影する幾何モデル

WindowLightの開発記録。太陽位置近似から窓の4隅を床へ投影し、凸多角形を部屋で切り取る設計を解説。国立天文台の16時刻との限定照合、大気差・低高度・実際の採光との違い、SVGと設定の再編集を整理します。

read log →
GridLights

電源構成の比較を作る:割合と実量を100個の電球で読み解く

電源構成比較GridLightsの開発記録。12か国の年次発電データを最大剰余法で100個の電球に変換し、割合とTWhを比較する設計を解説。欠損を0補完しない7分類、予想と実値、出典付きSVGと実データ検証を整理します。

read log →
SoundLoom

音のお絵かきを作る:実WAVから絵を測り直す設計

音のお絵かきの開発記録。描画を正弦波のPCMとWAVへ変換し、保存音声の周波数をGoertzel法で測って比較する仕組みを解説。音量・測定窓・量子化の限界と、編集・離脱時の古い結果を抑止する検証を整理します。

read log →
MazeLoom

宝探し迷路を作る:寄り道まで含めた最短ルートの探索

宝探し迷路の開発記録。通路を木として生成し、現在地と3つの宝の取得状態を組み合わせて最短ルートを探す仕組みを解説。問題コードの版固定、画面と解答付きPDFの一致、手数と印刷の検証を整理します。

read log →