前回の第17回「簡単なクラスを定義する」で予告したとおり、今回のお題は引続きクラス定義と透視投影を使ったコードだ。Keith Peters氏が著書『ActionScript 3.0 アニメーション』でつくられたサンプルの動きをもとに、CreateJSでスクリプティングした。ユーザーインタラクションは含まれていない。
Shapeクラスを継承する3次元座標のクラス定義
早速、3次元座標のクラスを定める。前回と違うのは、座標だけでなくボールのシェイプも、このクラスでつくってしまうことだ。EaselJSなら、Shapeクラスを使えば円形のシェイプが描ける。では、この機能を丸ごといただこう。こういうとき、プログラミング言語では、Shapeクラスを「継承」して3次元座標のクラスを定義する。
JavaScriptの継承は他の言語と少し変わっている。継承したいクラス(「スーパークラス」)のオブジェクトを、継承するクラス(「サブクラス」)のFunction.prototypeプロパティに代入してしまう。すると、スーパークラスのプロトタイプオブジェクトが、サブクラスのインスタンスから参照できる[1]。そして、そのプロトタイプオブジェクトに備わるメソッドやプロパティが、インスタンスに定められたかのように扱える仕組みなのである。
今回、3次元座標のクラス(Ball3D)は、つぎのようにFunction.prototypeプロパティにShapeオブジェクトを与えて継承した。したがって、コンストラクタが受取った引数の半径とカラーで、自らのGraphicsオブジェクトにメソッド(drawBall())で円が描ける。そして、前回のクラスと同じく、コンストラクタで3次元座標をプロパティとして定める。ただし、Shapeクラスにはすでにxとyというプロパティが備わっている。そこで、xyの2次元に透視投影した座標をこのプロパティに与えることとし、3次元空間における座標のプロパティは別の名前(realX/realY/realZ)にした。
前掲クラス(Ball3D)のインスタンスを落とすメソッド(move())は、第11回「マウスポインタの動きに合わせてインスタンスをランダムに落とす」の「インスタンスの動きに水平方向の初速を加える」から抜き出した、つぎのコードにならった。ただし、メソッドはクラスのインスタンス自身を落とすので、this参照が操作の対象となる。まずは、2次元平面で落としてみる。その速さはxyzのそれぞれの向きごとに、コンストラクタでプロパティ(velocityX/velocityY/velocityZ)に定めた。
なお、クラスのコンストラクタ(Ball3D())が初めに呼び出しているメソッドinitialize()は、Shapeクラスに内部的に定められている初期化のメソッドだ。インスタンスがもつGraphicsオブジェクトも、このメソッドでつくられる。
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.xとShape.yプロパティ)に与えている。この2次元のxy座標は、後で3次元座標から透視投影して定めるつもりだ。
これで、Shapeを継承したクラス(Ball3D)でつくられた100個のボールが、2次元平面でランダムな向きに落ちていく(図1)。まだオブジェクトの3次元座標を透視投影しておらず、そもそもz座標値が0のまま動かないため、すべてのボールが同じ大きさのまま下に向かって散らばるだけだ。
z軸方向の速さと透視投影のメソッドを加える
2次元平面でボールを落とせたら、つぎはz軸方向の動きを加えて、透視投影する。つまり、3次元で動くボールに、z軸の向きにも速さ(velocityZ)を加える。初期化の関数(initialize())でz軸方向の速さをランダムに定め、ボールのクラス(Ball3D)のオブジェクトを落とすメソッド(move())で、z軸の向きの動きも与える。
3次元で動くボールのクラス(Ball3D)には、さらに透視投影のメソッド(getProjectedData())をつぎのように定める。焦点距離(focalLength)を引数に受け取って、xy座標(xおよびy)とオブジェクトの伸縮率(scale)をObjectインスタンスのプロパティに納めて返す。
参考にしたのは第16回「3次元空間で座標を回す」の「3次元空間座標を透視投影する」から抜き出したつぎの関数だ。ただし、前掲コードは大きさをもつボールのクラス(Ball3D)のメソッド(getProjectedData())なので、透視投影の比率(焦点距離 / (焦点距離 + z位置))を戻り値のオブジェクトに伸縮率のプロパティ(scale)として加えた。
前掲透視投影のメソッド(getProjetedPoint())は、ボールの動きを2次元平面に定める関数(move())から呼び出す。メソッドから返されたObjectインスタンスのプロパティ(scaleおよびxとy)を、ボールの(スーパークラスShapeの)xy座標と水平・垂直伸縮率に与えて透視投影する。なお、透視投影のメソッドに渡す焦点距離は、あらかじめ変数(focalLength)に定めた。
ボール100個が落ちる動きを3次元に拡げたのがつぎのコード2だ。ボールのオブジェクトはz座標値に応じて透視投影され、遠ざかれば小さく、近づけば大きく表示される(図2)。もっとも、今の表現では3次元の動きだといわれなければ、ただオブジェクトが伸縮しながら落ちているだけに見えるかもしれない。
できあがったコードはjsdo.itに掲げた。次回は、3つ手を加えてお題を仕上げるつもりだ。まず、ボールを床で弾ませる。つぎに、ステージから見えなくなったオブジェクトは、メモリから片づけたい。そして、3次元の表現ではもうひとつ忘れてならない処理がある。それは何か。年をまたいでしまうので、お正月の宿題としよう(つぎのjsdo.itのアニメーションをよく見ていただくとヒントがある)。では、よいお年をお迎えいただきたい!