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

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

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

棒でつないだふたつの点を落とす

いよいよ,(VerletPoint)と棒(VerletStick)のふたつのクラスからインスタンスをつくり,棒でつながれたふたつの点を放物線状に落とす。前掲コード2に以下のような手を加える。まず,変数だ。点の数は次回以降さらに増えるので配列(_points)で定めた。それから,棒のオブジェクトを入れる変数(_stick)も加える(こちらも,次回配列に差し替える)⁠

つぎに,初期化の関数(initialize())で,点のオブジェクトふたつを配列(_points)に加えた。棒のオブジェクトもやはり変数(_stick)に納める。注目していただきたいのは,(velocityX)を加えるのがひとつの点だけということだ。もうひとつの点は,棒につながれて動くことになる。

Ticker.tickイベントのリスナー関数(draw())は,新たな関数(updatePoints())でふたつの点を動かし,棒のメソッド(update())でつなぎ,ふたつの点を別の関数(renderPoints())で描いて,棒の描画メソッド(render())を呼出す。ふたつの点を動かす関数と,描画する関数は,配列から点のオブジェクトをforループで取り出して,前掲コード2と同じ内容の処理を行っている。

// var _point;
var _points = [];
var _stick;
function initialize() {

  // _point = new VerletPoint(50, 50);
  _points.push(new VerletPoint(70, 100));
  _points.push(new VerletPoint(50, 25));
  // _point.x += velocityX;
  _points[0].x += velocityX;
  _stick = new VerletStick(_points[0], _points[1]);

}
function draw(eventObject) {
  // updatePoint();
  updatePoints();
  _stick.update();

  // _point.render(drawingGraphics);
  renderPoints();
  _stick.render(drawingGraphics);

}
/*
function updatePoint() {
  _point.y += velocityY;
  _point.update();
  _point.constrain(_stageRect);
}
*/
function updatePoints() {
  var count = _points.length;
  for (var i = 0; i < count; i++) {
    var point = _points[i];
    point.y += velocityY;
    point.update();
    point.constrain(_stageRect);
  }
}
function renderPoints() {
  var count = _points.length;
  for (var i = 0; i < count; i++) {
    var point = _points[i];
    point.render(drawingGraphics);
  }
}

ふたつのクラス(VerletPointとVerletStick)からつくった2点と棒のインスタンスをつないで,放物線状に落とすJavaScriptは以下のコード5のとおりだ。力は前掲コード2と同じく,水平方向には初めに1度だけひとつの点に与え,垂直方向にはふたつの点それぞれに重力を加えている。すると,下端に落ちても弾まない点が,右端に当たると跳ね返されるように逆向きに動いた。これは棒の働きだ。

棒のクラス(VerletStick)のupdate()メソッドは,長さが保たれるように両端の2点の位置を調整した。右端にぶつかった点は,それ以上進めない。しかし,後ろの点は右に動こうとする。それでは棒の長さが詰まってしまうので,update()メソッドが両端の点を外側に押し返す。後ろの点は戻されるものの,前の点はやはり動けない。それが繰返されるうちに,後ろの点は逆向きに進み出し,前の点はそれに引っ張られる。こうして,跳ね返る動きになったのだ。

コード5 ふたつの点を棒でつないで放物線状に落とす

var stage;
var drawingGraphics;
var _points = [];
var _stick;
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
  );
  _points.push(new VerletPoint(70, 100));
  _points.push(new VerletPoint(50, 25));
  _points[0].x += velocityX;
  _stick = new VerletStick(_points[0], _points[1]);
  createjs.Ticker.timingMode = createjs.Ticker.RAF;
  createjs.Ticker.addEventListener("tick", draw);
}
function draw(eventObject) {
  updatePoints();
  _stick.update();
  drawingGraphics.clear();
  renderPoints();
  _stick.render(drawingGraphics);
  stage.update();
}
function updatePoints() {
  var count = _points.length;
  for (var i = 0; i < count; i++) {
    var point = _points[i];
    point.y += velocityY;
    point.update();
    point.constrain(_stageRect);
  }
}
function renderPoints() {
  var count = _points.length;
  for (var i = 0; i < count; i++) {
    var point = _points[i];
    point.render(drawingGraphics);
  }
}

引数のデフォルト値と値の確認

先にお見せしたjsdo.itのVerletStickクラスは,コンストラクタの引数が4つあり,ステートメント数も前掲コード4より少し多い。それを解説して,今回は締めよう。

コンストラクタVerletStick()には,以下のコード6のように,引数をふたつ加えてクラスの4つのプロパティすべてが渡せるようにした。とはいえ,後のふたつはつねに与えなくともよい。そのために,デフォルト値を定めた。引数が渡されなかった場合や数値が適切でないときは,デフォルト値が用いられる※2)⁠なお,クラスの他のふたつのメソッドに変更はない。

コード6 棒を定めるクラスVerletStickのコンストラクタに引数追加

function VerletStick(point0, point1, length, elasticity) {
  if (!elasticity || elasticity > 0.5 || 0 > elasticity) {
    this.elasticity = 0.2;
  } else {
    this.elasticity = elasticity;
  }
  this._point0 = point0;
  this._point1 = point1;
  if (!length || length < 0) {
    this._length = point0.getDistance(point1);
  } else {
    this._length = length;
  }
}
VerletStick.prototype.update = function() {
  var delta = this._point1.subtract(this._point0);
  var distance = delta.getLength();
  var difference = this._length - distance;
  var offsetX = (difference * delta.x / distance)  * this.elasticity;
  var offsetY = (difference * delta.y / distance)  * this.elasticity;
  this._point0.addCoordinates(-offsetX, -offsetY);
  this._point1.addCoordinates(offsetX, offsetY);
};
VerletStick.prototype.render = function(graphics) {
  graphics.beginStroke("black")
  .setStrokeStyle(0.5)
  .moveTo(this._point0.x, this._point0.y)
  .lineTo(this._point1.x, this._point1.y);
};

もうひとつだけ補っておきたいのは,棒の固さのプロパティ(elasticity)のデフォルト値を0.5でなく0.2としたことだ。前述のとおり,0.5では2点間の距離と棒の長さとの誤差は,ふたつの点の位置をそれぞれ半分ずつずらして直ちに補正される。しかし,この後点と棒を増やしていくと,あちらを立てればこちらが立たずという場合が起こり,無理が生ずることもある(その場合は,自然なアニメーションにならない)⁠そこで,誤差を少しずつ解消するように,プロパティのデフォルト値は小さな値にした。

さて,今回使った運動の仕組みでは,座標の移動と位置関係や動く範囲を定めるだけで,跳ね返りの速度や方向など考えることなく複数のオブジェクトをつなげて弾力のある動きができた。次回はすでに示唆したとおり,点と棒の数を増やそう。

※2
引数が渡されなかった場合は,論理否定演算子!で判定している。引数がないことを示すundefinedは,演算子によりtrueと評価される。ただし,数値0もtrueとされることに注意しなければならない。今回の例では,0を除いて構わないので,この式を用いた。

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書