LoginSignup
3
4

More than 1 year has passed since last update.

【PixiJS 覚書】第四回 PIXI.Tickerでアニメーションしよう(描画メインループ)

Last updated at Posted at 2021-12-13

これまでのあらすじ

PixiJSの基本となる仕組みを把握し、図形、スプライト、文字の描画を見てきました。(ざっくり)

次はアニメーション処理のメインループとなる、PIXI.Tickerの基本を押さえます。

requestAnimationFrameについて

本題であるPIXI.Tickerについて語る前に、前提として押さえておきたい考え方が一つあります。
それはJavaScriptにおけるアニメーションループの基盤です。
JavaScriptでアニメーション処理を行う場合、たいていはrequestAnimationFrameが利用されます。
それはPixiJSにおいても例外ではなく、PIXI.Tickerも中身を掘り下げて行くと、このrequestAnimationFrameに行き着きます。

・requestAnimationFrame(callback)
(MDN:Window.requestAnimationFrame()

このrequestAnimationFrameは引数としてコールバック関数を一つ受け取り、キューに追加します。
そしてディスプレイの次のリフレッシュが行われる直前に、渡されたコールバックのキューを一斉に一度だけ実行します。
一度だけ実行して終わってしまうとループにはなりませんので、通常は以下のようなイメージで再度自身をキューに追加することでループを構築します。
(※以下のコードは仕組みを大幅に単純化したイメージであり、実際にPixiJS内に以下のようなコードがあるわけではありません)

sample
function updateMain() {
  // 各種更新処理
  updateA();
  updateB();
  updateC();

  // 各種更新処理が終わったら、再度、リクエストキューに自身を追加
  requestAnimationFrame(updateMain);
}

この状態でupdateMainを実行すると、以下のように、ディスプレイのリフレッシュに同期する形でループが回り始めます。

  1. 各種更新処理が行われた後、requestAnimationFrameにより再びupdateMainがキューに追加される
  2. ディスプレイでリフレッシュが行われるタイミングでキューに追加されていたupdateMainが実行され、「1.」に戻る

このループにより毎フレーム毎に各描画オブジェクトの座標やゲーム情報などを更新し続けるのがJavaScriptにおけるアニメーション処理の定跡です。

なお、もちろん、各更新処理が次のリフレッシュフレームまでに終了しなければ、次に同期して更新がかかるのは次のフレームになります。
ディスプレイのリフレッシュレートに同期しているとは言っても、厳密に毎秒60フレームの更新が保証されているわけではない点は注意しましょう。

PixiJSのメインループを担うPIXI.Tickerは上記サンプルよりもはるかに多機能かつ高度なので私も把握しきれてはいませんが、基本的な仕組みは同様と考えて良いでしょう。
(気になる方はPIXI.Tickerのコンストラクター内、122行目辺りから_tickの処理を軽く追ってみてください。
上記の仕組みを念頭に置いて眺めると、requestAnimationFrameによってthis._tickがぐるぐる回っていることがなんとなく伝わってくると思います。
(参考:PIXI.Ticker#constructor 122行目~))

要点1:JavaScriptのアニメーション処理の基本はrequestAnimationFrame
要点2:requestAnimationFrameに渡したコールバック関数は、ディスプレイのリフレッシュと同期を取る形で呼び出される。
要点3:コールバック内で再びrequestAnimationFrameを用いて自身をコールバックさせることでメインループが構築される。

PIXI.Ticker

tickerプロパティ

さて、JavaScriptによるアニメーション処理の基本を整理したところで、本題のPIXI.Tickerです。
実際の利用方法としては、PIXIアプリケーションのtickerプロパティにPIXI.Tickerがインスタンス化されているので、それを介して各種機能を使用します。

要点4:PixiJSにおいてはPIXI.Tickerのインスタンスであるtickerプロパティがメインループを担う。

サンプルコード

今回はPlaygroundのデフォルトのサンプルコードをそのまま使わせてもらうこととします。
サンプルコードから該当箇所だけを抜き出した物が以下です。

    // Listen for animate update
    app.ticker.add(function(delta)
    {
        // Rotate mr rabbit clockwise
        bunny.rotation += 0.1 * delta;
    });

シンプルですね。

addメソッド

・add(fn, context, priority)

PixiJSの描画ループに処理を追加するメソッドです。
引数として3つの値を受け取りますが、第一引数以外は任意引数です。

fn:メインループに追加するコールバック関数
context:コールバック関数を呼び出す際にbindするオブジェクト。実行時の「this」。
priority:優先順位。通常(省略時)は0。範囲は-50~+50(正が優先される)で設定するみたい。

というわけで、addメソッドによりメインループ内で繰り返し呼び出されるコールバック関数を追加しました。
これでディスプレイのリフレッシュに合わせて、渡したコールバック関数による更新が行われます。

要点5:メインループに処理を追加するためにはaddメソッドを使う。

コールバック関数に渡されるdelta

addしたコールバック関数をよくよく見ると「delta」という引数を受け取るようになっていますね。

function(delta) {
  // Rotate mr rabbit clockwise
  bunny.rotation += 0.1 * delta;
}

この「delta」はPIXI.Tickerがコールバック関数を呼び出す際に渡してくる値であり、その意味は
「前フレームから今フレームまでの経過時間。
ただし単位は1フレームの通常の処理時間、つまり1/60秒(16.6666666…ミリ秒)を基準とした比率」
です。
つまり順当に60FPSが維持されていれば、deltaの値はおよそ1に、処理が重くて2倍の時間がかかっているようであれば2に近付きます。
実際にはCPU負荷状況やディスプレイのリフレッシュ精度などの影響で常にわずかにズレています。
delta.png
今回のサンプルコードの場合、このdeltaの値を使って毎フレームにおおよそ0.1(単位:ラジアン)ずつ、バニーさんを回転させているわけです。

要点6:addされたコールバック関数は、呼び出される際に経過時間(※)を受け取る。
(※:単位は1フレームの通常の処理時間を基準とした比率、処理が重くなっていなければおおよそ1)

removeメソッド

addがあるならremoveも当然あります。利用場面はあまり多くなさそうですけど。

・remove(fn, context)
リファレンス

今回のサンプルでは無名関数を直接渡しているので識別子がないですが、addする際に名前付き関数として定義して渡していればその識別子でremoveできます。

speedプロパティ

PIXI.Tickerには面白いプロパティが用意されています。
以下のコードをサンプルの19行目辺りにでも入れてみてください。

app.ticker.speed = 3;

通常の三倍の速さで回転し始めたかと思います。
文字通り、更新処理のスピードを係数倍で調整するプロパティです。倍速の秒間120フレームなら2、半分の30フレームならば0.5という具合です。
ただし、speedを上げるということは処理負荷も上げることに直結しますので、ご利用は計画的に。

要点7:speedプロパティでループの更新速度は変更可能。ただし負荷増大に注意!

おまけ

ただぐるぐる回っているだけでは動きが少なくて寂しいので、もう少しダイナミックに動かしてみましょう。
今回のサンプルコードを以下に書き換えてみてください。バニーさんがダイナミックな遊泳をしてくれるようになります。

    // Listen for animate update
    let elapsedTime = 0;
    app.ticker.add(function(delta)
    {
        // Rotate mr rabbit clockwise
        bunny.rotation += 0.1 * delta;

        elapsedTime += delta;

        bunny.x = Math.sin(elapsedTime * 0.01) * app.renderer.width / 4 + app.renderer.width / 2;
        bunny.y = Math.sin(elapsedTime * 0.005) * app.renderer.height / 4 + app.renderer.height / 2;
    });

他にもコンテナそのものを回転させてみたり、拡大縮小してみたり、色々ためしてみると良い練習になりそうです。第二回の復習でもあります。

今回のまとめ

要点1:JavaScriptのアニメーション処理の基本はrequestAnimationFrame

要点2:requestAnimationFrameに渡したコールバック関数は、ディスプレイのリフレッシュと同期を取る形で呼び出される。

要点3:コールバック内で再びrequestAnimationFrameを用いて自身をコールバックさせることでメインループが構築される。

要点4:PixiJSにおいてはPIXI.Tickerのインスタンスであるtickerプロパティがメインループを担う。

要点5:メインループに処理を追加するためにはaddメソッドを使う。

要点6:addされたコールバック関数は、呼び出される際に経過時間(※)を受け取る。
(※:単位は1フレームの通常の処理時間を基準とした比率、処理が重くなっていなければおおよそ1)

要点7:speedプロパティでループの更新速度は変更可能。ただし負荷増大に注意!


今回はここまで!

次回はインタラクション要素でクリックしたりタップしてみたりするよ!

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4