単振動を時間微分して動かす

この世の周期運動は全てサインウェーブの重ね合わせで表現できるーと云うか、任意の波形はサインウェーブで表現できるからーーーかは知りませんが、敵の動きなんかを作るときにはサインウェーブを使うと大抵いい感じになります。

たとえば、地点A(0,0)から地点B(100,0)に60フレーム*1の時間をかけて移動するキャラクタ(x,y)を作りたいとして、素直に考えれば

  x = x + (100/60);

…でOK。上の処理を1/60秒で1回まわるループで実行すれば、めでたく60フレーム(1秒)の時間をかけて100ドット移動するキャラのできあがりです。

ただ、この世の物体は往々にして慣性の支配下にありますので、だんだんスピードを上げていって、x=50のところを最高速に、だんだんスピードを落としてx=100に到達した方が自然に見えます。こういう時には

  static anime = 0;

  anime++;
  x = 50 - 50*cos( PI * anime / 60 );  // PI:円周率

こんな感じ。半円を置いた正弦のグラフを思い浮かべて頂ければ納得できるでしょうか。60フレームかけて山を上がって降ります。

ですが、上の式だと、現在座標のほかに、原点座標とアニメ情報の2つパラメータを必要としてしまいます。パラメータは少ない方がいいですし、時間分割した更新処理をループで回して動作を表現しているアクションゲームのフレームワークだと、一番最初に挙げた

  x = x + dx;    // dx:xの増分

の形にできた方が自然です。そうしましょう。増分を求めるには、時間で微分してあげればよかったのですね。

  x = x + 50*PI/60 * sin( PI * anime / 60 );

なんだけど、なんだけど、係数がけったいなことになるし、分周の細かさとかもあって、誤差がかなりでます。なので、(dx) == (dtあたりのxの増加量)と云う基本にたちかえった方がよいみたいです。現在の位相での値から一手前の位相の値を引くと微分が出ます。

  dx = (50-50*cos(PI*anime/60)) - (50-50*cos(PI*(anime-1)/60);
     = -50*(cos(PI*anime/60) - cos(PI*(anime-1)/60));

つうことで、毎フレームの更新処理を書くと

  static anime = 0;
  anime++;
 
  float nowAngle  = PI/60 * anime;      // 現在の位相
  float prevAngle = PI/60 * (anime-1);  // 1フレーム前の位相

  x = x - 50 * ( cos( nowAngle ) - cos( prevAngle ) );

こんな感じ。めでたく、現在座標とアニメ情報のみから次の座標が得られました。まる。

*1:フレーム:ゲーム中の最小時間