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

第31回 位置座標の相互作用で弾力を表す

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

今回からは,つぎのお題に取り組む。多角形の頂点を結んだかたちが,放物線状に落ちてきて,弾力のある動き方をする。それぞれの頂点の間の力のかかり方や,それらが弾むときの方向,他の頂点への力の変化など,かなり複雑な計算が求められるように感じるだろう。実は四則演算を使った簡単な式で,このアニメーションはつくられている。

クラスでつくった点を放物線状に落とす

第24回「マウスポインタの動きに弾みがついた曲線を滑らかに描く」では,「オイラー法」という考え方にもとづき,バネのような動きを加速度と速度の四則演算で表した(「バネのような動きを数学の目で確かめる参照)。速度を位置に足し込めば,新たな位置が求められる。さらに,速度も変わるなら,加速度を加えて新たな速度が導けた。

今回のアニメーションも,やはり近似計算で式を簡単にする。オイラー法と異なるのは,時系列の位置座標を使って運動が表されることだ。ベレ法と呼ばれ,動きが座標間の関係を示す四則演算で導けてしまう。

まず,アニメーションするかたちは点からつくられる。その点をクラス(VerletPoint)として定めよう。クラスのつくり方については,第17回簡単なクラスを定義するをご参照いただきたい。いつもの解説とは少し進め方を変えて,いきなりクラスのプロパティとメソッドの役割および働きを決めることにする。

プロパティ
  • x, y:オブジェクトの現在のxy座標。
  • _oldX, _oldY:オブジェクトの前のxy座標。
メソッド
  • VerletPoint(x, y):コンストラクタ。xy座標値を引数に渡す。
  • update():オブジェクトの座標のプロパティ値を,速度にもとづいて更新する。
  • constrain(rect):引数にRectangleオブジェクトを受取って,オブジェクトの座標をその矩形領域内に収める。
  • getVelocity():オブジェクトの新旧xy座標から速度を求めて返す。
  • render(graphics):引数に受取ったGraphicsオブジェクトに,オブジェクトの現行xy座標の点を描く。
  • addCoordinates(x, y):引数に受取ったxyピクセル値を,オブジェクトのxy座標に加える。

点のクラス(VerletPoint)は以下のコード1のように定めた。このクラスをどう使ってアニメーションにつなげるのかは別にして,上の説明と照らし合わせれば,プロパティとメソッドひとつひとつが何をしているのかはほぼおわかりいただけるだろう。それらを簡単に説明していこう。

コンストラクタVerletPoint()の引数にはインスタンスのxy座標値を渡して,その値が新旧xy座標のプロパティに初期値として与えられる。インスタンスを動かせば,今の座標とその前の座標が,それぞれのプロパティに定められることになる。

update()メソッドは,インスタンスを動かしてプロパティの値を改める。動かすための速度はgetVelocity()メソッドが,古い座標と新しい座標の差から導いてPointオブジェクトで返す。そのため,このクラスは速度をプロパティにもたない。速度(ベクトル)のxy座標値をaddCoordinates()メソッドでオブジェクトの座標に加えて,新旧座標のプロパティが更新される。

constrain()メソッドは,オブジェクトの座標を引数のRectangleオブジェクトが示す矩形領域の中に収める。Canvasの矩形領域を引数のRectangleオブジェクトとして与えれば,アニメーションする点はその領域の外に出ることはない。

render()メソッドは,黒点を引数のGraphicsオブジェクトに描く。描画を消すGraphics.clear()メソッドやステージ再描画のStage.update()メソッドは呼んでいない。後々多くの点や線を描くため,これらのメソッドはクラスの外でまとめて呼び出したほうがよいからだ。

コード1 点を定めるクラスVerletPoint

function VerletPoint(x, y) {
  this.x = this._oldX = x;
  this.y = this._oldY = y;
}
VerletPoint.prototype.update = function() {
  var tempX = this.x;
  var tempY = this.y;
  var velocity = this.getVelocity();
  this.addCoordinates(velocity.x, velocity.y);
  this._oldX = tempX;
  this._oldY = tempY;
};
VerletPoint.prototype.constrain = function(rect) {
  var left = rect.x;
  var right = left + rect.width;
  var top = rect.y;
  var bottom = top + rect.height;
  if (this.x < left) {
    this.x = left;
  } else if (this.x > right) {
    this.x = right;
  }
  if (this.y < top) {
    this.y = top;
  } else if (this.y > bottom) {
    this.y = bottom;
  }
};
VerletPoint.prototype.getVelocity = function() {
  var velocity = new createjs.Point(this.x - this._oldX, this.y - this._oldY);
  return velocity;
};
VerletPoint.prototype.render = function(graphics) {
  graphics.beginFill("black")
  .drawCircle(this.x, this.y, 2.5)
  .endFill();
};
VerletPoint.prototype.addCoordinates = function(x, y) {
  this.x += x;
  this.y += y;
};

このクラス(VerletPoint)でつくったひとつの黒点を放物線状に落としてみよう図1)。script要素に書いたJavaScriptは以下のコード1のとおりだ。いつものお約束どおり,初期化の関数(initialize())はbody要素のonload属性で呼び出す。

<body onLoad="initialize()">
  <canvas id="myCanvas" width="400" height="300"></canvas>
</body>

図1 黒点が放物線状に落ちる

図1 黒点が放物線状に落ちる

図1 黒点が放物線状に落ちる

図1 黒点が放物線状に落ちる

初期化の関数(initialize())は,ステージに描画用のShapeオブジェクト(shape)を加え,そのGraphicsオブジェクトを変数(drawingGraphics)にとった。そして,Canvasの矩形領域をRectangleオブジェクトに与えて変数に納める(_stageRect)。そのうえで,コード1のクラス(VerletPoint)からつくったオブジェクトを変数(_point)に入れ,x座標値を動かした。アニメーションは,Ticker.tickイベントに加えたリスナー関数(draw())で扱う。

アニメーションのリスナー関数(draw())がまず呼び出すのは,この後説明する点を動かす関数(updatePoint())だ。そして,描画のGraphicsオブジェクトをクリアしてから,点のオブジェクト(_point)のrender()メソッドで新たな点を描く。

点を動かす関数(updatePoint())は,点のオブジェクト(_point)のy座標を動かしてオブジェクトのupdate()メソッドで更新した後,動く範囲をconstrain()メソッドでCanvasの矩形領域(_stageRect)に納めた。

コード2 点を放物線状に落とす

var stage;
var drawingGraphics;
var _point;
var _stageRect;
var velocityX = 5;
var velocityY = 0.25;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  var shape = new createjs.Shape();
  stage = new createjs.Stage(canvasElement);
  stage.addChild(shape);
  drawingGraphics = shape.graphics;
  _stageRect = new createjs.Rectangle(
    0,
    0,
    canvasElement.width,
    canvasElement.height
  );
  _point = new VerletPoint(50, 50);
  _point.x += velocityX;
  createjs.Ticker.timingMode = createjs.Ticker.RAF;
  createjs.Ticker.addEventListener("tick", draw);
}
function draw(eventObject) {
  updatePoint();
  drawingGraphics.clear();
  _point.render(drawingGraphics);
  stage.update();
}
function updatePoint() {
  _point.y += velocityY;
  _point.update();
  _point.constrain(_stageRect);
}

実際の動きを確かめた方がよいので,jsdo.itに前掲コード1およびコード2を掲げた。コード1のクラス定義は[HTML]の欄に分けて書いている。見ると,何の変哲もないアニメーションだ。黒い点が,放物線状に落ちる。とくに弾ませる処理は加えていないため,Canvas下端に落ちると,そのまま滑って右端で止まる。

しかし,前掲コード2と動きをよく見比べると,ある特徴に気づく。点のオブジェクト(_point)のx座標を動かしたのは,初期設定の関数(initialize())の中で1度きりだ。それでも水平方向に動き続けるのは,点のクラス(VerletPoint())が速度をgetVelocity()メソッドにより,前の座標と今の座標の差で捉えているためだ。つまり,点のクラスのオブジェクトは,ひとたび動かせば,そのまま放っておいても同じ向きに動き続ける仕組みになっている。

アニメーションのリスナー関数(draw())からは,点を動かす関数(updatePoint())が呼び出された。そして,その関数が呼び出されるたびに,点のオブジェクト(_point)のy座標に一定の値を加えている。その結果,垂直方向には重力がかかったような動きになった。すなわち,点のオブジェクトの座標を動かすことは,今の動きにさらに力を加えるのに等しいといえる※1)。

※1
つまり,点のオブジェクトの位置座標を動かすのは,力つまり加速度を加えるのに等しい。ただ,位置に足し込む値なので,細かいことはいわず,変数名には速度(velocity)という語を用いた。

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入