Processingで滑らかなアニメーションを実現する(2)

我が家にPS4がやってきました。本体にセットでついてきたKNACK(ナック)は世間からあまり注目されていないようですが、久しぶりにプレイしていて純粋に楽しいゲームでした。雑魚敵と戦うだけで楽しいゲームって作るのがすごく難しいんですよね。

今回は前回に引き続き、Processingで滑らかなアニメーションを実現する方法を解説していきます。

滑らかなアニメーションの実現方法

Processingで滑らかなアニメーションを実現する方法は2種類あります。

1つ目はdraw関数で更新を行う際に、前フレームからの経過時間に基づいて更新内容を算出する方法(時間ベースのアニメーション)。もう1つはアニメーションを更新処理と描画処理に分けて、経過時間に基づき更新処理を複数回実行する方法(フレームベースのアニメーション)です。

フレームベースのアニメーション

今回は処理が比較的シンプルで、既存のProcessingコードからも移植が行いやすいフレームベースのアニメーションについて説明します。

更新処理と描画処理の分離

まず、アニメーション処理を更新処理と描画処理に分離します。例えば以下のコードの場合、

void draw() {
  x += vx;  // 座標の更新処理
  y += vy;

  ellipse(x, y, 10, 10); // 図形の描画処理
}

次のように、前半の更新処理をupdate関数として分離します。

void update() {
  x += vx;
  y += vy;
}

void draw() {
  // ここにupdate関数の呼び出し処理が入る

  ellipse(x, y, 10, 10);
}

更新処理の呼び出し

次に前フレームのdraw関数呼び出しからの経過時間を計測し、必要な回数だけupdate関数を呼び出します。

最初に更新処理用の変数を用意します。時間の単位は全てミリ秒です。

final float TARGET_FPS = 60.0f; // 目標フレームレート
final float FRAME_TIME = 1000.0f / TARGET_FPS; // 1フレームの許容処理時間
int lastUpdateTime = 0; // 前回の更新時刻
float elapsedTime = 0.0f;   // 前フレームからの経過時間

続いてupdate関数を呼び出す処理をdraw関数に追加します。

void draw() {
  int curTime = millis();
  elapsedTime += curTime - lastUpdateTime;
  lastUpdateTime = curTime;

  for( ; elapsedTime >= FRAME_TIME; elapsedTime -= FRAME_TIME) {
    update();
  }

  // 描画処理(省略)
}

動作を確認する

前回のサンプル(上)と今回の仕組みを入れたもの(下)を実際に動かして比較してみます。

描画負荷用のテストだったのでそもそも描画し過ぎという話はありますが、一つ一つの円を目で追っていくと等速で移動していることが確認できると思います。

終わりに

非常に簡単ですがフレームベースのアニメーション更新の仕組みを解説してみました。

今回の手法とセットで使われる事の多い、描画オブジェクトの階層構造を簡単に管理できるシーングラフについてもそのうち解説したいと思います。