曲線

キャラクターの移動経路や、動きの緩急(加速・減速)を作るための曲線の表し方について説明します。

<注意>
このページでは、数式を表示するためにMathJaxを使用しています。ブラウザの設定でスクリプト実行をブロックしている場合、ページ内の数式が正しく表示されません。

媒介変数で表す曲線

\(\ y = f(x)\ \)の形の式では、同じ\(\ x\ \)に対して\(\ y\ \)が1つしか決まらないので、円や、行って戻ってくる経路は表せません。
そこで、時間のような変数\(\ t\ \)を1つ用意し、\(\ x\ \)と\(\ y\ \)をそれぞれ\(\ t\ \)の式で表します。

\[ (x,\ y) = (x(t),\ y(t)) \]

これを媒介変数表示と言います。
たとえば\(\ (r\cos t,\ r\sin t)\ \)は、\(\ t\ \)を0から\(\ 2\pi\ \)まで動かすと半径\(\ r\ \)の円を描きます(三角関数の「円運動と単振動」)。
ゲームでは、\(\ t\ \)をフレーム数にとれば、そのまま「何フレーム目にどこにいるか」になります。

ベジェ曲線

始点と終点に加えて、曲がり方を決める点(制御点)を与えて曲線を作ります。

定義 (3次ベジェ曲線)
4つの点\(\ P_0,\ P_1,\ P_2,\ P_3\ \)に対して、\(\ 0 \le t \le 1\ \)で

\[ B(t) = (1-t)^3P_0 + 3(1-t)^2t\,P_1 + 3(1-t)t^2\,P_2 + t^3P_3 \]

が描く曲線を3次ベジェ曲線と言う。

3次ベジェ曲線と、tを等間隔に進めたときの点

ベジェ曲線には、次の性質があります。

  • 始点\(\ P_0\ \)と終点\(\ P_3\ \)を通る。\(\ P_1,\ P_2\ \)は一般には通らない。
  • 始点での向きは\(\ P_0\ \)から\(\ P_1\ \)へ、終点での向きは\(\ P_2\ \)から\(\ P_3\ \)へ向かう。
  • 曲線は、4つの点を囲む多角形の内側に収まる。

線形補間の繰り返しで求める

ベジェ曲線の点は、線形補間を繰り返して求めることもできます(ド・カステリョのアルゴリズム)。

  1. 隣り合う点どうしを\(\ t : (1-t)\ \)に分ける点を求める(4点から3点)。
  2. 求めた3点で、同じことをする(3点から2点)。
  3. 求めた2点で、同じことをする(2点から1点)。最後の1点が\(\ B(t)\ \)。

図の左は\(\ t = 0.4\ \)の場合で、緑・オレンジの点が途中で求めた点です。
線形補間のルーチンが1つあれば、式を覚えていなくてもベジェ曲線を描けます。

一定の速さで動かす

\(\ t\ \)を等間隔に進めても、曲線の上を一定の速さで進むとは限りません。
図の右のように、点の間隔は場所によって変わります(この例では、真ん中で遅く、両端で速くなります)。

一定の速さで動かしたいときは、\(\ t\ \)を細かく進めながら曲線の長さを足していった表を作っておき、「進みたい距離」から\(\ t\ \)を逆に引きます。

点を通る曲線

ベジェ曲線は制御点を通らないので、「この点とこの点を通る経路」を作るには不便です。
通りたい点を並べて、その間を補間法の3次補間(キャットムル-ロムの式)でつなぐと、すべての点を通るなめらかな曲線になります。
このように、区間ごとに別の3次式を使い、つなぎ目でなめらかにつながるようにした曲線をスプライン曲線と言います。

3次式を足し算だけで計算する

3次式の値を、\(\ t\ \)を一定の間隔で進めながら順に求める場合は、差分法と数値積分法の差分を使うと、掛け算をせずに済みます。

定理
\(\ n\ \)次式の値を等間隔に並べると、\(\ n\ \)階差分は一定になる。

3次式なら、3階差分が一定です。
最初に、値・1階差分・2階差分・3階差分の4つを計算しておけば、あとは次のように足し算3回で、次の値が求まります。

\[ \Delta^2 \leftarrow \Delta^2 + \Delta^3, \qquad \Delta^1 \leftarrow \Delta^1 + \Delta^2, \qquad f \leftarrow f + \Delta^1 \]

この方法を前進差分法と言います。
固定小数点数で計算すると、足し算のたびに誤差がたまるので、小数部のビット数には余裕を持たせます。

動きの緩急(イージング)

決まった時間で決まった場所へ動かすとき、等速で動かすと、動き始めと止まる瞬間がぎこちなく見えます。
そこで、経過時間の割合\(\ t\ \)(0~1)を、「進んだ割合」に変換する関数を挟みます。

\[ \text{位置} = \text{開始位置} + (\text{終了位置} - \text{開始位置}) \times e(t) \]

いろいろなイージング関数と、同じ時間おきの位置

関数\(\ e(t)\ \)動き
\(\ t\ \)等速
\(\ t^2\ \)ゆっくり動き始めて、加速する
\(\ 1 - (1-t)^2\ \)速く動き始めて、減速して止まる
\(\ 3t^2 - 2t^3\ \)ゆっくり動き始め、ゆっくり止まる

どれも\(\ e(0) = 0,\ e(1) = 1\ \)なので、開始と終了の位置は変わりません。
右の図は、同じ時間おきの位置を点で表したもので、点の間隔が広いところほど速く動いています。

\(\ e(t)\ \)の表を作っておけば、掛け算1回と表引き1回で使えます。
メニューのカーソルや画面の切り替えの動きに使うと、それだけで動きが自然に見えます。

リサジュー曲線

\(\ x\ \)と\(\ y\ \)を、違う周期の正弦波で動かすと、いろいろな形の曲線を描きます。

\[ (x,\ y) = (A\sin(at + \delta),\ B\sin(bt)) \]

リサジュー曲線

\(\ a = b\ \)なら円や楕円、\(\ a : b = 1 : 2\ \)なら8の字になります。
敵がその場で決まった模様を描いて動き回る動きは、正弦の表(角度と三角関数の表)を2回引くだけで作れます。

参照

補間法
差分法と数値積分法
三角関数
線形補間(65816のレシピ)
角度と三角関数の表