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


この記事はTouchDesigner Advent Calendar 2020の20日目の記事です。

はじめに

「Perlin Noise?Simplex Noise?それともCurl Noise?」

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

という事で、
今回はAnimation COMPを使ったアニメーションフレームワークの考察を書きます。
最終的にこんな感じのアニメーションを作る際の参考になればと思っています。

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

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

1.手付けアニメーションならAnimation COMP

1-1.Animation COMPとは

以下公式ドキュメントから抜粋。

The Animation Component is a special component used for creating keyframe animation channels. (Animation COMPはキーフレームアニメーションチャンネルを作成するための特別なコンポーネントです。)

要はAfterEffectsみたいにフレームごとの細かいアニメーションをカーブを描いて作成することができるコンポーネントです。↓こんなやつですね。

使い方は@takashi_kawamuraさんが以下の記事でとても丁寧に解説されています!
【TouchDesigner】キーフレームを使ったアニメーション──AnimationCOMPを使う

1-2.Animation COMPにはcallbacksがない

とてもパワフルなAnimation COMPなんですが、残念なことにAnimation COMPからはcallbacksを受け取れません


🤔(え、、なんで??)


なので、
「アニメーション終わりに別のアニメーションを動かして・・・・」
とか
「アニメーション終わったらシーンを切り替えて・・・」
みたいなことがそのままだとできません。

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

2.Animation COMPの使い勝手を向上させる

2-1.正規化してアニメーションをつくる

今回は長いアニメーションではなく最小単位のアニメーション素材を組み合わせて一つのアニメーションを作成することを意図しており、使い勝手の良さを考え単位量でのアニメーションを作ります。

・アニメーションの数値は-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にしておきましょう。

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を受け取り様々なタイミングで別処理を走らせることができそうです。(今回は再生終わりにフォーカスしています。)


・Animation COMPとの紐づけ

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の動きが同期すると思います。

2-3.callbacksを使って再利用可能にする

2-2の実装では紐づけができましたが一度再生して終わりです。
今度はcallbacksを利用して再利用可能な仕組みに仕上げてみましょう。

Timer CHOPの右下の矢印をクリックしてDATを出します。その中にcallbacksの関数が入っているので、処理を実行したいタイミングのcallbacksにコードを追加してあげます。

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


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


3.メソッドチェーンへの道

メソッドチェーンはメソッドの返り値をまた別のメソッドにかけて順番に処理していく手法ですね。(かっこいいい!)

Method chaining

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

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

3-1.コンポーネント化する

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

最小単位のアニメーションをつくるにもオペレーターの数が多くなりがちなのでBase COMPの中に全部入れてしまい、必要に応じてCustom Parameterで設定をいじれるようにしてしまいます。

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

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

1.ジャンプして小さく消える

2.波紋の様に広がる

3.ぐるっと回転して円が描かれる

3-2.チェーン可能にする

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

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

↓これは各COMPが独立しており、それぞれのCOMPを再生しているところです。

↓そしてこれがチェーンしている状態です。上と違うのは左のCOMPから右のCOMPへノードがつながっているところです。一番左のCOMPを再生すると、各COMPの再生終わりのタイミングで次にノードでつながれたCOMPが再生していっています。(表示上前のCOMPを継承して各COMPが表示されるためちょっとわかりずらいかもしれませんが、、)

・ノード接続と連続再生の制御
まずノード接続の制御ですが、同じ種類のCOMPだけinputに接続できるようにしています
下の動画を見るとわかりますが、アニメーション用のCOMPはつながって、Constant TOPはアラートが出て接続を拒否されています。
これはオペレーターに「animation」というタグがついているかどうかで判断しています。もっと厳密に制御することもできますが、運用でカバーするとして簡易的にタグだけで制御しています。

そして連続再生の部分ですが、COMP自身のアニメーションが終わった後のonDoneのところで、outputにつながっている次のCOMPのパラメーターである「Play」をOnにしています。outputはその他のTOPにも複数つなげることができるため、パラメーターに「Play」があるオペレーターであれば処理を実行するように制御しました。
またCOMPの外でも色々制御ができるようにCHOPもoutputさせています。

4.まとめ

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

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