【TouchDesigner】メソッドチェーンぽい実装によるアニメーションフレームワークの考察

この記事は[TouchDesigner Advent Calendar 2020](https://qiita.com/advent-calendar/2020/touchdesigner)の20日目の記事です。
<br>
##はじめに
「Perlin Noise？Simplex Noise？それともCurl Noise？」

師走になるとクリスマスソング🎅に交じって聞こえてくるノイズアニメーショントークですが、**今年の年末はTouchDesignerで手付けのアニメーションもやりたい！**

という事で、
今回はAnimation COMPを使ったアニメーションフレームワークの考察を書きます。
最終的にこんな感じのアニメーションを作る際の参考になればと思っています。
![ダウンロード.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/28445973-3ebe-20af-6c1a-4fa7bb969426.gif)



かっこいいアニメーションの作り方ではなく**アニメーションをつくる際のシステムを作る話**なのでちょっと文字多めです。

プロジェクトファイルは↓こちらにアップしてあります。
https://github.com/atsonic/TDAnimationFramework

##1.手付けアニメーションならAnimation COMP
###1-1.Animation COMPとは
以下[公式ドキュメント](https://docs.derivative.ca/Animation_COMP)から抜粋。
>The Animation Component is a special component used for creating keyframe animation channels. （Animation COMPはキーフレームアニメーションチャンネルを作成するための特別なコンポーネントです。）


要は**AfterEffectsみたいにフレームごとの細かいアニメーションをカーブを描いて作成することができるコンポーネント**です。↓こんなやつですね。
![img0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/0c266642-cf91-d3c7-f38f-1d7533305aeb.png)


使い方は@takashi_kawamuraさんが以下の記事でとても丁寧に解説されています！
[【TouchDesigner】キーフレームを使ったアニメーション──AnimationCOMPを使う](https://qiita.com/takashi_kawamura/items/1af840ee5482dbddd8d7)


###1-2.Animation COMPにはcallbacksがない
とてもパワフルなAnimation COMPなんですが、残念なことに**Animation COMPからはcallbacksを受け取れません**。
<br>
🤔（え、、なんで？？）
<br>
なので、
「アニメーション終わりに別のアニメーションを動かして・・・・」
とか
「アニメーション終わったらシーンを切り替えて・・・」
みたいなことが**そのままだと**できません。

なのでなんとかしてこの後callbacks的な機能を付加したいと思います。

##2.Animation COMPの使い勝手を向上させる
###2-1.正規化してアニメーションをつくる
今回は長いアニメーションではなく最小単位のアニメーション素材を組み合わせて一つのアニメーションを作成することを意図しており、使い勝手の良さを考え**単位量でのアニメーション**を作ります。
![img1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/3f492f86-9426-d54d-c7f1-b83a31c679de.png)
**・アニメーションの数値は-1~1の間で設定**
positionを**fraction**で指定する場合は、動かしたい対象にそのまま数値を与えればOKです。
positionを**pixel**で指定する場合は、この-1~1にMath CHOPでpixel座標をかけてあげれば良いだけなので、あとあとの微調整を考えて正規化しています。

**・Range（REnd）はFPSと同じにする**
Range（REnd）をFPSと同じにするということは、**1秒間の単位量アニメーション**という事になります。
Animation COMPには**speed**というパラメータがあり**再生速度をコントロール**することができるため、1秒でベースを作ってあとで再生速度を調整することが可能になります。

それと**Play Mode**はタイムラインに依存しない**Sequential**にしておきましょう。
![img2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/e6d07dd5-86e2-820c-1496-4d7bd854cf05.png)


###2-2.callbucks未実装へのアンサー

**・Timer CHOP**

売られた何とかにはアンサーするのがエンジニアの流儀なので**Timer CHOP**でアンサーします。
>The Timer CHOP is an engine for running timed processes. It outputs channels such as timing fractions, counters, pulses and timer states, and it calls python functions (callbacks) when various timing events occur.

この説明にあるように、Timer CHOPは**様々なタイミングでイベントが発生した時にcallbacksを呼び出す**とあります。
Animation COMPとTimer CHOPを紐づけてあげれば、**Animation COMPの時間の流れでTimer CHOPからcallbacksを受け取り**様々なタイミングで別処理を走らせることができそうです。（今回は再生終わりにフォーカスしています。）
<br>
**・Animation COMPとの紐づけ**
![img3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/17a1638f-0f7c-3fc1-2d8c-e2c3440e1232.png)
1.Animation COMPの中にある**Timeline CHOPのRange Endを「On」**にする
2.Timer CHOPの**Length**の単位を**Frames**にし、**1.でOnにした「Range End」**を参照させる
3.Timer CHOPの**Initializeの「Pulse」**に**Animation COMPの「cuepulse」**を参照させる
4.Timer CHOPの**Startの「Pulse」**に**Animation COMPの「Play」**を参照させる

上記画像の通りの設定をして**Animation COMPの「Play」をOn**にするとAnimation COMPとTimerの動きが同期すると思います。
<br>
###2-3.callbacksを使って再利用可能にする
2-2の実装では紐づけができましたが一度再生して終わりです。
今度はcallbacksを利用して再利用可能な仕組みに仕上げてみましょう。

Timer CHOPの**右下の矢印をクリック**してDATを出します。その中にcallbacksの関数が入っているので、処理を実行したいタイミングのcallbacksにコードを追加してあげます。
![img4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/c26f30e9-c5db-4b53-23e9-87519c40712b.png)

DATの一番下に**「onDone」**という関数があり、そこに下記の様に2行加えます。
Animationの終わりにonDoneが呼ばれ、そのタイミングでAnimation COMPの再生をOFFにしてCueをたたきます。そうすることで**Animation COMPが初期化され、さらにそれらを参照しているTimer CHOPも初期化**されます。
これで再利用可能になりました。

```python

def onDone(timerOp, segment, interrupt):
	op('animation1').par.play = False
	op('animation1').par.cuepulse.pulse()
	return
```
<br>

##3.メソッドチェーンへの道
メソッドチェーンはメソッドの返り値をまた別のメソッドにかけて順番に処理していく手法ですね。（かっこいいい！）

[Method chaining](https://en.wikipedia.org/wiki/Method_chaining)

この記事の内容では特に返り値を設定しておらず処理の終わりに連続して別処理を呼び出すだけの感じなので、本来のメソッドチェーンとは少し意味合いが違ってきますが、**実装次第で返り値を持たせることも可能です**。
どちらかと言うと**Tween系ライブラリのアニメーション連結**みたいな感じですね。

この先は細かい実装の話を書いていくと冗長になりすぎてしまうので、実装のコンセプトについて書きたいと思います。

###3-1.コンポーネント化する
Animation COMPはアニメーションのベースとなるタイムラインを作りCHOPに出力するコンポーネントなので、グラフィックは別で用意する必要があります。

最小単位のアニメーションをつくるにもオペレーターの数が多くなりがちなのでBase COMPの中に全部入れてしまい、必要に応じてCustom Parameterで設定をいじれるようにしてしまいます。
![img5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/776e1293-6b19-ab85-cc9a-5653cf82f775.png)

上図の**赤で囲った部分のパラメーターはAnimationの基本的な制御に関するパラメーター**で、これらを基本のパラメーターとしたBase COMPを量産していきます。
それ以外のパラメーターは実際のアニメーションに関わるものなのでアニメーションに応じて自由に編集します。

記事の最初に掲載したアニメーションは下記3つのアニメーションパートから構成されていますが、それぞれがコンポーネント化され最小単位のアニメーションとして作成されています。

1.ジャンプして小さく消える
![download1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/8359d9fb-2663-f533-fc92-781870e32c7a.gif)
2.波紋の様に広がる
![download2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/dcebc506-0c98-acbd-bc36-3afed7425985.gif)
3.ぐるっと回転して円が描かれる
![download3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/e819ae96-61db-630b-dc8d-14fd322f7c8b.gif)

###3-2.チェーン可能にする
最後に3-1で作ったコンポーネントを、nodeでつなげて連続再生できるように仕上げます。

**・チェーン可能とは**
チェーン可能な状態とは、**COMP同士をノードでつなげて、先頭のCOMPを再生すると、nodeでつながったCOMPが連続して再生していく状態**をさします。

↓これは各COMPが独立しており、それぞれのCOMPを再生しているところです。
![download4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/3ca513d9-c2e3-fc50-7759-5deb771bce5f.gif)

↓そしてこれがチェーンしている状態です。上と違うのは左のCOMPから右のCOMPへノードがつながっているところです。**一番左のCOMPを再生すると、各COMPの再生終わりのタイミングで次にノードでつながれたCOMPが再生していっています**。（表示上前のCOMPを継承して各COMPが表示されるためちょっとわかりずらいかもしれませんが、、）
![download5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/c465ada7-0fd4-6369-2936-25625ad3ea7c.gif)

**・ノード接続と連続再生の制御**
まずノード接続の制御ですが、**同じ種類のCOMPだけinputに接続できるようにしています**。
下の動画を見るとわかりますが、アニメーション用のCOMPはつながって、**Constant TOPはアラートが出て接続を拒否**されています。
これはオペレーターに**「animation」というタグがついているかどうか**で判断しています。もっと厳密に制御することもできますが、運用でカバーするとして簡易的にタグだけで制御しています。
![download.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/efd5c710-782f-2f12-038f-f1bf38cd464c.gif)

そして連続再生の部分ですが、COMP自身のアニメーションが終わった後の**onDoneのところで、outputにつながっている次のCOMPのパラメーターである「Play」をOn**にしています。outputはその他のTOPにも複数つなげることができるため、パラメーターに「Play」があるオペレーターであれば処理を実行するように制御しました。
またCOMPの外でも色々制御ができるようにCHOPもoutputさせています。
![img6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31194/1d36fb6b-1175-5e78-4aa5-8aacaf79e812.png)


##4.まとめ
長くつらつらと書いてきましたが、最小単位のアニメーションコンポーネントを連続で再生していくフレームワークを考察してみました。SceneChangerのAnimation特化下位互換版みたいな感じですかね。
コンポーネントごとに分担して作ることもできるし、この手のモーショングラフィックスは使いまわしも多く発生するため再利用可能な状態にしておくことで効率があげられるのかなと思います。

不完全な部分はまだありますが、個人的には今後のTouchDesignerでのプログラミングの仕方を考える良いきっかけになりそうです😀