前回の第18回「クラスの継承と透視投影」では、Shapeのサブクラスとして3次元座標のボールのクラスを定め、100個のオブジェクトをランダムな向きに落としたうえで、2次元平面に透視投影した。今回は仕上げとして、3つ手を加える。まず、ボールを床で弾ませる。つぎに、ステージから見えなくなったオブジェクトは、メモリを無駄に使わないように片づける。そして、3次元の表現で忘れてならないのは、オブジェクトの重ね順を整えることだ。
ボールを床で弾ませる
第18回コード2「100個つくったオブジェクトの3次元座標をを透視投影してランダムな向きに落とす」を書き替えていく。まず、ボールを弾ませるには、以下のようなコードを加える。弾ませる床の垂直位置は変数(floor)に与えた。そして、ボールを落とすアニメーションの関数(move())で、床より落ちたボール(ball)の垂直速度(velocityY)を逆転し、床にめり込んでしまった垂直位置(realY)はその分持ち上げる。
これで、ボールは床で弾むようになる。このアニメーションの処理は、第13回「モーションブラーと弾むアニメーション」の「オブジェクトをステージ下端で弾ませる」から抜き出した次のコードと基本的に同じだ。理解に不安のある読者は、こちらの解説をお読みいただきたい。
つぎに、要らなくなったオブジェクトをメモリから除こう。やはりボールを落とすアニメーションの関数(move())で、以下のコードのようにふたつの軸で条件を与えた。ひとつは、透視投影したボールのx座標だ。この値がステージの端(0またはstageWidth)から外に出れば、見えなくなってもう戻ってくることはない。
もうひとつ、z座標(realZ)も考える。透視投影では、スクリーンの位置をz座標0として、視点は焦点距離下がった(マイナスの)位置に置く(再掲第16回図6)。すると、焦点距離の分手前(-focalLength)まで迫ったオブジェクトは眼の前にあり、さらに下がれば視点の後方に消える。したがって、そのオブジェクトも除いて構わない。
ステージから消すオブジェクトは、Stageオブジェクトの表示リストからContainer.removeChild()メソッドで除く。さらに、第18回コード2では、インスタンスを配列(balls)に入れてまとめた。この配列エレメントも削除したい。だが、配列エレメントはインデックスで指定しなければならない。
EaselJS 0.7.0で新たに備わったindexOf()関数に、配列と取出したいエレメントを引数に渡すと、そのエレメントのインデックスが返される。インデックスがわかれば、Array.splice()メソッドで配列エレメントは除ける。indexOf()はクラスに属さないグローバルな関数なので、名前空間「createjs」に続けて直接参照して呼び出す。
これで、3次元空間にランダムに落ちたボールは床で弾む。そして、ステージから外れたオブジェクトは、参照が除かれ、やがてメモリからも消える(第14回「オブジェクトの使い回しとアニメーション素材の変更」の「ガベージコレクションを減らす」参照)。書直したscript要素のJavaScript全体は、つぎのコード1のとおりだ。
残るは、オブジェクトの重ね順だ。このお題では、ボールの大きさはすべて同じにした。そのため、透視投影にしたがえば、手前のボールほど大きく映り、小さいボールがその前にあってはいけない(図1)。この重ね順を正しく整えたい。
オブジェクトの重なりをz座標値の順に並べ替える
CreateJSでは、インスタンスは親オブジェクトの表示リストに加えられた順に手前に重なる。重ね順はあえて動かさないかぎり、ずっとそのままだ。重なりのインデックスは親インスタンス(今回はStageオブジェクト)のContainer.setChildIndex()メソッドで変えられる。
すると、各ボールのz座標値の順序をどうやって調べるかだ。実は、そのためにオブジェクトを配列(balls)に納めておいた。配列にはArray.sort()という並べ替えのメソッドが備わっている。もちろん、私たちが定義したクラス(Ball3D)のプロパティ(realZ)で、何もいわずに都合よく並べ替えてはくれはしない。どう並べ替えるのかは比較関数というかたちで決めて、Array.sort()メソッドに渡す。
Array.sort()メソッドは比較関数を渡されると、並べ替えるエレメントをふたつ取り出しては関数にその順序を尋ねる[1]。比較関数はそのふたつのエレメントを引数に受け取って、どちらが先になるのかを数値で返す。戻り値は、順序を入替えるなら正数、そのままでよければ負数、等しい(つまりそのままの)ときは0とする。
オブジェクトの重なりをz座標値(realZ)の順に整える関数(sortZ())は、配列(balls)を参照してArray.sort()メソッドでつぎのような比較関数(compare())により並べ替えればよい。配列エレメントは、z座標値の大きい(奥からの)順に揃う。
そのうえで、オブジェクトの重なりを整える関数(sortZ())は、forループで配列(balls)のエレメントを順にすべて取り出して表示リストの中のインデックスをContainer.setChildIndex()メソッドで並べ替える。配列エレメントはz座標値が大きい順なので、表示リストのインデックスはいずれも0に定めれば、後から加えたオブジェクトほど奥に置かれていく。
これで、ボールのオブジェクトの重なりはz座標値の順に整えられる。手を加えたscript要素のJavaScript全体は、つぎのコード2のとおりだ。アニメーションが目で確かめづらいときは、ボールのインスタンスにアウトラインを描き、焦点距離は短くして遠近感を強め、フレームレートも落としてみるといいだろう(図2)。
jsdo.itにもコード2のサンプルを掲げた。ただし、背景色は黒に定めてある。「Fireworks」(花火)というタイトルにイメージが近づいたのではないか。