에듀테크 프로젝트 대시보드
보이드 무리 짓기 (Boids)
한 마리 한 마리는 딱 3가지 규칙만 본다. 리더도, 전체 계획도 없다. 그런데 화면 전체에서는 살아 있는 새 떼처럼 보인다 — 이것이 창발(emergence)이다.
각 보이드는 자기 주변 지각 반경(perception radius) 안에 있는 이웃만 본다. 그 이웃들을 보고 세 방향의 조향력(steering force)을 계산해 더한다.
분리 Separation
너무 가까운 이웃에게서 멀어진다. 충돌·뭉침 방지.
정렬 Alignment
이웃들의 평균 속도(방향)에 내 방향을 맞춘다. 같이 흐른다.
응집 Cohesion
이웃들의 무게중심으로 향한다. 무리에서 낙오하지 않는다.
회색 원 = 지각 반경, 흰 보이드 = 나, 파란 보이드 = 이웃, 화살표 = 그 규칙이 만드는 조향 방향
세 규칙의 가중치를 바꿔 보라. 분리만 크면 서로 밀어내 흩어지고, 응집만 크면 한 점으로 뭉친다. 셋이 균형을 이룰 때 비로소 "떼"가 된다.
가장자리는 반대편으로 이어진다(토러스). 매를 켜면 무리가 갈라지며 도망치는 회피 물결이 보인다.
이웃마다 거리를 재므로 소박하게 짜면 O(n²)다. 개체가 많아지면
공간 격자나 쿼드트리로 "근처 칸"만 검사해 O(n)에 가깝게 줄인다.
function flock(b, boids, r, wSep, wAli, wCoh, maxSpeed, maxForce) {
let sep = {x:0,y:0}, ali = {x:0,y:0}, coh = {x:0,y:0}, n = 0;
for (const o of boids) {
if (o === b) continue;
const dx = b.x - o.x, dy = b.y - o.y;
const d = Math.hypot(dx, dy);
if (d > 0 && d < r) {
sep.x += dx / (d*d); sep.y += dy / (d*d); // 가까울수록 강하게 밀어냄
ali.x += o.vx; ali.y += o.vy; // 이웃 속도 합
coh.x += o.x; coh.y += o.y; // 이웃 위치 합
n++;
}
}
if (n === 0) return;
ali.x /= n; ali.y /= n; // 평균 속도
coh.x = coh.x/n - b.x; coh.y = coh.y/n - b.y; // 무게중심으로의 방향
const acc = {
x: wSep*unit(sep).x + wAli*unit(ali).x + wCoh*unit(coh).x,
y: wSep*unit(sep).y + wAli*unit(ali).y + wCoh*unit(coh).y
};
b.vx = clampMag(b.vx + acc.x, maxSpeed);
b.vy = clampMag(b.vy + acc.y, maxSpeed);
b.x += b.vx; b.y += b.vy;
}
unit()는 벡터를 길이 1로, clampMag()는 길이 상한을 씌운다.
실제 구현은 각 규칙에도 maxForce로 조향력 상한을 둬 급회전을 막는다.
| 추가 규칙 | 효과 |
|---|---|
| 목표 추종 (seek) | 먹이·이동 경로 등 특정 지점으로 무리를 유도 |
| 장애물 회피 | 앞쪽 벽·바위를 감지해 미리 옆으로 틈 |
| 포식자 회피 (flee) | 매·상어가 지각 반경에 들어오면 강한 반대 방향 힘 |
| 시야각 제한 | 뒤쪽 이웃은 무시 → 더 자연스러운 흐름 |
| 경계 처리 | 벽에서 튕김 / 화면을 감싸는 토러스 / 중앙으로 살짝 당김 |