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

##これまでのあらすじ

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

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

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

・requestAnimationFrame(callback)
（MDN：[Window.requestAnimationFrame()](https://developer.mozilla.org/ja/docs/Web/API/Window/requestAnimationFrame)）

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

```JavaScript: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行目～](https://pixijs.download/dev/docs/packages_ticker_src_Ticker.ts.html)））


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

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

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

####サンプルコード
今回は[Playground](https://pixiplayground.com/#/edit)のデフォルトのサンプルコードをそのまま使わせてもらうこととします。
サンプルコードから該当箇所だけを抜き出した物が以下です。

```JavaScript:
    // Listen for animate update
    app.ticker.add(function(delta)
    {
        // Rotate mr rabbit clockwise
        bunny.rotation += 0.1 * delta;
    });
```
シンプルですね。

####addメソッド
・add(fn, context, priority)

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

fn：メインループに追加するコールバック関数
context：コールバック関数を呼び出す際にbindするオブジェクト。実行時の「this」。
priority：優先順位。通常（省略時）は0。[範囲は-50～+50](https://pixijs.download/dev/docs/PIXI.html#UPDATE_PRIORITY)（正が優先される）で設定するみたい。

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

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


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

```JavaScript:
function(delta) {
  // Rotate mr rabbit clockwise
  bunny.rotation += 0.1 * delta;
}
```
この「delta」はPIXI.Tickerがコールバック関数を呼び出す際に渡してくる値であり、その意味は
「前フレームから今フレームまでの経過時間。
ただし単位は１フレームの通常の処理時間、つまり1/60秒（16.6666666…ミリ秒）を基準とした比率」
です。
つまり順当に60FPSが維持されていれば、deltaの値はおよそ1に、処理が重くて２倍の時間がかかっているようであれば2に近付きます。
実際にはCPU負荷状況やディスプレイのリフレッシュ精度などの影響で常にわずかにズレています。
![delta.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107226/886e0d44-af30-c02b-df6f-c75968182597.png)
今回のサンプルコードの場合、このdeltaの値を使って毎フレームにおおよそ0.1（単位：ラジアン）ずつ、バニーさんを回転させているわけです。

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

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

・remove(fn, context)
（[リファレンス](https://pixijs.download/dev/docs/PIXI.Ticker.html#remove)）

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

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

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

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

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

```JavaScript:
    // 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;
    });
```

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

##今回のまとめ

**要点１：JavaScriptのアニメーション処理の基本は`requestAnimationFrame`。**

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

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

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

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

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

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

------
今回はここまで！

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