はじめに
この記事はGoodpatch Advent Calendar2日目の記事です。
今日は12/18ですが、2日目の記事を書く人が色々あって急遽書けなくなってしまったので、僕が代打で書く事にしました。
今回はGoodpatchのエンジニアが隔週で行なっている社内勉強会(その名もテクみ勉強会)で発表した内容ですが、「群れ」について書きたいと思います。
そもそも「群れ」とは一体何なのか
「群れ」についてWikipediaには以下のように書かれています。
群れは集団と言う数で淘汰(自然選択)の圧力に対抗しようとした生存戦略の一つ(個体ではすぐに天敵に食べられてしまうが、集まることで天敵を寄せ付けないなど)であると考えられる一方、群れで行動することで、生殖の面でも有利に働くと考えられている。
集団的自衛権然り渋谷のハロウィン然り、群れを成す事で他を圧倒し、自らが生き残るための生存戦略なのですね。(皆でやれば怖くない的な)
群れを表現するための3つのルール
ご存知の方も多いかもしれませんが、群れの動きをシミュレートする方法として有名なボイド理論(Boids)というものがあります。
ボイド理論は1987年にCraig Raynoldsさんが発表した理論で、以下の3つのルールを規定するだけで鳥の群れのシミュレーションが出来るというものです。
- Cohesion(結合)
- Separation(引き離し)
- Alignment(整列)
上記の3つのルールだけだとイメージが湧かないと思うので、こちらに実際に動作するものを用意しています。
(Qiitaにiframeやscript埋め込みが出来ないのが辛い...)
上記の処理はざっくり以下のような流れで処理が行われています。
- 鳥を描画するためのCanvasを用意する
- 予め決められた数の鳥(個々の丸)を描画する
- 個々の鳥に対して、ボイド理論の3つのルールを適用する
- 3の処理結果をもとに鳥を再描画する
- 3~4を延々と繰り返す(1秒間に30回)
それではボイド理論の話に戻り、3つのルールについて簡単にご説明します。
それぞれのルールは関数になっており、引数のindexをもとに全ての鳥(this.boids)に対して、繰り返し実行されるイメージになります。
Cohesion(結合)
Cohesionは鳥たちが多くいる方へ向かって飛ぶルールです。
今回は鳥たちが多くいる=群れの中心位置に向かって移動するようにしています。
cohesion(index) {
let center = {x: 0, y: 0};
let boidLength = this.boids.length;
//鳥たちの中心位置を求める
for( let i = 0; i < boidLength; i++ ) {
if( i === index ) {
continue;
}
center.x += this.boids[i].x;
center.y += this.boids[i].y;
}
center.x /= boidLength - 1;
center.y /= boidLength - 1;
//中心位置に向かうためのベクトルを算出
this.boids[index].vx += (center.x - this.boids[index].x) / 100;
this.boids[index].vy += (center.y - this.boids[index].y) / 100;
}
Separation(引き離し)
Separationは鳥同士がぶつからないように近づきすぎたら離れるルールです。
今回は鳥たちが10px以内に近づいたら、離れるようにしています。
separation(index) {
for( let i = 0, len = this.boids.length; i < len; i++ ) {
if( i === index ) {
continue;
}
//鳥同士の距離を測る
let distance = this.getDistance( this.boids[i], this.boids[index] );
//鳥同士が10px以内に位置する場合はベクトルを調整する
if( distance < 10 ) {
this.boids[index].vx -= this.boids[i].x - this.boids[index].x;
this.boids[index].vy -= this.boids[i].y - this.boids[index].y;
}
}
}
Alignment(整列)
Alignmentは周囲の鳥と飛ぶスピードや方向を合わせるルールです。
今回は鳥たちが全体の平均ベクトルに近づくように処理を行なっています。
alignment(index) {
let average = {vx: 0, vy: 0};
let boidLength = this.boids.length;
//全体の平均ベクトルを算出する
for( let i = 0; i < boidLength; i++ ) {
if( i === index ) {
continue;
}
average.vx += this.boids[i].vx;
average.vy += this.boids[i].vy;
}
average.vx /= boidLength - 1;
average.vy /= boidLength - 1;
//平均ベクトルに近づくように自分のベクトルを調整する
this.boids[index].vx += (average.vx - this.boids[index].vx) / 8;
this.boids[index].vy += (average.vy - this.boids[index].vy) / 8;
}
処理の説明自体はこれ以上しませんが、自分でも処理を実装してみたい方はこちらのサイトが分かりやすく解説されているので、参考にして頂ければと思います。(一部改良していますが、ほとんど同じです。)
ボイド理論を応用して色々な群れの動きが表現できる
今回は鳥の群れ(というより虫っぽいけど)を表現しましたが、少し変化を加えるとまた別の面白い動きを表現する事ができるようです。
例えば、前職時代の先輩がボイド理論に以下の変更を加える事で魚の群れを表現していました。
- 各個体は一定範囲内の個体との距離が遠い場合は互いに近づこうとする
- 各個体は一定範囲内の個体の進行方向に自分の進行方向を合わせようとする
終わりに
一見難しそうな群れの動きが比較的簡単に表現できるボイド理論。ゲームプログラミングではよく使われるそうなのですが、Webデザインやアート作品など色々なところで面白い使い道ができそうです。