前回の第32回「弾力のある多角形を放物線状に落とす」の解説では、弾力のある四角形をつくり、さらにパラメータを変えて八角形なども試してみた。第31回にお題として示したのは、つぎのjsdo.itのコードだった。違いは、中心に点が加えられていることだ。だが、これだけではおもしろくない。今回は、弾力のある多角形を、ドラッグして放れるようにしよう。
描画をドラッグするには
画面に見えるものをドラッグさせようとするとき、そのオブジェクトへのマウス操作(イベント)で扱うのがお約束だ。たとえば、図形を描いたShapeオブジェクトであれば、その上でマウスボタンを押して、マウスを動かし、ボタンを放すという操作に応じてオブジェクトの位置が変わればよい。
ところが、今回Shapeオブジェクトはあくまでかたちを描く対象なので、位置は動かない(動いても意味がない)。Shapeオブジェクトはそのままで、絵をドラッグしたいというのが、今回の課題になる。幸いお題で描くのは、正多角形だ。頂点数がある程度あれば、円に近いとみなせる。
そうであれば、図形の中心とマウスポインタとの距離で、マウス操作がその上で行われているかどうか調べられる。近似した円の半径より距離が小さければ、ポインタは図形に重なっていると決めればよい(図1)。ゲームの当たり判定などでも、よく使われる考え方だ。計算が簡単なので、処理の速さも稼げる。
八角形に中心点を加えて放物線状に落とす
そこで、中心にVerletPointオブジェクトを加えたうえで、正八角形をつくろう。第32回コード2「点と棒でつくった四角形を放物線状に落とす」に手を加える。中心点のオブジェクトを加えるには、頂点をつくる関数(makePoints())に1行書き足すだけだ。頂点をつくるforループに入る前に、つぎのように頂点の配列(_points)に中心点のVerletPointオブジェクトを納めればよい。そして、四角形から八角形に頂点数を増やすのに併せて、パラメータも少しいじった(第32回の「パラメータを変えて試してみよう」の項参照)。
第32回コード2にまだ少しの手直ししかしていない。しかし、参照しやすいようにコード1としてまとめておこう。また、ふたつのクラスVerletPointとVerletStickは第31回のままだ。これらも併せて掲げておく。このコードが、基本的に第31回にお題として示した動きだ。ただし、細かいコードは少し気分で変えているため、確認の意味も含めてjsdo.itのサンプルも添えた。
図形をドラッグして放る
それでは、弾力のある八角形をドラッグして放り投げられるようにしたい。ドラッグは、ステージへの操作として捉え、前述のとおり図形の中心からマウスポインタの距離によって、それが図形の上で行われたかどうか判定する(前掲図1参照)。ステージにおけるドラッグは、つぎのように3つのマウスイベントで扱う[1]。
- ドラッグを3つのマウスイベントで扱う
- Stage.stagemousedownイベント
- ドラッグを始める
- ふたつのイベントリスナーを登録
- Stage.stagemousemove
- Stage.stagemouseup
- Stage.stagemousemoveイベント
- Stage.stagemouseupイベント
- ドラッグを終える
- ふたつのイベントリスナーを削除
- Stage.stagemousemove
- Stage.stagemouseup
図形をマウスでドラッグするために加えるJavaScriptコードは、以下に抜書きしたとおりだ。Stage.stagemousemoveイベントのリスナー関数(startDrag())は、初期化の関数(initialize())で加える。
このドラッグを始めるリスナー関数(startDrag())では、マウスボタンを押したポインタの座標(mousePoint)と中心点(centerPoint)との距離(distance)が多角形の頂点をつくった半径(_radius)より小さいときに、図形上の操作とみなしてStage.stagemousemoveとStage.stagemouseupイベントのリスナー関数(drag()とstopDrag())をそれぞれ加える。
ドラッグのリスナー関数(drag())は、図形の中心点(centerPoint)をポインタ座標(mousePoint)に合わせて動かす。ただし、今回使っている仕組み(ベレ法)では、点の移動は力を加えることになる。あまり大きな力をかけると、アニメーションが乱れてしまう。そのため、ここでもマウスポインタが図形の上にある(距離が半径より小さい)ことを条件とした。ポインタが図形から外れたら、ドラッグ終了の関数(stopDrag())を呼んでいる。
ドラッグを終えるリスナー関数(stopDrag())では、前述のとおりStage.stagemousemoveとStage.stagemouseupイベントのリスナー関数(drag()とstopDrag())が除かれている。
細かいので後に回した説明が、ひとつ残っている。ドラッグを始めるリスナー関数(startDrag())の中で、変数(offset)に納めたマウスポインタと中心点の差の使い途だ。図形の中心点をそのままポインタ座標に合わせると、マウスポインタがつねに図形の中心になるように動く。しかし、図形の端からドラッグを始めたら、ポインタが端にあるまま図形を動かしたい。そこで、ドラッグするリスナー関数(drag())では、マウスポインタの座標(mousePoint)から変数にとった差の値を差し引いて、ポインタと図形の位置関係を保っている。
図形のドラッグを加えたscript要素は、つぎのコード2にまとめた。点と棒のクラスVerletPointとVerletStickは、前掲第31回コード3と第31回コード6のまま変えていない。jsdo.itにもサンプルを掲げた。これで、弾力のある八角形を、ドラッグで放り投げることができる。なお、前述のとおり仕様として、ドラッグするマウスポインタが図形の外に出ると、マウスボタンを放したのと同じようにドラッグは終わってしまう。勢いをつけたかったら、マウスは徐々に速く動かすのがコツだ。
見た目をボールにしてみる
今回まで取り組んできたお題は、とりあえずこれでできあがりとしたい。後は、読者のみなさんがそれぞれ試していただきたい。ひとつの例として、見た目をボールにしてみよう。前掲コード2からのおもな書替えはふたつだ。第1に、丸く見えるように頂点の数を増やす。第2は、点と棒ではなく、輪郭と塗りで描く。なお、それにともなって、パラメータも少し変えた方がよいかもしれない。
筆者は、つぎのように手直ししてみた。jsdo.itにもForkしたコードを掲げたので、興味があったら比べてみてほしい。頂点数を増やすと円に近づくものの、計算量も増えるし、点と棒の互いの影響が強まって、動きが緩慢になる。棒の固さや重力など、試しながら気に入った値を選ぶとよいだろう。