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

第18回 クラスの継承と透視投影

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

イベントのお知らせ

12月20日,アドビ システムズ株式会社にてCreateJS勉強会~末~が開催されます。本連載の筆者である野中文雄氏が「CreateJS今日この頃: EaselJS 0.7.1で何が変わったのか」というタイトルで発表する予定です。皆さんのご参加をお待ちしています。

前回の第17回簡単なクラスを定義するで予告したとおり,今回のお題は引続きクラス定義と透視投影を使ったコードだ。Keith Peters氏が著書ActionScript 3.0 アニメーションでつくられたサンプルの動きをもとに,CreateJSでスクリプティングした。ユーザーインタラクションは含まれていない。

Shapeクラスを継承する3次元座標のクラス定義

早速,3次元座標のクラスを定める。前回と違うのは,座標だけでなくボールのシェイプも,このクラスでつくってしまうことだ。EaselJSなら,Shapeクラスを使えば円形のシェイプが描ける。では,この機能を丸ごといただこう。こういうとき,プログラミング言語では,Shapeクラスを「継承」して3次元座標のクラスを定義する。

JavaScriptの継承は他の言語と少し変わっている。継承したいクラス(「スーパークラス」)のオブジェクトを,継承するクラス(「サブクラス」)のFunction.prototypeプロパティに代入してしまう。すると,スーパークラスのプロトタイプオブジェクトが,サブクラスのインスタンスから参照できる※1)。そして,そのプロトタイプオブジェクトに備わるメソッドやプロパティが,インスタンスに定められたかのように扱える仕組みなのである。

// サブクラスのコンストラクタ関数の定義
function クラス名() {
  // インスタンスの初期化
}
// スーパークラスの継承
クラス名.prototype = スーパークラスのオブジェクト;

今回,3次元座標のクラス(Ball3D)は,つぎのようにFunction.prototypeプロパティにShapeオブジェクトを与えて継承した。したがって,コンストラクタが受取った引数の半径とカラーで,自らのGraphicsオブジェクトにメソッド(drawBall())で円が描ける。そして,前回のクラスと同じく,コンストラクタで3次元座標をプロパティとして定める。ただし,Shapeクラスにはすでにxyというプロパティが備わっている。そこで,xyの2次元に透視投影した座標をこのプロパティに与えることとし,3次元空間における座標のプロパティは別の名前(realX/realY/realZ)にした。

function Ball3D(radius, color) {
  this.initialize();
  this.radius = radius;
  this.color = color;
  this.realX = 0;
  this.realY = 0;
  this.realZ = 0;
  this.velocityX = 0;
  this.velocityY = 0;
  this.velocityZ = 0;
  this.drawBall(radius, color);
}
Ball3D.prototype = new createjs.Shape();
Ball3D.prototype.move = function(gravity) {
  this.realX += this.velocityX;
  this.realY += this.velocityY;
  this.velocityY += gravity;
};
Ball3D.prototype.drawBall = function(radius, color) {
  this.graphics.beginFill(color)
  .drawCircle(0, 0, radius);
};

前掲クラス(Ball3D)のインスタンスを落とすメソッド(move())は,第11回「マウスポインタの動きに合わせてインスタンスをランダムに落とす」インスタンスの動きに水平方向の初速を加えるから抜き出した,つぎのコードにならった。ただし,メソッドはクラスのインスタンス自身を落とすので,this参照が操作の対象となる。まずは,2次元平面で落としてみる。その速さはxyzのそれぞれの向きごとに,コンストラクタでプロパティ(velocityX/velocityY/velocityZ)に定めた。

なお,クラスのコンストラクタ(Ball3D())が初めに呼び出しているメソッドinitialize()は,Shapeクラスに内部的に定められている初期化のメソッドだ。インスタンスがもつGraphicsオブジェクトも,このメソッドでつくられる。

function animate(eventObject) {
  var count = stage.getNumChildren() - 1;
  for (var i = count; i > -1; i--) {

    var newY = child.y + child.velocityY;

    child.x += child.velocityX;
    child.y = newY;

    child.velocityY += 2;

  }

}

3次元空間のボールのオブジェクトを100個つくって,とりあえず2次元平面でランダムな向きに落とすスクリプトが以下のコード1だ。まず,初期化の関数(initialize())の中のforループでボールのクラスのコンストラクタ(Ball3D())を呼ぶ。ボールのオブジェクトの塗り色(color)とxy方向の速さ(velocityXとvelocityY)は,ランダムに与える。また,つくったオブジェクトは,後で扱いやすいように変数(balls)の配列に入れておく。なお,アニメーションはステージの真ん中を原点とするため,そのxy座標値を変数(centerXとcenterY)に納めた。

アニメーションは,お約束どおりTicker.tickイベントのリスナー(animate())で行う。リスナー関数は,配列(balls)からすべてのオブジェクト(ball)を順に取り出し,ボールの動きを定める関数(move())に渡す。その関数はボールのオブジェクトの3次元座標をクラス(Ball3D)のメソッド(move())で落下させ,その結果を2次元のxy座標(Shape.xShape.yプロパティ)に与えている。この2次元のxy座標は,後で3次元座標から透視投影して定めるつもりだ。

コード1 Shapeのサブクラスのオブジェクトを100個つくって2次元平面でランダムな向きに落とす

function Ball3D(radius, color) {
  this.initialize();
  this.radius = radius;
  this.color = color;
  this.realX = 0;
  this.realY = 0;
  this.realZ = 0;
  this.velocityX = 0;
  this.velocityY = 0;
  this.velocityZ = 0;
  this.drawBall(radius, color);
}
Ball3D.prototype = new createjs.Shape();
Ball3D.prototype.move = function(gravity) {
  this.realX += this.velocityX;
  this.realY += this.velocityY;
  this.velocityY += gravity;
};
Ball3D.prototype.drawBall = function(radius, color) {
  this.graphics.beginFill(color)
  .drawCircle(0, 0, radius);
};
var stage;
var balls = [];
var numBalls = 100;
var stageWidth;
var centerX;
var centerY;
var gravity = 0.2;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  stageWidth = canvasElement.width;
  centerX = stageWidth / 2;
  centerY = canvasElement.height / 2;
  for (var i = 0; i < numBalls; i++) {
    var color = createjs.Graphics.getRGB(getRandom(0, 0xFFFFFF));
    var ball = new Ball3D(3, color);
    balls.push(ball);
    ball.realY = -50;
    ball.velocityX = getRandom(-3, 3);
    ball.velocityY = getRandom(-6, 0);
    stage.addChild(ball);
  }
  createjs.Ticker.addEventListener("tick", animate);
}
function animate(eventObject) {
  for (var i = balls.length - 1; i > -1; i--) {
    var ball = balls[i];
    move(ball);
  }
  stage.update();
}
function move(ball) {
  ball.move(gravity);
  ball.x = centerX + ball.realX;
  ball.y = centerY + ball.realY;
}
function getRandom(min, max) {
  var randomNumber = Math.random() * (max - min) + min;
  return randomNumber;
}

これで,Shapeを継承したクラス(Ball3D)でつくられた100個のボールが,2次元平面でランダムな向きに落ちていく図1)。まだオブジェクトの3次元座標を透視投影しておらず,そもそもz座標値が0のまま動かないため,すべてのボールが同じ大きさのまま下に向かって散らばるだけだ。

図1 100個のボールが2次元平面でランダムな向きに落ちる

図1 100個のボールが2次元平面でランダムな向きに落ちる 図1 100個のボールが2次元平面でランダムな向きに落ちる

※1

厳密には,もちろんFunction.prototypeプロパティの参照は,スーパークラスのオブジェクトで上書きされる。そして実は,すべてのオブジェクトにはObject.__proto__というプロパティが備わり,スーパークラスのプロトタイプオブジェクトはそのプロパティをたどって参照される(MDN「Function.prototype」プロパティの項参照)。

なお,Function.prototypeプロパティの上書きになるので,スーパークラスのオブジェクトは,サブクラスのプロトタイプオブジェクトにメソッドを定める前に代入しなければならない。

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入