概要
BoidsをJavaScriptで再現するために、基本コードの枠組みとなるコード準備する。
- 指定した下図の個体を発生させる
- ボタン操作で各個体の動作を開始・停止する
- 個体の動作は同じ速さでランダムな方向に動く
基本の動作から始めて順次拡充していくが、以後共通する仕様は以下の通り。
- グローバル定義
- HTML5のcanvasで描画することとし、canvasとそのコンテキストをグローバルオブジェクトとする
- その他、個体や群れの挙動に係るパラメータもグローバル定義する
- 個体のクラスCreature
move()
メソッドは、個々のCreatureインスタンスの一定時間後の位置をその現在位置と速度から決定し、新しい位置に変更するdraw()
メソッドは個々のCreatureインスタンスをcanvasに描く- 初期段階から、Boidsの3つのルールに対応するメソッドの枠のみ用意し、順次実装していく
- Boidsの実行
- 個体クラスのインスタンスをランダムな位置に複数発生させ、配列に格納する
- 以下を繰返す
- 現在位置の全てのCreatureオブジェクトを
drawAll()
関数で描画 - 一定時間間隔で全てのCreatureオブジェクトの位置を
moveAll()
関数で変更する moveAll()
の中で3つのルールに対応する関数を呼び出すが、初期段階では枠のみ用意し、順次実装していく
- 現在位置の全てのCreatureオブジェクトを
仕様
以下の仕様で、個体とその群の生成、基本動作のみを実装する。
- 操作
- ブラウザのテキストボックスに個体数を入力
- ボタンを押すごとに起動・停止を繰り返す
- 初期設定
- 初期状態で、ランダムな位置に個体を生成
- 各個体の速さは一定とし、速度ベクトルの向きはランダムに与える
- 挙動
- 各個体は与えられた速度で移動
- 上下左右の壁に当たった時は全反射
- 個体間の干渉は考慮しない(重なりを許す)
デモンストレーション
個体数
スクリプトの内容
グローバル定義部
アプリケーション全体で保持・利用する変数・オブジェクトを定義。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 時間間隔は50ms var TIME_SLICE = 50; // 速度の最大値 var MAX_VELOCITY = 100; // 個体群を格納する配列 var creatures; // 描画用canvasとコンテキスト var canvas; var context; // 起動・停止用のフラグ var isRunning = false; // setInterval()用の変数 var timer; |
Creatureクラス
一つ一つの個体を表すCreatureクラスを以下の様に構築する。これらのコンストラクタやメソッドは、以後の拡張を通して変わらない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/* 個体のクラスのコンストラクタ */ function Creature() {} /* 位置のセッター */ Creature.prototype.setPosition = function( x, y ) {} /* 速度のセッター */ Creature.prototype.setVelocity = function( vx, vy ) {} /* 個体を描画するメソッド */ Creature.prototype.draw = function() {} /* 個体を移動させるメソッド */ Creature.prototype.move = function() {} |
コンストラクタ
コンストラクタは、Creatureクラスのインスタンスを生成する。Creatureオブジェクトは、個々の座標と速度ベクトルの要素を保持する。
1 2 3 4 5 6 7 8 |
function Creature() { // 位置座標 this.x = 0; this.y = 0 // 速度ベクトル this.vx = 0; this.vy = 0; } |
位置と速度のセッター
位置座標、速度ベクトルの要素を個体にセットする。
1 2 3 4 5 6 7 8 9 10 |
Creature.prototype.setPosition = function( x, y ) { this.x = x; this.y = y; } Creature.prototype.setVelocity = function( vx, vy ) { this.vx = vx; this.vy = vy; } |
描画メソッド
自身をcanvasに描画する。ここでは位置座標を中心とする小円を描いている。canvasのコンテキストはグローバル変数contextに設定されている前提。
1 2 3 4 5 |
Creature.prototype.draw = function() { context.beginPath(); context.arc( this.x, this.y, 4, 0, Math.PI * 2, true ); context.fill(); } |
移動メソッド
与えられた速度で移動する。上下・左右の壁に当たった時の反射処理を行っている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Creature.prototype.move = function() { this.x += this.vx * TIME_SLICE / 1000; this.y += this.vy * TIME_SLICE / 1000; // 左右の壁の反射処理 if ( this.x < 0 ) { this.x = 0; this.vx = -this.vx; } else if ( this.x > canvas.width ) { this.x = canvas.width; this.vx = -this.vx; } // 上下の壁の反射処理 if ( this.y < 0 ) { this.y = 0; this.vy = -this.vy; } else if ( this.y > canvas.height ) { this.y = canvas.height; this.vy = -this.vy; } } |
実行関数群
以下の関数群のうち、createCreatures()
はボタンが押されるたときに与えられた数の個体を生成。
boids()
はボタンが押されるたびに実行・停止を切り替える。
他の関数は、実行時に群全体を異動させ、描画するための関数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/* 複数の個体を生成する関数 */ function createCreatures( n ) {} /* Boidsを起動・停止するメイン関数 */ function boids() {} /* タイマーで描画するための関数 */ function animate() {} /* 全ての個体を移動させる関数 個々の個体のmove()メソッドを呼び出しているだけ */ function moveAll() {} /* 全ての個体を描画する関数 */ function drawAll() {} |
createCreatures()~個体の生成
与えられた数の個体をCreatureインスタンスとして生成し、グローバルな配列に格納する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function createCreatures( n ) { creatures = new Array( n ); for ( var i = 0; i < creatures.length; i++ ) { creatures[i] = new Creature(); // ランダムな位置座標を発生 var x = Math.floor( Math.random() * canvas.width ); var y = Math.floor( Math.random() * canvas.height ); // 速度の方向をランダムに発生 var v = MAX_VELOCITY; var ang = Math.random() * Math.PI * 2; // 個体に位置と速度をセット creatures[i].setPosition( x, y ); creatures[i].setVelocity( v * Math.cos( ang ), v * Math.sin( ang ) ); } } |
boids()
~メイン関数
ボタンが押されるたびに呼び出され、全体の実行/停止を制御する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function boids() { canvas = document.getElementById( "canvas_boids" ); if ( canvas.getContext ) { context = canvas.getContext( "2d" ); if ( isRunning ) { clearTimeout( timer ); isRunning = false; } else { // テキストボックスから個体数を取得 var numCreatures = parseInt( document.getElementById( "txt_boids" ).value, 10 ); // 数値としてパースできたときのみ実行 if ( !isNaN( numCreatures ) ) { // 複数の個体を生成 createCreatures( numCreatures ); // 前個体を描画 drawAll(); // アニメーションをスタート animate(); isRunning = true; } } } } |
動画処理
タイマーにより個体を動かし、描画する。呼び出されるたびに、全ての個体を移動させ、描画する関数を呼び出す。
1 2 3 4 5 6 7 |
function animate() { moveAll(); drawAll(); timer = setTimeout( "animate()", TIME_SLICE ); } |
個体の移動
配列に格納された個体に移動を指示する。
1 2 3 4 5 |
function moveAll() { for ( var i = 0; i < creatures.length; i++ ) { creatures[i].move(); } } |
個体の描画
配列に格納された個体に描画を指示する。
1 2 3 4 5 6 7 |
function drawAll() { context.clearRect( 0, 0, canvas.width, canvas.height ); for ( var i = 0; i < creatures.length; i++ ) { creatures[i].draw(); } } |
次の記事:基本コード