HTML5テクニカルノート 速度から位置を決めるアニメーション
|
ID: FN1407001 | Technique: HTML5 and JavaScript | Library: EaselJS 0.7.1 |
物理的なアニメーションを表現しようとするとき、速度から位置を決めることがよくあります。直接位置を計算しようとするより、速度から導いた方が複雑なアニメーションも簡潔に表せたりするからです。これは数学的には、微分により物体の運動を捉えていることになります(RAVCO「運動方程式」参照)。微分の計算そのものはしなくても、考え方を知るだけで見通しはよくなり、応用の幅も広がます。いくつかのアニメーションのサンプルをもとにご説明しましょう[*1]。
引用: 結城浩「位置・速度・加速度」
私「位置→速度→加速度という三つの量。位置の変化が速度。速度の変化が加速度」
... [中略]...私「さっきの話に戻るけれど、位置から速度を求めることと、速度から加速度を求めることは、数学的には同じだ」
長男「へえ」
私「この計算が、微分なんだ」
[*1] 本稿は、2014年7月19土曜日に催された第18回「Creators MeetUp」で務めた15分間の一席をもとに加筆・補正して、数学的な説明を補ったノートです。そのビデオが公開されましたので、ご参考までに添えます。また、そのとき使ったレジュメも公開します。 |
ひとつの直線の上を同じ速さで進むのが等速直線運動です。つまり、位置(x)は経過した時間(t)に比例して動きます。したがって、初めの位置を0とすると、比例係数(a)を用いてつぎの1次式で表されます。
x = at
アニメーションのスクリプティングでは、時間の単位をフレームにすると扱いやすくなります。毎フレーム呼ばれる処理で時間の変数(time)を加算して、上記の1次式のとおり比例係数(param_a)と時間との積をオブジェクト(myShape)の座標(y)として与えればよいのです。
- time++;
- myShape.y = param_a * time;
しかし、等速直線運動をこのように書くことはまずありません。時間あたりの位置の変化つまり速度(ピクセル/フレーム)を予め決めて、フレームごとに位置に加える方が扱いやすいからです。
位置 += 速度
前掲の等速直線運動のスクリプトは、おなじみのつぎの1行で書替えられます。すると、時間をもつ変数(time)も要りません。実際のアニメーションのコードは、サンプル001でjsdo.itに掲げました(行番号はこのサンプルにもとづきます)。
- myShape.y += velocity;
サンプル001■等速直線運動
数学では一般に、速度(v)は位置(x)を表す方程式の時間(t)による微分(x')として示されます(結城浩「責任を伴う加速装置(前編)」参照)。上記1次式を微分すると定数(a)になります[*2]。この値が前掲スクリプトの速度(velocity)です。この速度を、時間単位が経過するたびに位置に加えれば、運動のアニメーションができあがります。
v = x' = a
[*2] y = kxnのxによる微分(y')は、つぎのようにxの次数(n)がひとつ減って係数に掛かります。なお、kは定数です。 y' = nkxn-1 一般に、つぎのようなn次関数f(x)を考えます。各項の係数k0~knは定数とします。 f(x) = knxn + kn-1xn-1+ … + k2x2 + k1x + k0 すると、この関数を微分して得られる関数(「導関数」という)f'(x)は、つぎのとおりです(Wikibooks「高等学校数学II 微分・積分の考え」1.1「微分係数と導関数」参照)。 f'(x) = nknxn-1 + (n-1)kn-1xn-2 + … + 2k2x + k1 |
自由落下は、宙に置かれた物体が重力で落ちる運動を考えます。その位置(x)は時間(t)の2乗に比例して動きます。比例係数(a)を定め、初めの位置を0とすると、つぎの2次式で表されます(「落体の運動」参照)。
x = at2
フレームごとに処理されるスクリプトに当てはめれば、カウントアップする時間を変数(time)にして、その2乗と係数(param_a)の積で位置を定めることになります。
- time++;
- myShape.y = param_a * time * time;
けれどこの場合も、速度から決めた方が扱いやすいでしょう。ただし、自由落下運動は2次方程式なので、微分で導かれる速度(v)が時間(t)に比例して増します。この比例係数(2a = A)が加速度です。
v = x' = 2at = At
フレーム単位のアニメーションで考えると、フレームごとに速度に加速度を足し込みます。その速度をさらに位置に加え続ければ、加速度のついたアニメーションが表せます。
速度 += 加速度
位置 += 速度
この考え方で書替えたのが、つぎのスクリプトです。アニメーションのコードはサンプル002としてjsdo.itに掲げました。なお、自由落下運動の加速度(acceleration)は定数です[*3]。
- velocity += acceleration;
- myShape.y += velocity;
サンプル002■自由落下運動
[*3] 自由落下運動に加わるのは重力加速度です。その値は9.806 65 m/s2とされています。 |
イーズアウトというのは、目標の位置に近づくほど動きが遅くなるアニメーションです(図001)。つまり、目標値と現在値との差に速度を比例させています。インターフェイスなどでおなじみの動きでしょう。なお、比例係数(減速率)には0から1の間の数値を定めます。
速度 = (目標値 - 現在値) * 減速率 (0 < 減速率 < 1)
位置 += 速度
図001■目標値に近づくほど動きが遅くなる
ここで注目していただきたいのは、予め位置を求める式は定めていないことです。先に速度の式を決めてしまい、その値を加えることで位置を導いています。このような考え方は、とくに動的に目標値が変わるときに、式が立てやすくなります。
サンプル003は、オブジェクト(myShape)がマウスポインタの後を追いかけます。ポインタから離れると速く、近づくほど遅くなるイーズアウトの動きです。オブジェクトの位置は、つぎに抜書きしたとおり、上記のイーズアウトの式をそのままxy座標に用いています(第14〜17行目)。
- var velocityX = (stage.mouseX - myShape.x) * ease;
- var velocityY = (stage.mouseY - myShape.y) * ease;
- myShape.x += velocityX;
- myShape.y += velocityY;
サンプル003■イーズアウトの動き
バネの伸び縮みの動き(単振動)を見せたいときは、三角関数のsinまたはcosを使うのが定石です(図002)。時間(t)あたりに決まった角度(ω)回転する三角関数(sin)の値に半径(r)を掛合わせれば位置(x)が求められます(サンプル004)。つぎの式は、振動の中心位置を0とします。
x = r・sin(ωt)
図002■三角関数sinに比例した値をオブジェクトの垂直座標に与えたバネの動き
引用: Wikipedia「Harmonic oscillator」
サンプル004■単振動
このバネの動きを取入れて、オブジェクトにマウスポインタを追いかけさせましょう。動的に目標値が変わるので、微分して速度から位置を決めた方がよさそうです。ところが、つぎのように三角関数を微分しても扱いやすい式になりません。しかも困るのは、目標値と絡められる変数が見当たらないことです。
v = x' = rω・cos(ωt)
そこで、イーズアウトと同じように位置の式は置いて、速度から決めることにしましょう。すると、速度は目標値から遠ざかるほど遅くなる、となりそうです。けれど、これでは足りません。この決まりを当てはめたのでは、目標値から遠ざかったら、速度はかぎりなく0に近づいてそのまま戻ってこなくなるからです。ではどうするかというと、さらに速度を微分します。
速度の微分は加速度です。加速度は、重力と同じで力と捉えられます。バネの動きでは、目標値から離れるほど、その遠さに比例して戻る力が強まります。これをイーズアウトのときと同じ考え方で、加速度を定める式にします。
では、加速度の式で求めた値は、どうすればよいでしょう。加速度は速度の微分です。その速度は位置の微分でした。そして、速度は位置に足し込みました。ですから同じように、加速度も速度に加えればよいのです。したがって、つぎの式にしたがって係数で調整すれば、バネのように伸び縮みをしながら目標値にたどり着く動きになります(図003)。
加速度 = (目標値 - 現在値) * 調整係数
速度 += 加速度
速度 *= 減衰係数
位置 += 速度
図003■伸び縮みが減衰して目標値にたどり着くバネのような動き
前述のバネの動きの位置を表す式には、三角関数が用いられていました。そして、それを微分した速度の式にも三角関数が表れました。ところが、今ご説明した考え方で組立てた式には三角関数などなく、四則演算だけでバネのような動きができています。これが、微分の「考え方」を使ったスクリプティングなのです。
- var accelerationX = (stage.mouseX - myShape.x) * ease;
- var accelerationY = (stage.mouseY - myShape.y) * ease;
- velocityX += accelerationX;
- velocityY += accelerationY;
- velocityX *= friction;
- velocityY *= friction;
- myShape.x += velocityX;
- myShape.y += velocityY;
サンプル005■バネのような動き
gihyo.jp連載「HTML5のCanvasでつくるダイナミックな表現―CreateJSを使う」第23回「マウスポインタの軌跡を滑らかな線で描きながら消す」から2回にわたって、つぎのサンプル006のつくり方を解説しました。描かれる線にバネのような動きを与えるために、サンプル005と同じ処理を用いています(第33〜38行目)。
サンプル006■EaselJS 0.7.1: Smooth Line tuned
バネの動き(単振動)について、興味ある読者向けに数学的な説明を補います。バネ運動の位置(x)の方程式とそれを微分した速度(v)の導関数は、前述のとおりです(以下の(1)と(2))。
x = r・sin(ωt) … (1)
v = x' = rω・cos(ωt) … (2)
一般に、三角関数を微分した式にはやはり三角関数が含まれるので、そのままでは扱いやすくなりません。速度を微分した加速度(a)は、つぎの方程式(3)で表されます。
a = v' = -rω2・sin(ωt) … (3)
しかし、式(1)と(3)を連立すると、つぎの方程式(4)のように三角関数が除けます。角速度(ω)は定数ですので、この式は加速度が原点と現在位置との差(x)に比例することを示します。つまり、前項04で決めた加速度の式は、数学的にも正しいのです。
a = -ω2x … (4)
数学的な説明をもうひとつつけ加えます。単位時間ごとに加速度は速度に、さらにその速度を位置に加えるというのは、微分の考え方にはもとづくものの、厳密には微分の近似値を求めています。この近似計算は「オイラー法」と呼ばれます。正確な運動のシミュレーションでなく、物理的なアニメーション表現であれば、誤差は気にならないでしょう。そして、これまで見てきたとおり、計算は簡単で扱いやすくなります。
作成者: 野中文雄
更新日: 注[*1]に第18回「Creators MeetUp」のビデオを追加。
作成日: 2014年7月19日