今回から取り組む新たなお題をjsdo.itに公開した。マウスポインタの動きに合わせてインスタンスがつくられ、ランダムな向きと速さで落ちていく(図1)。インスタンスにはスプライトアニメーションを用いている。スプライトアニメーションは次回に解説するので、今回はShapeオブジェクトを使う。ランダムな方向に初速を与えて、自然落下させる動きが課題になる。
マウスポインタの動く位置にランダムなカラーと大きさのインスタンスを置く
落下のアニメーションに入る前に、マウスポインタの動く位置にランダムなカラーと大きさのShapeインスタンスを置いてみよう(図2)。使うライブラリはEaselJSなので、script要素に読込んでおく。また、body要素のonload属性で、初期設定の関数(initialize())を呼出すという前提だ。
初期設定の関数は、つぎのようにStage.stagemousemoveイベントにリスナー関数(addInstance())を加える。そして、リスナー関数から改めてShapeインスタンスをつくる関数(createInstance())が呼ばれている。引数には、インスタンスを描くべきマウスポインタのxy座標が渡される。つまり、マウスポインタを動かすと、その座標にインスタンスがつくられるということだ。
Shapeインスタンスをつくる関数(createInstance())は、以下のようにランダムなカラーと大きさで円のShapeインスタンスを別の関数(createShape())でつくる。引数には描くインスタンスの半径の最小値と最大値を渡す。そして、関数から返されたShapeインスタンスの位置を定め、Stageオブジェクトの子として表示リストに加えている。
円のShapeインスタンスをつくって返す関数(createShape())は、ランダムな半径とカラーでインスタンスに円形を描く。最大値と最小値の範囲からランダムな数値を得る処理はほかにも使うので、別に関数(getRandom())として定めた。これで、ステージ上で動かすマウスポインタの位置に、ランダムなカラーと大きさの円形のShapeインスタンスがつぎつぎに描かれていく(前掲図2)。
インスタンスを垂直に落とすアニメーション
ステージにつぎつぎに置いたインスタンスに、ランダムな速さで垂直に落ちるアニメーションを加えよう。アニメーションはTicker.tickイベントで扱うのがお約束だ。そこで、以下のように初期設定の関数(initialize())でイベントリスナー(animate)を加えた。
落ちる速さはインスタンスごとにランダムにしたい。そのため、インスタンスをつくる関数(createInstance())では、垂直方向の初速に加えてフェードアウトするアルファを、ランダムな値でインスタンスのプロパティ(velocityYとvelocityAlpha)に定めている。初速は正負あり、引数(halfSpeed)でランダムな範囲の数値を受け取ることにした。負の初速は、インスタンスを始め上に飛ばしてから、次第に速度を落として落下させる。
Ticker.tickイベントのリスナー関数(animate())は、Stageオブジェクトの表示リストからすべてのインスタンスを取出して、インスタンスに定めた速度(velocityY)とアルファ(velocityAlpha)のプロパティを用いて、落下とフェードアウトのアニメーションにする。メソッドContainer.getNumChildren()は子オブジェクトの数を返し、Container.getChildAt()で引数のインデックスの子オブジェクトが表示リストから取り出せる。速度のプロパティには、重力加速度を表す定数値(2)を加えている。
これで、マウスポインタの動きに合わせてステージに置かれたインスタンスは、ランダムな初速から落下するようになる(図3)。しかし、コードとしては問題がある。インスタンスをただひたすらつくるだけだからだ。ステージの下端を超えても、アルファが完全に透明になっても、すべてのインスタンスにアニメーションの処理は行われ続ける。
インスタンスのアルファが0以下になるか、位置がステージの下端を超えたら、表示リストから消すことにしよう。表示リストのインデックスを渡して子インスタンスを除くのはContainer.removeChildAt()メソッドだ。Ticker.tickイベントのリスナー関数(animate())には、つぎのようなif文の処理を加える。なお、ステージ下端の座標はグローバルな変数(stageHeight)で宣言して、初期設定の関数(initialize())で値を納めた。
ひとつ説明を補っておく。アニメーションの関数(animate())に加えたfor文は、表示リストの子インスタンスをインデックスの大きい順に取り出している。これは、表示リストのインデックス0から順に調べると、子インスタンスを削除したとき、後から取出すインスタンスのインデックスが1ずつ繰り上がって、ずれてしまうからだ。大きい方から調べれば、子インスタンスを除いても、これから取出すインスタンスの番号は狂わない。
これでアルファや垂直位置がステージで見えなくなれば、インスタンスは表示リストから除かれる。アニメーションの見た目(前掲図3)に変わりはなくても、処理の無駄は多いに省けた。script要素全体は、つぎのコード1のとおりだ。
インスタンスの動きに水平方向の初速を加える
落ちるインスタンスの動きに、水平方向の初速も加えよう。簡単なのは、水平方向にもランダムな速度を定めることだ。しかし、今回ランダムな速度はひとつにする。その代わり、ランダムな角度を加える。速度と角度から水平・垂直それぞれの値を決めるのだ。そのためには、三角関数を用いる。
原点O(0, 0)を中心に描いた半径1の円(「単位円」と呼ぶ)について、原点Oからx軸正方向に対して角度θの直線と交わる点Pのxy座標は三角関数により(cosθ, sinθ)と定められている(図4)。つまり、原点からの距離は1で角度がθの点のxy座標は(cosθ, sinθ)ということだ。すると、原点からの距離がrなら、座標は(r cosθ, r sinθ)になる。
この距離rを速さと見立てれば、角度θを与えたときのxy各軸方向の速度が導ける。そこで、以下のインスタンスをつくる関数(createInstance())は、0から2πラジアン(360度)までのランダムな角度(angle)を求め、水平・垂直方向の初速をプロパティ(velocityXとvelocityY)として与えるように書替えた。
また、アニメーションの関数(animate())には、水平方向の動きを加えた。水平には重力は働かないので、基本は等速運動だ。ただ、摩擦などによる減速を表すため、定数(0.98)を乗じた。
これでマウスポインタの動く位置に表れるインスタンスに、ランダムな方向の初速が与えられる(図5)。垂直方向には重力加速度が加わりつつ、水平移動は少しずつ減速する。書替えたJavaScript全体は、つぎのコード2のとおりだ。
前掲コード2をjsdo.itにサンプルコードとして掲げた。今回は、ここまでにしておこう。次回は少し目先を変えてスプライトシートアニメーションについて解説したうえで、今回の弾けるインタラクティブなアニメーションのコードに組み込んでみる。