# Boid 三條規則：分離、對齊、聚合

正式網址：https://newmediaart.course.monolab.world/snippets/boid-rules

一個 flock(me, others, radius) 函式，回傳分離、對齊、聚合三條規則合成的力；粒子模板的 p／v 或 Boid 的 pos／vel 都認得，在粒子模板的 update() 加一行就變鳥群，範例用最小的 class 跑 60 隻。

- **分類**：自然的演算法
- **行數**：70 行（p5.js 1.x／2.x）
- **在哪一堂用到**：[第 3 堂](https://newmediaart.course.monolab.world/lesson/03)

把最底下的 flock() 複製進自己的 sketch；粒子模板（Hahow Week 3-0）在 update() 的規則區加一行 this.a = flock(this, particles, 60); 就變鳥群，不用另外寫 class。

```js
// Boid：60 隻，每一格先算好所有人的力、再一起更新
let boids = [];

function setup() {
  createCanvas(640, 420);
  for (let i = 0; i < 60; i++) boids.push(new Boid(random(width), random(height)));
}

function draw() {
  background(18, 25, 40);
  // 先讓每一隻算完力，全部算完再更新位置，順序才不會讓前面的人佔便宜
  for (let b of boids) b.steerWith(boids);
  for (let b of boids) { b.update(); b.draw(); }
}

class Boid {
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.vel = p5.Vector.random2D().mult(2);
    this.acc = createVector(0, 0);
  }
  steerWith(all) {
    this.acc = flock(this, all, 60); // 呼叫底下那個 flock() 函式
  }
  update() {
    this.vel.add(this.acc).limit(2.3);
    this.pos.add(this.vel);
    this.pos.x = (this.pos.x + width) % width; // 穿過邊界從另一邊出來
    this.pos.y = (this.pos.y + height) % height;
  }
  draw() {
    push();
    translate(this.pos.x, this.pos.y);
    rotate(this.vel.heading());
    noStroke();
    fill(100, 205, 245);
    triangle(8, 0, -5, -4, -5, 4);
    pop();
  }
}

// 三條規則合成的力。me 要有 p、v（粒子模板）或 pos、vel；others 是大家；radius 看得到多遠的鄰居
function flock(me, others, radius) {
  const maxSpeed = 2.3, maxForce = 0.08;
  const P = (b) => b.p || b.pos, V = (b) => b.v || b.vel; // 粒子模板用 p／v，這裡的 Boid 用 pos／vel，兩種都認得
  let sep = createVector(0, 0), ali = createVector(0, 0), coh = createVector(0, 0);
  let near = 0, close = 0;
  for (let o of others) {
    if (o === me) continue;
    let d = p5.Vector.dist(P(me), P(o));
    if (d > 0 && d < radius) {
      ali.add(V(o)); // 對齊：加總鄰居的速度
      coh.add(P(o)); // 聚合：加總鄰居的位置
      near++;
      if (d < radius * 0.4) { sep.add(p5.Vector.sub(P(me), P(o)).div(d * d)); close++; } // 分離：太近的推開
    }
  }
  // 想去的方向 → 轉向的力：想要的速度減掉現在的速度，再限制力道
  function steer(want) {
    if (want.magSq() === 0) return createVector(0, 0);
    return want.setMag(maxSpeed).sub(V(me)).limit(maxForce);
  }
  let force = createVector(0, 0);
  if (close > 0) force.add(steer(sep.div(close)).mult(1.5));
  if (near > 0) {
    force.add(steer(ali.div(near))); // 往鄰居平均的方向走
    force.add(steer(coh.div(near).sub(P(me)))); // 往鄰居的中心靠
  }
  return force.limit(maxForce * 2.5);
}
```

## 同類常用指令・自然的演算法

- [海龜繪圖：照字串畫線](https://newmediaart.course.monolab.world/snippets/turtle-draw)
- [L-System：規則換字長出一棵樹](https://newmediaart.course.monolab.world/snippets/lsystem-grow)
- [遞迴樹：branch 自己呼叫自己](https://newmediaart.course.monolab.world/snippets/recursive-branch)
- [Noise 年輪：接得起來的不規則圈](https://newmediaart.course.monolab.world/snippets/noise-ring)

[回到全部常用指令](https://newmediaart.course.monolab.world/snippets)

[到 Hahow 報名](https://hahow.in/cr/creative-code)。價格與優惠以 Hahow 頁面為準。
