サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

バネのような動きを加速度から定める ー オイラー法

ID: FN1403001 Technique: HTML5 and JavaScript Library: EaselJS 0.7.1

バネが伸び縮みするアニメーションは、「単振動」と呼ばれて三角関数で表すのが定石です。けれど、バネの端をもって振り回すような動きは、加速度にもとづく式で扱うのが便利です。この式は「オイラー法」という数学の考え方から導かれます。


01 三角関数を使ったバネの動き

バネの伸び縮みをアニメーションで見せたいときは、三角関数のsinまたはcosを使うのが定石です。sinとcosは半径1の「単位円」で定義されます。単位円の円周上の点Pと原点O(0, 0)を結ぶ線分がx軸の正方向となす角度をθとすると、点Pの座標は(cosθ, sinθ)と定められています(図001)。

図001■単位円の円周上の座標は原点と結ぶ線分がx軸となす角をθとするとき(cosθ, sinθ)
図001

角度θを等しい速さで増やしてゆくと、sinθとcosθは値がバネの動きのように上下します。横軸を角度、縦軸に三角関数値としたグラフを描くと、sinとcosは波のような曲線を描きます(図002)。ふたつの曲線のかたちは同じで、違うのは始まりの値(角度0)がsinは0でcosは1というタイミング(位相)です。

図002■三角関数sinとcosの角度に対する値の変化
図002

すると、原点(0, 0)を中心とするバネのような上下の動きは、角度θを等しい速さで増やしながら、振れ幅(半径)rにsinθを乗じて垂直座標yに定めればよいことになります(図003)。

y = r sinθ

図003■三角関数sinに比例した値をオブジェクトの垂直座標に与える
図003
引用: Wikipedia「Harmonic oscillator

たとえば、つぎのようなJavaScriptコードを書いて、アニメーションのための関数(move())を決まった間隔で呼出せば、オブジェクト(myShape)はバネのように上下します。以下のjsdo.itのサンプル001は、EaselJSでこのコードを用いました。

var velocity = Math.PI / 40;
var angle = 0;
var radius = 70;

function move(eventObject) {
  myShape.y = radius * Math.sin(angle);
  angle += velocity;

}

サンプル001■三角関数sinでオブジェクトがバネのように上下するアニメーション


02 速度を位置に加えたイーズアウトの動き

加速度を用いたバネの動きに取りかかる前に、速度を位置に加えるアニメーションについてご説明しておきます。目標の位置に近づくほど速度が遅くなるように、いわゆるイーズアウトさせてみましょう。時間と位置のグラフにすると、つぎの図004のようになります。インターフェイスなどでよく使われる動きです。

図004■動きが遅くなりながら目標に近づく
図004

オブジェクトのプロパティを目標値に動かすときイーズアウトさせるには、つぎのような式を使います。まず、目標値と現在値との差をとります。これをそのままプロパティに加えれば、その値はただちに目標値になります。そこで、差の値に0から1の間の減速率を乗じます。そして、これを速度としてプロパティに加えればよいのです。

速度 = (目標値 - 現在値) * 減速率   (0 < 減速率 < 1)
プロパティ += 速度

EaselJSで、上記の式を使ってみましょう。インスタンスにマウスポインタの後を、少し遅れて追いかけさせます。JavaScriptコードには、つぎのようにTicker.tickイベントのリスナー関数(followMouse())を定めます。リスナー関数は、まず目標となるマウスポインタの座標(Stage.mouseX/Stage.mouseYプロパティ)とインスタンスの位置(DisplayObject.x/DisplayObject.yプロパティ)との差に減速率(ease)を乗じて速度(velocityX/velocityY)とします。つぎに、速度をインスタンスの位置座標に加えればイーズアウトのアニメーションです。

var ease = 0.25;
var velocityX = 0;
var velocityY = 0;

createjs.Ticker.addEventListener("tick", followMouse);

function followMouse(eventObject) {
  velocityX = (stage.mouseX - myShape.x) * ease;
  velocityY = (stage.mouseY - myShape.y) * ease;
  myShape.x += velocityX;
  myShape.y += velocityY;
  stage.update();
}

EaselJSを用いたJavaScriptコード全体は、つぎのjsdo.itのサンプル002に掲げました。マウスポインタを動かすと、インスタンスが追いかけ、ポインタに近づくにつれて動きは遅くなります。

サンプル002■マウスポインタを追いかけるイーズアウトのアニメーション


03 加速度を速度に加えて表すバネの動き

バネの動きも、速度を位置に加えて表します。ただし、その速度には加速度が与えられることで、速さを変えます。バネは力の釣合う点(均衡点)から離れるほど、もとの位置に戻ろうとする力が働きます。この力が加速度です。

前項02「速度を位置に加えたイーズアウトの動き」では、目標となるマウスポインタの座標(Stage.mouseX/Stage.mouseYプロパティ)とインスタンスの位置(DisplayObject.x/DisplayObject.yプロパティ)との差に減速率(ease)を乗じて、そのまま速度としました。今回この値は加速度と捉えて、以下のコードのように速度(velocityX/velocityYプロパティ)に加えます。

その速度を位置座標に加えて、インスタンスのアニメーションにするのは前項と同じです。ただし、速度がそのままでは、バネの伸び縮みがいつまで経っても止まりません。そこで、速度に減衰(摩擦)係数(friction)を乗じることにより、揺れ幅が次第に小さくなって、目標値に落着くようにしました。下図005が、時間を水平軸、位置を垂直軸にしたこの式の変化のグラフです。

速度 += (目標値 - 現在値) * 調整係数
速度 *= 減衰係数
プロパティ += 速度

図005■伸び縮みが減衰して目標にたどり着くバネのような動き
図005

var ease = 0.25;
var friction = 0.75;
var velocityX = 0;
var velocityY = 0;

createjs.Ticker.addEventListener("tick", followMouse);

function followMouse(eventObject) {
  // velocityX = (stage.mouseX - myShape.x) * ease;
  // velocityY = (stage.mouseY - myShape.y) * ease;

  velocityX += (stage.mouseX - myShape.x) * ease;
  velocityY += (stage.mouseY - myShape.y) * ease;
  velocityX *= friction;
  velocityY *= friction;
  myShape.x += velocityX;
  myShape.y += velocityY;
  stage.update();
}

前掲サンプル002を書替えたJavaScriptコード全体は、つぎのjsdo.itのサンプル003のとおりです。マウスポインタを動かすと、インスタンスはそれを追いかけながら、ポインタにバネで結ばれたような伸び縮みの動きをします。前述01「三角関数を使ったバネの動き」が中心(均衡点)から位置を決めたのに対して、現在値と目標値が式の変数に含められるので、このような表現には適しています。

サンプル003■マウスポインタを追って伸び縮みするアニメーション

さらに、gihyo.jp連載「HTML5のCanvasでつくるダイナミックな表現―CreateJSを使う」では、つぎの2回にわたって以下のサンプル004のコードを解説しています。

サンプル004■EaselJS 0.7.1: Smooth Line tuned


04 オイラー法 ー 速度に加速度、位置に速度を加える

前項03「加速度を速度に加えて表すバネの動き」では、加速度に速度を加え、その速度を位置に加えてアニメーションにしました。これは「オイラー法」と呼ばれる考え方で、運動の方程式を近似的に表します。

厳密には、時間tに対する位置の関数x(t)を、時間で微分すると速度の関数v(t)が求まります。さらに、速度を時間で微分すると、加速度の関数a(t)が導かれるのです。関数x(t)の時間tによる微分(導関数)は、つぎの式の右辺のように経過時間Δtを0にするときの極限として表されます。

しかし、極限を求める前のつぎの式も、Δtが充分に小さければ近似として使えます。なお、記号「≃」は「ほぼ等しい」ことを示します(日本で用いる「≒」は、国際的にはあまり使われないようです)。

この近似式を変形すると、微細時間Δt経過後の位置x(t + Δt)がつぎの式で求められることになります。つまり、現在の位置x(t)に、そのときの速度v(t)と経過時間Δtの積を加えればよいのです。これがオイラー法の考え方です。

Δtの単位は小さければ構いません。そこで、アニメーションで扱いやすいように時間の単位を1フレームとし、フレームあたりの速度で考えれば、速度と経過時間の掛け算は要りません(×1フレームになるので)。また、加速度についても同じように式が導けます。つまり、オブジェクトの動きを表すスクリプトの式はつぎのようにすればよいのです。

つぎの1フレームあたりの速度 += 1フレームあたりの加速度
つぎのフレームの位置 += 1フレームあたりの速度

前述03「加速度を速度に加えて表すバネの動き」では、このオイラー法の考え方で、加速度から位置のアニメーションを定めました。もっとも、オイラー法をそれと知らずに、すでに使っている人は少なくないでしょう。たとえば、オブジェクトが落ちるとき、重力が加速度になります。すると、オイラー法から、フレームごとのオブジェクトの位置がつぎのように求められます。

つぎのフレームあたりの速度 += フレームあたりの重力(加速度)
つぎのフレームの位置 += フレームあたりの速度

重力は一定ですので、速度には定数を加えることになります。定数で増した速度をオブジェクトの位置に加えて自由落下のアニメーションにしたことがあれば、オイラー法を使っていたということです。



作成者: 野中文雄
更新日: 2014年7月5日 gihyo.jp連載第24回へのリンクを追加。
更新日: 2014年3月15日 03「加速度を速度に加えて表すバネの動き」にgihyo.jp連載の情報とサンプル004を追加。
作成日: 2014年3月5日


Copyright © 2001-2014 Fumio Nonaka.  All rights reserved.