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

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

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

前回の第32回弾力のある多角形を放物線状に落とすの解説では,弾力のある四角形をつくり,さらにパラメータを変えて八角形なども試してみた。第31回にお題として示したのは,つぎのjsdo.itのコードだった。違いは,中心に点が加えられていることだ。だが,これだけではおもしろくない。今回は,弾力のある多角形を,ドラッグして放れるようにしよう。

描画をドラッグするには

画面に見えるものをドラッグさせようとするとき,そのオブジェクトへのマウス操作(イベント)で扱うのがお約束だ。たとえば,図形を描いたShapeオブジェクトであれば,その上でマウスボタンを押して,マウスを動かし,ボタンを放すという操作に応じてオブジェクトの位置が変わればよい。

ところが,今回Shapeオブジェクトはあくまでかたちを描く対象なので,位置は動かない(動いても意味がない)。Shapeオブジェクトはそのままで,絵をドラッグしたいというのが,今回の課題になる。幸いお題で描くのは,正多角形だ。頂点数がある程度あれば,円に近いとみなせる。

そうであれば,図形の中心とマウスポインタとの距離で,マウス操作がその上で行われているかどうか調べられる。近似した円の半径より距離が小さければ,ポインタは図形に重なっていると決めればよい図1)。ゲームの当たり判定などでも,よく使われる考え方だ。計算が簡単なので,処理の速さも稼げる。

図1 中心からマウスポインタまでの距離と近似した円の半径で重なりを決める

図1 中心からマウスポインタまでの距離と近似した円の半径で重なりを決める

八角形に中心点を加えて放物線状に落とす

そこで,中心にVerletPointオブジェクトを加えたうえで,正八角形をつくろう。第32回コード2点と棒でつくった四角形を放物線状に落とすに手を加える。中心点のオブジェクトを加えるには,頂点をつくる関数(makePoints())に1行書き足すだけだ。頂点をつくるforループに入る前に,つぎのように頂点の配列(_points)に中心点のVerletPointオブジェクトを納めればよい。そして,四角形から八角形に頂点数を増やすのに併せて,パラメータも少しいじった(第32回の「パラメータを変えて試してみようの項参照)。

var velocityY = 0.05;   // 0.25;

function initialize() {

  makePoints(100, 70, 50, 8);   // 4);

}

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() {

  // _sticks.push(new VerletStick(_points[i], _points[j]));
  _sticks.push(new VerletStick(_points[i], _points[j], null, 0.05));

}

第32回コード2にまだ少しの手直ししかしていない。しかし,参照しやすいようにコード1としてまとめておこう。また,ふたつのクラスVerletPointとVerletStickは第31回のままだ。これらも併せて掲げておく。このコードが,基本的に第31回にお題として示した動きだ。ただし,細かいコードは少し気分で変えているため,確認の意味も含めてjsdo.itのサンプルも添えた。

第31回コード3 点を定めるクラス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.prototype.subtract = function(_point) {
  var subtractedPoint = new VerletPoint(this.x - _point.x, this.y - _point.y);
  return subtractedPoint;
};
VerletPoint.prototype.getLength = function() {
  var dx = this.x;
  var dy = this.y;
  var length = Math.sqrt(dx * dx + dy * dy);
  return length;
};
VerletPoint.prototype.getDistance = function(_point) {
  var distancePoint = this.subtract(_point);
  return distancePoint.getLength();
};

第31回コード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);
};

コード1 中心点の加わった八角形を放物線状に落とす

var stage;
var drawingGraphics;
var _points = [];
var _sticks = [];
var _stageRect;
var velocityX = 5;
var velocityY = 0.05;
var _radius = 50;
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);
}
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);
  }
}

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入