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

第25回楕円軌道に残像を描きながら回るパーティクル

今回から取り組むお題は、さまざまな楕円軌道を描いて回るパーティクルだ。CreateJSコミュニティエバンジェリストSebastian DeRossi氏によるFun with Particlesを参考に、仕組みがわかりやすいスクリプトに書き直した。つぎのjsdo.itのサンプルが目指す表現だ。楕円軌道を動かす式の立て方が課題となる。

楕円軌道を描いて動かす

今回のスクリプトは以下のような組立てにしよう。楕円軌道で動くパーティクルは、Shapeクラスのサブクラス(Particle)として定める。Shapeクラスの継承については、第18回「クラスの継承と透視投影」Shapeクラスを継承する3次元座標のクラス定義で解説した。初期化の関数(initialize())をbody要素のonload属性で呼び出すのはいつもどおりだ。パーティクルのオブジェクトは、その関数の中からコンストラクタ関数を呼び出してつくる。

<script src="http://code.createjs.com/easeljs-0.7.1.min.js"></script>
<script>
function Particle(設定の引数) {
  // パーティクルの生成
}
Particle.prototype = new createjs.Shape();
</script>
<script>
var stage;
var particle;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  particle = new Particle(設定の引数);
  // 初期化の処理
}
</script>
<body onLoad="initialize()">
  <canvas id="myCanvas" width="240" height="180"></canvas>
</body>

さて、楕円軌道の動きは、円軌道の方程式に手を加えて導く。半径1の円周上で、x軸の正方向となす角がθの点は(cosθ, sinθ)で定められた(再掲第11回図4⁠。このxy座標に原点からの距離を掛合わせれば、任意の距離と角度の座標が求められる(第11回「マウスポインタの動きに合わせてインスタンスをランダムに落とす」インスタンスの動きに水平方向の初速を加える参照⁠⁠。そして、その角度を変えれば、座標が円軌道の上を動く。

  • 水平座標(x) = 水平中心座標 + 半径×cos角度
  • 垂直座標(y) = 垂直中心座標 + 半径×sin角度
第11回 図4 原点から距離が1で角度θのxy座標は(cosθ, sinθ)⁠再掲)
第11回 図4 原点から距離が1で角度θのxy座標は(cosθ, sinθ)(再掲)

円軌道を楕円にするには、三角関数cosとsinに掛合わせる半径の長さを変えればよい。パーティクルを楕円軌道で回すメソッド(move())は、つぎのようにパーティクルのクラス(Particle)に定めた。引数に渡されたラジアン角(advance)だけ、楕円軌道に沿って動く。なお、中心のxy座標(centerXとcenterY)および半径(radius⁠⁠、ならびに現在の回転角(angle)は、別にプロパティとして与える。また、垂直方向の半径は係数(0.8)を乗じて変えている。

Particle.prototype.move = function(advance) {
  var angle = this.angle + advance;
  this.x = this.centerX + this.radius * Math.cos(angle);
  this.y = this.centerY + this.radius * 0.8 * Math.sin(angle);
  this.angle = angle;
};

パーティクルを定めるクラス(Particle)には、楕円軌道で回すメソッド(move())のほかに、基本的なプロパティを与えるメソッド(reset())も加えたコード1⁠。後者のメソッドにより、楕円軌道の中心座標(centerXとcenterY)や楕円の横半径(radius⁠⁠、および軌道上の位置を決めるラジアン角(angle)がいつでも変えられる。

コード1 パーティクルを定めるクラス

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) {
  this.centerX = x;
  this.centerY = y;
  this.radius = radius;
  this.angle = angle;
};
Particle.prototype.move = function(advance) {
  var angle = this.angle + advance;
  this.x = this.centerX + this.radius * Math.cos(angle);
  this.y = this.centerY + this.radius * 0.8 * Math.sin(angle);
  this.angle = angle;
};

パーティクルのインスタンス(particle)をひとつつくって、楕円軌道で動かすのが以下のコード2図1⁠。インスタンスは設定の関数(resetParticle())に渡して、クラスのメソッド(reset())によりランダムな楕円軌道の半径(radius)とラジアン角の位置(angle)とともに、中心(center)の座標を与える。

また、Ticker.tickイベントのリスナー関数(tick())からは、パーティクル移動の関数(moveParticle())を呼出す。後者の関数は今のところ、クラスに定めたメソッド(move())の呼出しにより、オブジェクトを楕円軌道で回しているだけだ。

図1 丸いパーティクルが楕円軌道を描いて回る
図1 丸いパーティクルが楕円軌道を描いて回る
コード2 クラスでつくったオブジェクトひとつを楕円軌道で回す
var stage;
var center = new createjs.Point();
var particle;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  var stageWidth = canvasElement.width;
  var stageHeight = canvasElement.height;
  stage = new createjs.Stage(canvasElement);
  center.x = stageWidth / 2;
  center.y = stageHeight / 2;
  particle = new Particle(4, "#0016E9");
  resetParticle(particle);
  stage.addChild(particle);
  createjs.Ticker.timingMode = createjs.Ticker.RAF;
  createjs.Ticker.addEventListener("tick", tick);
}
function resetParticle(particle) {
  var radius = 40 + Math.random() * 50;
  var angle = Math.random() * Math.PI * 2;
  particle.reset(center.x, center.y, radius, angle);
}
function tick() {
  moveParticle(particle);
  stage.update();
}
function moveParticle(particle) {
  particle.move(Math.PI / 90);
}

10個のパーティクルをさまざまな大きさの楕円軌道で回す

パーティクルの数を10個に増やそう。インスタンスは、新たな変数(particles)として定めた配列に納める。また、初期化の関数(initialize())に加えたforループで、パーティクルの半径(radius)はランダムに与えた。なお、前掲コード1で定めた設定の関数(resetParticle())により、楕円軌道の半径や位置(ラジアン角)もそれぞれランダムに決まる。そして、Ticker.tickイベントのリスナー関数(tick())が、forループで配列から取り出したパーティクル(particle)を順に動かしている。

var total = 10;
// var particle;
var particles = [];

function initialize() {

  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);
  }

}

function tick() {
  var count = particles.length;
  for(var i = 0; i < count; i++) {
    var particle = particles[i];
    moveParticle(particle);
  }

}

前掲コード2にこれらの修正を加えると、大きさの違う10個のパーティクルがつくられ、Canvasの中心からそれぞれ半径の異なる楕円軌道を描いて回る図2⁠。ここまで書き替えたのが以下のコード3だ。

図2 大きさの違う10個のパーティクルが中心から半径の異なる楕円軌道を回る
図2 大きさの違う10個のパーティクルが中心から半径の異なる楕円軌道を回る
コード3 大きさの違う10個のパーティクルを中心から半径の異なる楕円軌道で回す
var stage;
var total = 10;
var center = new createjs.Point();
var particles = [];
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  var stageWidth = canvasElement.width;
  var stageHeight = canvasElement.height;
  stage = new createjs.Stage(canvasElement);
  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);
  }
  createjs.Ticker.timingMode = createjs.Ticker.RAF;
  createjs.Ticker.addEventListener("tick", tick);
}
function resetParticle(particle) {
  var radius = 40 + Math.random() * 50;
  var angle = Math.random() * Math.PI * 2;
  particle.reset(center.x, center.y, radius, angle);
}
function moveParticle(particle) {
    particle.move(Math.PI / 90);
}
function tick() {
  var count = particles.length;
  for(var i = 0; i < count; i++) {
    var particle = particles[i];
    moveParticle(particle);
  }
  stage.update();
}

黒い背景に残像の加わった光るパーティクルを回す

さらに目指すお題に近づけるため、背景は黒にして、光るパーティクルに残像も加えよう。もっともこれらは、すでにこの連載で解説したことの応用になる。背景色の黒はCSSで定めておく。

canvas {
  background-color: black;
}

インスタンスが光るような表現には、DisplayObject.compositeOperationプロパティを用いる。プロパティ値に"lighter"を与えれば、重ね合わせたカラー値が加算されて明るくなる。第4回「時間差をつけたトゥイーン」DisplayObject.compositeOperationプロパティでイメージのピクセルを合成するで説明した効果だ。もっとも、プロパティの定めはすでにクラス(Particle)のコンストラクタ関数で済ませてある(前掲コード1⁠。あとは、残像とインスタンスを重ねればよい。

残像をつくるには、Stage.autoClearプロパティにfalseを与える。考え方は、第13回「モーションブラーと弾むアニメーション」アニメーションの残像にモーションブラーを加えると同じだ。ただし、とくにぼかしなどのフィルタは掛けないので、いちいちCanvasを塗り直すには及ばない。つぎのように、新たな関数(addBackground())で透明に近い背景色("black")のShapeオブジェクト(background)を手前に置けば、勝手に残像とともに塗り重ねられつつ消えてゆく図3⁠。

var fading = 0.04;

function initialize() {

  addBackground(stageWidth, stageHeight, fading);

}

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;
}
図3 楕円軌道の残像に重なるインスタンスが明るさを増す
図3 楕円軌道の残像に重なるインスタンスが明るさを増す

これで楕円軌道で回るインスタンスの残像がつくられ、重なり合うインスタンスは明るさを増しつつ、少しずつ消えてゆく。ここまでのスクリプトがつぎのコード4だ。クラス定義はコード1のまま変わらない。サンプルコードをjsdo.itにも掲げた。今回はここまでにしよう。次回は、楕円軌道を描く式は別の考え方で定める。また、パーティクルを増やすとともに、おなじみオブジェクトの使い回しも加えるつもりだ。

コード4 楕円軌道の残像に重なったパーティクルが輝きながら消えてゆく
var stage;
var total = 10;
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 = 40 + Math.random() * 50;
  var angle = Math.random() * Math.PI * 2;
  particle.reset(center.x, center.y, radius, angle);
}
function moveParticle(particle) {
    particle.move(Math.PI / 90);
}
function tick() {
  var count = particles.length;
  for(var i = 0; i < count; i++) {
    var particle = particles[i];
    moveParticle(particle);
  }
  stage.update();
}
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;
}

おすすめ記事

記事・ニュース一覧