前回に引き続きcanvasで記事を書こうと思い、何か動いてて楽しい物をと考えていたのですが、誰もが知っていてプログラムにしやすい物として、スパイダソリティアの花火を思い付きました。私と同じ世代の方なら、家にやってきた初めてのパソコンはWindows XPで、ソリティアなどのゲームをやっていたんではないでしょうか?ソリティアは難しいのでトランプが飛び出るクリア画面はなかなか見られませんでしたが、スパイダソリティアは難易度を選べたのでよく初級で遊んでいました。あとはマインスイーパーとかウサギがめちゃくちゃ強いエアホッケーとか。。。
ProcessingやそれをJavaScriptのライブラリにしたp5.jsを使えばこちらのような花火が出来ますが、そういう便利な物は使わずに自前で描いてみます。
移動処理のベース
実際の花火がどのように運動するかは知りませんが、簡単な物理演算をすることになります。まずは高校の物理で出てきた水平投射、鉛直投げ上げ、斜方投射をJSにしてみました。ややこしくなるので摩擦、空気抵抗、球の質量は無視します。ただし、それっぽく見せるために速度にランダムな要素を入れるなどの調整はします。
前回同様、canvasのxy座標系は左上が0で、xは右、yは下へ+になり、感覚的ではありません。なので計算上はyは上向きのつもりで計算し、描画時に反転するようにしています。
コードを見ると色々書いてありますが、この3つの運動は全て以下の計算で表現されます。x, y成分についてt(時間), ax, ay(加速度), vx0, vy0(初速度), x0, y0(初期位置)で計算します。今回は質量を無視するので、MKS単位系でもCGS単位系でも好きな方で考えてください。
1 2 |
x[i] = ax * t * t / 2 + vx0[i] * t + x0[i]; y[i] = ay * t * t / 2 + vy0[i] * t + y0[i]; |
花火玉を打ち上げる
打ち上げてから開くまでは、花火玉を斜方投射(ということに)します。滑らかに動かすために、intervalSecondやgを調整しています。更に、g = -0.98にしている(浮動小数点数なので本当はもっと桁がありますが)のを、重力よりも勢いよく上がっているように見せるためにay = -0.68としました。画面外に出る前に、2つの花火が交差したあたりで開かせたいので、ここでタイマーストップします。
上記の通り、位置x, yは変数t(プログラミングにおけるvarやletの「変数」ではなく、数学の意味での「変数」)によって決まります。もし加速度a = 0つまり一定の速度で動くと考えるなら、タイマーを実行するたびに(tが1増えるたびに)速度vをx, yに足すようにしても良いです。ゲームのように、常にtが増える場合やt以外の要素(キーボードで移動する操作など)に影響されるなら、tを使わない方がシンプルになります。今回はこのままtを使います。
1 2 3 4 5 6 7 8 9 10 |
// 初回のみ vx[i] = vx0[i]; vy[i] = vy0[i]; x[i] = x0[i]; y[i] = y0[i]; // タイマーの中での処理 vx[i] += ax[i]; vy[i] += ay[i]; x[i] += vx0[i]; y[i] += vy0[i]; |
花火を開く
花火が開くと小さい火花(星)が瞬間的に広がり、その後は自由落下します(ということにします)。花火が開くまでのコードは一旦忘れます。
実行した時に一度explode()を実行し、花火が開く位置をランダムに決めます。角度(方向)と勢い(長さ。速さ。速度の絶対値)もランダムで決めて、x, yの初速度vx0, vy0を定義します。あとは自由落下なのでay = g(重力加速度)で運動させます。火花の数はsparkleNum = 50個としてますが、foreachで回すだけなので難しくありません。
1 2 3 4 |
let tmpAngle = Math.random() * Math.PI * 2; let tmpForce = Math.random() * forceMax; vx0.push(Math.cos(tmpAngle) * tmpForce); vy0.push(Math.sin(tmpAngle) * tmpForce); |
実際の花火玉を画像検索すると星が同心円状に並んでいるので、綺麗に円状に開くような気がしますが、tmpForceを一定の数値にすると綺麗になりすぎるのでランダムにします。
完成
第二段階と第三段階のを合わせますが、配列xなどの要素数が違うのでそのままくっつけることはできません。開く前はsparkleNumを2個、開いた後は50 * 2 = 100個としました。本当はこちらのようにオブジェクトを使う方がすっきりして良いです。
花火が開くまでの動きはinit()で斜めに動くように定義し、花火玉が交差したら(=花火が開いたら)explode()を実行します。全て画面外に出たらまたinit()で最初から実行し直します。左側の玉と右側の玉で色を変えたり、時間が経過すると燃えて小さくなるなどの処理も入れてます。もっと工夫するなら、色を暗くする、少しずつ速度を落とすのも良さそうですね。
まとめ
前回と今回のcanvasや数学的な内容は、データ可視化やゲームといった業務以外では直接扱いません。ですが変数をどう使い回すか、どういう処理の流れにするかなど、プログラミングの練習・息抜きにおすすめです。