TouchDesignerでアタック25のアレを作る


はじめに

最近社内の勉強会でTouchDesignerに触れる機会があり、それからちょこちょこと触っています。
クリエイティブコーディング的なイメージが強いTouchDesignerですが、
今回はUI周りの勉強を兼ねて、みなさんご存知のクイズ番組「アタック25」のアレを作ってみました。
実装においてのポイントを書き残す形になっていますので細かい説明は飛ばし飛ばしですが、実際に何か作るときに参考やきっかけになどなれば幸いです。

TouchDesingerって?

機能を持った箱(ノード)を置いたり繋げたりすることでコードを書かずにプログラミングをしていく、
ビジュアルプログラミングツールのひとつです。
通常のプログラミング知識が少なくても直感的にプログラミングができたり、映像/音声の出力や制御のための機能が揃っていたりすることから、
デザインやアート寄りの人がテクノロジーを組み合わせたアート表現のために活用していることが多いのだとか。

作ったもの

デン!アタック25のヤツ!
左には5x5の数字パネルが、右には各色の状況パネルが表示されています。
右のパネルを選択してから左のパネルをクリックすると、選択した色に塗り替えることができます。
最終問題ボタンを押すと、選択した色のパネルが透明になり、後ろの映像が見えるようになります

ざっくり構成

実装

データ

パネルの状態などの各種情報は、一番上の階層に「Table DAT」を配置して管理します。
最終問題フラグは後から急遽追加したため変数構成がガバガバなのはご愛嬌。

メインパネル

数字を表示する

パネルはそれぞれ1〜25までの数字を持っていますが、各パネルがどの番号なのかというデータが必須です。
今回は、各パネルのモジュール名に連番を振っておき、「モジュール名から数値を抜き出す」処理を用いて各パネルに番号を設定します。

TouchDesingerでは各モジュールのパラメータを設定できるパネルがあり、固定値を入力することができますが、項目左側のプラスマークをクリックして展開、水色の四角をアクティブにすることで関数などを呼び出すことができます。
meはそのコンポーネント自身を指し、 .parent()は自身を持っている親コンポーネントを指します。me.parent()でモジュール名「panel0」を取得し、そこからdigitsで数値だけを抜き出して「0」という文字列を取得しています。

背景色を表示する

各背景色と文字を合成することでパネルを表示しますが、背景色を5つ用意し、Switch TOPで切り替えられるようにしておきます。
どの背景色に切り替えるかの値は【パネル情報】から自身のパネル番号を検索し、参照します。

クリックを検知する

TouchDesignerには既にボタン用のコンポーネントButton COMPがありますが、
自身で作成したコンポーネントに、クリックを検知する機能を仕込むことも可能です。
Panel CHOPに対象のコンポーネント(ここでは親)を指定すると、それがクリックされた際に値を出力することができます。

背景色を変更する

クリックを検知したら【選択中の色】を取得し、その値で【パネル情報】の値を上書きします。
Panel CHOPからの信号をCount CHOPで受け取り、CHOP Execute DATで値の変更を検知したらクリックされたとみなし、パネル情報の更新を行います

def onValueChange(channel, sampleIndex, val, prev):
  root.op('panelStatus')[me.parent().digits,0] = root.op('curMode')[0,0]
  return

パネルを複製する

これを25枚手作業でそれぞれ作ってしまうと、何か修正するたびに全てのパネルに同じ修正を展開しないといけません。
そんなことをしていてはクリスマスを通り越して初日の出を拝んでしまうので、
Replicator COMPを利用して、1つ作ったパネルを雛形にして25枚のパネルを複製します。

item1 から item25 まで複製されました。自身の名前を元に表示する数字を決めているので、自動的に1〜25のパネルが出来上がっています。

パネルを修正する場合はコピー元を修正した後、Replicator COMPのプロパティパネルからRecreate All Operatorsの項目にあるボタンをクリックすると、複製のやり直しが行われて修正が反映されます。

状況パネル

数字と背景色を表示する

基本的な見た目はメインパネルと同様ですが、表示する数字は「メインパネルに各色が何枚あるか」です。
各パネルには【パネル情報】に設定する色番号とに合わせた番号を設定します。
そして【パネル情報】のデータから、該当の色番号が設定された項目を抽出、それが何件あるかをカウントすることで、各色のパネルが何枚あるかを計算しています。

len(root.op('panelStatus').findCells(me.parent().digits-1))

背景色の切り替えも仕組みはメインパネルと同様ですが、切り替えるための値はパネルの名前からdigitsで直接取得します

クリックを検知する

クリック検知もメインパネルと同様に、Panel CHOPを潜影蛇手
こちらはクリックされた時に【選択中の色】へ自らの番号を設定します。

def onValueChange(channel, sampleIndex, val, prev):
  root.op('curMode')[0,0] = me.parent().digits - 1
  return

また、今どの色が選択されているかがわかりにくいので、選択中の場合は右下に小さな四角が表示されるようにしました。
設定した条件式に応じて値を出し分けることができるExpression CHOPで、
色が選択中であれば1 そうでなければ0を出力。

1 if root.op('curMode')[0,0] == me.parent().digits - 1 else 0 

Switch TOPに渡して小さな四角と透明な画像を出し分け、パネルに合成しています

パネルを複製する

おばあちゃんが言っていた。「無駄を省く」のと「手間を惜しむ」のはまるで別物だと。
これもReplicator COMPで複製していきます。

最終問題ボタン

def onValueChange(channel, sampleIndex, val, prev):
  root.op('curMode')[1,0] = round(val)
  return

最終問題フラグを切り替えるボタンは特に見た目を気にすることもないため、Button COMPを利用します。クリックするたびに出力が1と0で切り替わるので、値の変更を検知して最終問題フラグを切り替えます

また、メインパネルでは「最終問題フラグが1」かつ「自身のパネルの色が【選択中の色】」である時、パネル画像の代わりに同じサイズの透過画像を表示します。
これで特定の色のパネルだけを透過させることができ、
最終問題(優勝者の色のパネルだけが取り除かれた状態で問題の映像が流れる。パネルを取れば取るほど最終問題が見やすくなり有利になる仕組み)が実装できました

レイアウト

親コンポーネントが、自身の子となるコンポーネントをどのように表示するかを設定します。

メインパネルは5x5のグリッド状に並べます。

最終問題ボタンと状態パネルは縦に並べます。

この2つをまとめて、横に並べれば完成です。

下から上、右から左など、多少トリッキーな並べ方も可能ですが、自由自在に並べるというのは難しそうなので、
あらかじめレイアウト(どこに何を配置するのか)を考えておき、それに合わせてコンポーネントの階層を作っておくのが良さそうです

未練(やりたかったこと)

  • パネル色が変わる時のトランジション演出
  • 選択中の色が獲得できるパネルのハイライト(獲得できるパネルにはルールがある)
  • コロンビア

完走した感想

今回はTouchDesigner上での画面操作、UI構築などについて勉強してみました。
ボタンコンポーネントを魔改造せずとも、Panel CHOPを仕込むことで、実際に表示される見た目を優先しつつ、そこにクリック検知等を仕込めることがわかったのは収穫でした。

気付き

「これ、コンポーネント設計そのものじゃん」ということに作り終わってから気付きました。
ビジュアルプログラミングが故に、成果物がそのまま構成書にも見えるんですね。

普段Vueなどを触っているのですが、
「コンポーネント化ってどれをどこまでで1つのコンポーネントにすりゃ良いんだ…」という感じでなかなかコンポーネントの概念を消化できずにいました。
ですが今回、目に見えるブロックを自分で動かしたり繋げたりすることで、かなりイメージできるようになった気がします。

ビジュアルプログラミングは「普段コードを書かない人のためのもの」というようなイメージがありましたが、
普段ガリガリコード書いてる人にもかなり有用なツールだと思います。

おわりに

新年会の余興でアタック25をやることになっても、TouchDesigner(TouchPlayer)の入ったPCとモニタがあれば安心です。


FORK Advent Calendar 2019
20日目 日本語で書く言語 @re_sai
21日目 正規表現にマッチした文字列を replace を使わずにハイライトさせる @yshrkn