HTML5のCanvasでつくるダイナミックな表現―CreateJSを使う

第26回 パーティクルを使い回しながらさまざまな楕円軌道で回す

この記事を読むのに必要な時間:およそ 7 分

ランダムな楕円軌道のバランスをとる

前記の速度を求める方程式の中で「半径×角速度」の値には,とくに三角関数は含まれない。クラス(Particle)のプロパティを定めるメソッド(reset())で用いた三角関数Math.cos()Math.sin()メソッド)が果たすのは,ランダムな引数(angle)値から±1の範囲の係数を定めるだけの役割だ。

したがって,引数値は使わずに,たとえばつぎのようにランダムに±1の範囲の値を決めても構わない。ただ,この式から得られるパーティクル10個の楕円軌道は,縮こまった感じになる図2⁠。

Particle.prototype.reset = function(x, y, radius, angle) {

  // this.speedX = radius * Math.cos(angle);
  // this.speedY = radius * Math.sin(angle);
  this.speedX = radius * (Math.random() * 2 - 1);
  this.speedY = radius * (Math.random() * 2 - 1);

};

図2 10個のパーティクルの楕円軌道が縮こまる

図2 10個のパーティクルの楕円軌道が縮こまる

xとy方向それぞれ独立に±1の範囲のランダムな値を決めると,両方とも0に近い値になることが起こりうる。それに対して,ランダムな引数(angle)からcosとsinで求める値は,一方が0に寄ると,他方は1または-1に近づく。軌道の長さが縮こまることはない。つまり,ランダムに定めた楕円軌道のバランスがとれるのだ。

100個のパーティクルを使い回して再設定する

パーティクルの数をさらに増やそう。ただ,インスタンスを新たにつくり続けるのではなく,ある程度経ったら設定を変えることにして,オブジェクトは使い回す。そこで,アニメーションが描かれる(move())たびにカウントダウンするプロパティ(lifetime)を,つぎのようにパーティクルのクラス(Particle)に加える。値はプロパティを定めるメソッド(reset())の引数とした。

// Particle.prototype.reset = function(x, y, radius, angle) {
Particle.prototype.reset = function(x, y, radius, angle, lifetime) {
  this.x = x;
  this.y = y;
  this.speedX = radius * Math.cos(angle);
  this.speedY = radius * Math.sin(angle);
  this.lifetime = lifetime;
  this.angle = 0;
};
Particle.prototype.move = function(advance) {
  var angle = this.angle + advance;
  var velocityX = this.speedX * Math.sin(angle);
  var velocityY = this.speedY * Math.cos(angle);
  this.x += velocityX;
  this.y += velocityY;
  this.angle = angle;
  this.lifetime--;
};

つぎに,クラス(Particle)からパーティクルをつくってアニメーションさせるJavaScriptコードもつぎのように手直しする。インスタンスの数(total)は大幅に増やす。また,パーティクルにプロパティを与えるメソッド(reset())呼出しのとき,前述のカウントダウンの値を引数(lifetime)として加えた。この数値はインスタンス数の範囲までのランダムな整数だ※1⁠。

そして,インスタンスをアニメーションさせる関数(moveParticle())は,カウントダウンのプロパティ(lifetime)値が0になっていなければ,アニメーションのメソッド(move())を呼び出して回す。値が0までカウントダウンされたインスタンスは,プロパティを設定す関数(resetParticle())の呼出しによって使い回している。

var total = 100;    // = 10;

function resetParticle(particle) {
  var radius = 1 + Math.random();
  var angle = Math.random() * Math.PI * 2;
  var lifetime = Math.random()* total | 0;
  // particle.reset(center.x, center.y, radius, angle);
  particle.reset(center.x, center.y, radius, angle, lifetime);
}

function moveParticle(particle) {
  if (particle.lifetime > 0) {
    particle.move(Math.PI / 90);
  } else {
    resetParticle(particle);
  }
}

これで,100個のパーティクルがつくられ,Canvasの真ん中からさまざまな楕円軌道で残像を描きながら回る図3⁠。そして,ランダムに定めたカウントダウンのプロパティ値が0になるたび,インスタンスは設定し直される。ここまで書き替えたクラス(Particle)の定めが以下のコード3で,クラスのインスタンスをつくってアニメーションさせるスクリプトはコード4にまとめて掲げた。

図3 100個のパーティクルがCanvasの中心からさまざまな楕円軌道で残像を描く

図3 100個のパーティクルがCanvasの中心からさまざまな楕円軌道で残像を描く

コード3 カウントダウンのプロパティを加えた楕円軌道で動くパーティクルのクラス

function Particle(radius, color) {
  this.initialize();
  this.graphics.beginFill(color)
  .drawCircle(0, 0, radius)
  .endFill();
  this.compositeOperation = "lighter";
}
Particle.prototype = new createjs.Shape();
Particle.prototype.reset = function(x, y, radius, angle, lifetime) {
  this.x = x;
  this.y = y;
  this.speedX = radius * Math.cos(angle);
  this.speedY = radius * Math.sin(angle);
  this.lifetime = lifetime;
  this.angle = 0;
};
Particle.prototype.move = function(advance) {
  var angle = this.angle + advance;
  var velocityX = this.speedX * Math.sin(angle);
  var velocityY = this.speedY * Math.cos(angle);
  this.x += velocityX;
  this.y += velocityY;
  this.angle = angle;
  this.lifetime--;
};

コード4 パーティクル100個を使い回しながらさまざまな楕円軌道で回す

var stage;
var total = 100;
var center = new createjs.Point();
var particles = [];
var fading = 0.04;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  var stageWidth = canvasElement.width;
  var stageHeight = canvasElement.height;
  stage = new createjs.Stage(canvasElement);
  stage.autoClear = false;
  center.x = stageWidth / 2;
  center.y = stageHeight / 2;
  for(var i = 0; i < total; i++) {
    var radius = 1 + Math.random() * 4;
    var particle = new Particle(radius, "#0016E9");
    resetParticle(particle);
    particles.push(particle);
    stage.addChild(particle);
  }
  addBackground(stageWidth, stageHeight, fading);
  createjs.Ticker.timingMode = createjs.Ticker.RAF;
  createjs.Ticker.addEventListener("tick", tick);
}
function resetParticle(particle) {
  var radius = 1 + Math.random();
  var angle = Math.random() * Math.PI * 2;
  var lifetime = Math.random()* total | 0;
  particle.reset(center.x, center.y, radius, angle, lifetime);
}
function tick() {
  var count = particles.length;
  for(var i = 0; i < count; i++) {
    var particle = particles[i];
    moveParticle(particle);
  }
  stage.update();
}
function moveParticle(particle) {
  if (particle.lifetime > 0) {
    particle.move(Math.PI / 90);
  } else {
    resetParticle(particle);
  }
}
function addBackground(width, height, alpha) {
  var background = new createjs.Shape();
  background.graphics.beginFill("black")
  .drawRect(0, 0, width, height)
  .endFill();
  stage.addChild(background);
  background.alpha = alpha;
}

jsdo.itにもサンプルとしてコードを掲載した。アニメーションの見た目は,ほぼでき上がったといってよい。だが,さらにパーティクルを増やすと,込み入った感じになる。もう少し,パーティクルの動きにばらつきを加えたい。それは次回としよう。また,楕円運動の速度を用いた方程式についても,少し数学的な説明を補うつもりだ。

※1

数値を,ビットごとの論理和(OR)演算子|で0と演算すると,数値の小数点以下が切捨てられて整数になる。もっとも,この使い方は裏技に近い。

数値 | 0 → 整数

論理「和」という名前のとおり,この演算は0との足し算とほぼ同じ結果になる。つまり,数値は基本的に変わらない。ただし,ビット演算子は整数に対して用いられるのが決まりだ。そのため,小数点以下が切捨てられることになる。もちろん,Math.floor()メソッドで切捨ててもよい。だが,ビット演算子の方がMathクラスのメソッドより高速だ。

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書