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

第33回 弾力のある多角形をドラッグして落とす

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

図形をドラッグして放る

それでは,弾力のある八角形をドラッグして放り投げられるようにしたい。ドラッグは,ステージへの操作として捉え,前述のとおり図形の中心からマウスポインタの距離によって,それが図形の上で行われたかどうか判定する(前掲図1参照⁠⁠。ステージにおけるドラッグは,つぎのように3つのマウスイベントで扱う※1⁠。

ドラッグを3つのマウスイベントで扱う
  1. Stage.stagemousedownイベント
    • ドラッグを始める
    • ふたつのイベントリスナーを登録
      • Stage.stagemousemove
      • Stage.stagemouseup
  2. Stage.stagemousemoveイベント
    • インスタンスの位置をマウス座標に合わせて動かす
  3. Stage.stagemouseupイベント
    • ドラッグを終える
    • ふたつのイベントリスナーを削除
      • Stage.stagemousemove
      • Stage.stagemouseup

図形をマウスでドラッグするために加えるJavaScriptコードは,以下に抜書きしたとおりだ。Stage.stagemousemoveイベントのリスナー関数(startDrag())は,初期化の関数(initialize())で加える。

このドラッグを始めるリスナー関数(startDrag())では,マウスボタンを押したポインタの座標(mousePoint)と中心点(centerPoint)との距離(distance)が多角形の頂点をつくった半径(_radius)より小さいときに,図形上の操作とみなしてStage.stagemousemoveStage.stagemouseupイベントのリスナー関数(drag()とstopDrag())をそれぞれ加える。

ドラッグのリスナー関数(drag())は,図形の中心点(centerPoint)をポインタ座標(mousePoint)に合わせて動かす。ただし,今回使っている仕組み(ベレ法)では,点の移動は力を加えることになる。あまり大きな力をかけると,アニメーションが乱れてしまう。そのため,ここでもマウスポインタが図形の上にある(距離が半径より小さい)ことを条件とした。ポインタが図形から外れたら,ドラッグ終了の関数(stopDrag())を呼んでいる。

ドラッグを終えるリスナー関数(stopDrag())では,前述のとおりStage.stagemousemoveStage.stagemouseupイベントのリスナー関数(drag()とstopDrag())が除かれている。

var offset;
function initialize() {

  stage.addEventListener("stagemousedown", startDrag);
}

function startDrag(eventObject) {
  var mousePoint = new VerletPoint(eventObject.stageX, eventObject.stageY);
  var centerPoint = _points[0];
  var distance = mousePoint.getDistance(centerPoint);
  if (distance < _radius) {
    offset = mousePoint.subtract(centerPoint);
    stage.addEventListener("stagemousemove", drag);
    stage.addEventListener("stagemouseup", stopDrag);
  }
}
function drag(eventObject) {
  var mousePoint = new VerletPoint(eventObject.stageX, eventObject.stageY);
  var centerPoint = _points[0];
  if (mousePoint.getDistance(centerPoint) < _radius) {
    var movePoint = mousePoint.subtract(offset);
    centerPoint.x = movePoint.x;
    centerPoint.y = movePoint.y;
  } else {
    stopDrag(null);
  }
}
function stopDrag(eventObject) {
  stage.removeEventListener("stagemousemove", drag);
  stage.removeEventListener("stagemouseup", stopDrag);
}

細かいので後に回した説明が,ひとつ残っている。ドラッグを始めるリスナー関数(startDrag())の中で,変数(offset)に納めたマウスポインタと中心点の差の使い途だ。図形の中心点をそのままポインタ座標に合わせると,マウスポインタがつねに図形の中心になるように動く。しかし,図形の端からドラッグを始めたら,ポインタが端にあるまま図形を動かしたい。そこで,ドラッグするリスナー関数(drag())では,マウスポインタの座標(mousePoint)から変数にとった差の値を差し引いて,ポインタと図形の位置関係を保っている。

図形のドラッグを加えたscript要素は,つぎのコード2にまとめた。点と棒のクラスVerletPointとVerletStickは,前掲第31回コード3第31回コード6のまま変えていない。jsdo.itにもサンプルを掲げた。これで,弾力のある八角形を,ドラッグで放り投げることができる。なお,前述のとおり仕様として,ドラッグするマウスポインタが図形の外に出ると,マウスボタンを放したのと同じようにドラッグは終わってしまう。勢いをつけたかったら,マウスは徐々に速く動かすのがコツだ。

コード2 弾力のある八角形をドラッグで放る

var stage;
var drawingGraphics;
var _points = [];
var _sticks = [];
var _stageRect;
var velocityX = 5;
var velocityY = 0.05;
var _radius = 50;
var offset;
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(
    _radius / 8,
    _radius / 8,
    canvasElement.width - _radius / 4,
    canvasElement.height - _radius / 4
  );
  makePoints(100, 70, 50, 8);
  makeSticks();
  _points[0].x += velocityX;
  createjs.Ticker.timingMode = createjs.Ticker.RAF;
  createjs.Ticker.addEventListener("tick", draw);
  stage.addEventListener("stagemousedown", startDrag);
}
function draw(eventObject) {
  updatePoints();
  updateSticks();
  drawingGraphics.clear();
  renderPoints();
  renderSticks();
  stage.update();
}
function makePoints(centerX, centerY, radius, vertices) {
  var angle = -Math.PI / 2;
  var theta = 2 * Math.PI / vertices;
  _points.push(new VerletPoint(centerX, centerY));
  for (var i = 0; i < vertices; i++) {
    var x = centerX + radius * Math.cos(angle);
    var y = centerY + radius * Math.sin(angle);
    _points.push(new VerletPoint(x, y));
    angle += theta;
  }
}
function makeSticks() {
  var count = _points.length;
  for (var i = 0; i < count - 1; i++) {
    for (var j = i + 1; j < count; j++) {
      _sticks.push(new VerletStick(_points[i], _points[j], null, 0.05));
    }
  }
}
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 updateSticks() {
  var count = _sticks.length;
  for (var i = 0; i < count; i++) {
    var stick = _sticks[i];
    stick.update();
  }
}
function renderPoints() {
  var count = _points.length;
  for (var i = 0; i < count; i++) {
    var point = _points[i];
    point.render(drawingGraphics);
  }
}
function renderSticks() {
  var count = _sticks.length;
  for (var i = 0; i < count; i++) {
    var stick = _sticks[i];
    stick.render(drawingGraphics);
  }
}
function startDrag(eventObject) {
  var mousePoint = new VerletPoint(eventObject.stageX, eventObject.stageY);
  var centerPoint = _points[0];
  var distance = mousePoint.getDistance(centerPoint);
  if (distance < _radius) {
    offset = mousePoint.subtract(centerPoint);
    stage.addEventListener("stagemousemove", drag);
    stage.addEventListener("stagemouseup", stopDrag);
  }
}
function drag(eventObject) {
  var mousePoint = new VerletPoint(eventObject.stageX, eventObject.stageY);
  var centerPoint = _points[0];
  if (mousePoint.getDistance(centerPoint) < _radius) {
    var movePoint = mousePoint.subtract(offset);
    centerPoint.x = movePoint.x;
    centerPoint.y = movePoint.y;
  } else {
    stopDrag(null);
  }
}
function stopDrag(eventObject) {
  stage.removeEventListener("stagemousemove", drag);
  stage.removeEventListener("stagemouseup", stopDrag);
}
※1
2のインスタンスを動かす処理は,Stage.stagemousemoveでなく,Ticker.tickイベントで扱う場合もある。具体的な例は,第9回「ドラッグでアルファマスクを描く」マウスポインタの座標を直線で結ぶをご覧いただきたい。

見た目をボールにしてみる

今回まで取り組んできたお題は,とりあえずこれでできあがりとしたい。後は,読者のみなさんがそれぞれ試していただきたい。ひとつの例として,見た目をボールにしてみよう。前掲コード2からのおもな書替えはふたつだ。第1に,丸く見えるように頂点の数を増やす。第2は,点と棒ではなく,輪郭と塗りで描く。なお,それにともなって,パラメータも少し変えた方がよいかもしれない。

筆者は,つぎのように手直ししてみた。jsdo.itにもForkしたコードを掲げたので,興味があったら比べてみてほしい。頂点数を増やすと円に近づくものの,計算量も増えるし,点と棒の互いの影響が強まって,動きが緩慢になる。棒の固さや重力など,試しながら気に入った値を選ぶとよいだろう。

var velocityX = 50;   // 5;
var velocityY = 0.2;   // 0.05;
function initialize() {

  makePoints(100, 70, 50, 24);   // 8);
}
function draw(eventObject) {

  // renderPoints();
  // renderSticks();
  renderShape();

}

/*
function renderPoints() {
  // ...[中略]...
}
function renderSticks() {
  // ...[中略]...
}
*/
function renderShape() {
  var count = _points.length;
  var point = _points[count - 1];
  drawingGraphics.beginFill("cyan")
  .beginStroke("blue")
  .setStrokeStyle(1)
  .moveTo(point.x, point.y);
  for (var i = 1; i < count; i++) {
    point = _points[i];
    drawingGraphics.lineTo(point.x, point.y);
  }
  drawingGraphics.endFill();
}

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書