loopIn/loopOutを極める
はじめに
"cycle" だけじゃない!意外と知らない人が多い、ループのエクスプレッションについて。
パラメータを見る前に...loopInとloopOutの違い
loopOutはなぜかみんな使うのに、loopInを使わない人が多い。違いは次の通り。
loopOut:最後のキーフレームを過ぎた後に繰り返す。
loopIn:最初のキーフレームが来るまで繰り返す。
これだけである。
「1,2,3,1,...」のときはloopOutでループする人は多いが、「1,2,3,1,...1,2,3,4,5,6」のときにloopInしない人が多い。「1,2,3,1,...1,2,3,4,5,6,7,8,9,7,...」はloopInとloopOut両方を使うことでループ分のキーを打たなくて済む。
どちらもプロパティにキーフレームを打っていることが前提。2つ以上打っていて初めて意味を成す。
どっちかしか使えない?
loopを覚えたてのときは楽しくて仕方がない。しばらくすると次に思うのが「loopIn、loopOut同時に使えないのか」。エクスプレッション初心者だと1つのプロパティに対して1つのコマンドしか書けないと思い込みがち。
それはエクスプレッションの最終結果が1つの値でないといけないわけで、同時に使えないわけではない。と言うと、こう書く人がいる。
loopIn();
loopOut();
そして言う「loopOutしかされないんだけど!」。そりゃあ、そうでしょう。
エクスプレッションはいろいろ書いても最終結果を採用するので、上のエクスプレッションだと常に loopOut(); で評価された値になる。
どうするか。
単純にifで条件分けすればよいのでは?
ではどのような条件を設定すればよいだろう。
loopIn、loopOutそれぞれの挙動を改めて図解すると右図の通り。loopInの挙動が上部、loopOutの挙動が右図の下部。
もう少し丁寧に書くと、
loopInは、最初のキーフレームよりも前にいるときはloopInの計算結果を取り、最初のキーフレームを超えたら元のキーフレーム通りの値、つまりvalueを取る。
loopOutは、最後のキーフレームよりも後ろにいるときはloopOutの計算結果を取り、最後のキーフレームより前にいる時は元のキーフレーム通りの値、つまりvalueを取る。
右図下のようにABC3つの区間に分けて考えればよさそう。
正直に条件を設定するならば、
if( time < key(1).time ){ loopIn(); } //A区間。1つめのキーフレームより前のときloopIn
else if( key(numKeys).time < time ){ loopOut(); } //C区間。最後のキーフレームより後ろのときloopOut
else{ value; } //B区間。上記どちらでも無いとき、つまり、最初と最後のキーフレームの間にいるときvalue
一番丁寧な書き方。
これで
loopInしてきて、
キーフレーム通りに動いて、
loopOut
という動きが作れる。
これでもよいが、まだ短縮できる。
loopInでもloopOutでもloopしていないときはどっちにしろ value を取るので、else{ value; } は要らないのでは?
ということを考えると三項演算子を使って次のように書ける。
( time < key(1).time )? loopIn() : loopOut();
のように書く。日本語にすると、
「現在の時間」(time)が、「このプロパティの1番目のキーフレームの時間」(key(1).time)よりも前のとき、loopIn()。そうじゃないときloopOut()。
という感じ。
これの場合、B区間はloopOut()の結果(それはつまりvalueなのだが)を採用している。
これでもよいが、条件分けしない書き方も出来る。
そもそもloopIn、loopOut両方使いたいということは、
loopInしてきて、
キーフレーム通りに動いて、
loopOut
という挙動をさせたいということ。
これを区間ごとに表記してみると、
A区間:loopIn
B区間:value
C区間:loopOut
このような結果が得られればよい。
ところで突然だが、右図のようにloopIn()とloopOut() の値を足してみる。
エクスプレッションとしては、
loopIn() + loopOut()
を記入するということだが、これをABCそれぞれの区間で分けて見ると、
A区間:loopIn + value
B区間:value + value
C区間:value + loopOut
となる。
目標とかなり近い形じゃね?というかどの区間でも value を引けば目標の形になるのでは?
じゃあ引けばいい。
loopIn() + loopOut() - value;
で両方使える。
1番目のキーフレームより前の時はloopOutの取る値はvalueと等しいので、-valueと相殺されて、loopInが残る。最後のキーフレームより後ろの時は、loopInの取る値はvalueと等しいので-valueと相殺されて、loopOutが残る。
最初と最後のキーフレームの間にいる時は、loopInとloopOutともにvalueと等しいので、value + value - value でvalueになる。
簡単に書くことができても意味が理解できなければ使いこなせないので、分かる方を使えば良い。ただし、いつまでも分からないままでいるのは恥ずかしい。
引数(ひきすう)の基本
ここから詳しく見ていく。まず基本の話。引数というのは loopOut('cycle') でいうと 'cycle' にあたる( )の中に与えるパラメータ。エクスプレッション直打ちじゃなくて一覧から選択する人は( )の中に最初から文字が入った状態で入力されるはず。
例えばwiggleをこれで入れてみる。
wiggle(freq, amp, octaves = 1, amp_mult = .5, t = time)
が自動的に入る。( )の中には「,」で区切られた5つの引数がある。このまま決定してもエラーになる。なぜなら、これらの引数は「与えるべき形式」(数字とか文字とか色とか座標とか)が決まっている。それが適切に与えられていないからエラーになる。予め入っているこれらの引数はユーザーに「何を入れてね」って教えてくれてるだけ。ただし、先程の「与えるべき形式」を教えてくれているのではない。「何を意味する値を入れるべきなのか」を教えてくれてる。
具体的に見る。
freq:見るからにfrequencyの略。つまり周波数。周波数というのは1秒間に何サイクルするか。数字で入れる。
amp:見るからにamplitudeの略。つまり振幅。波の振れ幅。数字で入れる。
てな具合で、それぞれの引数に適当に数字を入れてやる。
wiggle(2, 10, 1, .5, time)
とすると動く。でも直打ちする人だったら、
wiggle(2,10)
と書くはず。引数が足りない?でも動く。何故か。最初の式の3つ目以降の引数を見てみる。
octaves = 1, amp_mult = .5, t = time
2つ目までの引数と決定的に違うのは、「=」が含まれていること。「=」のあとに数字と「time」も文字が書いてある。
これらが意味するものは「初期値」「デフォルト値」である。ユーザーが入力しなくとも自動的に「=」の後に続く値が入ってることになる。つまり、
wiggle(2,10)
とかくことは
wiggle(2, 10, 1, .5, time)
と書くことと同義である。
loopの引数
loopOutをさっきの画像のやつから入力してみる。
loopOut(type = "cycle", numKeyframes = 0)
となる。
type:ループのタイプ
numKeyframes:キーフレームの数
というのは見ればわかる。今回は両方にデフォルト値がある。なので先程の例に倣うとこう書くこともできる。
loopOut()
引数が与えられていない?でも問題ないのである。必要な2つの引数は両方共初期値が設定されているので、
loopOut("cycle", 0)
と書いているのと同義なのである。
loopの引数を詳しく見る
typeには先程 "cycle" が自動的に入っていた。指定できるということは他のパターンもあるということ。Adobeのリファレンスを読め!と言えばもともこもないので、ここで簡単に説明してみる。
type
"cycle" (デフォルト値。指定しない場合はこれが自動的に選ばれる)
"pingpong"
"offset"
"continue"
4つのうちどれかを指定する。どれにするかでループの挙動が違う。
numKeyframes
ループする範囲の指定。繰り返したいキーフレームの数で指定する。0だと全てのキーフレーム範囲をループする。
typeについて詳しく
違いを見ていくにあたって、グラフで表示するのが一番わかり易い。ただし、「速度グラフ」では意味がない。「値グラフ」で見たとき初めて違いがわかる。
■cycle
ご存知cycle。同じ値を繰り返す。
上図だと、3つのキーフレームが打たれている。エクスプレッションは
loopOut("cycle",1)
これを翻訳するとしたらこんな感じ?
loopOut:最後のキーフレームを越えたら
"cycle":全く同じ値を使って繰り返す。
1:最後のキーフレームから1つ前のキーフレームまでの範囲を
グラフの点線部分がエクスプレッションによって与えられた値を示すので、見てみると、確かに同じ値が繰り返されているのがわかる。
使い所は、セルのリピートなど。
■pingpong
ピンポン。つまり卓球。行ったり来たり。
先ほどと同じく3つのキーフレームが打たれている。エクスプレッションは
loopOut("pingpong",1)
最後のキーフレームまで到達すると、そのあと「指定したキーフレームまで逆再生」の動きをする。指定のキーフレームまで戻ってきた後、また最初と同じ動きをして...の繰り返し。
使い所は、ローリングなど。(コマ打ちの場合そのままではうまくいかないが、キーの打ち方でうまいことやる方法がある)
■offset
「オフセット」の言葉から連想できるように、ループのたびに値がオフセットされる。ループの仕方は"cycle"と似ているが、決定的に違うのは「前のループの最後の値をスタート地点として次のループが開始する」ということ。ループ範囲の最初と最後の値が同じときは、"cycle" と同じ挙動になる。
使い所は、歩きのSLなど。
歩きSLが必要な場合、足の位置を合わせないといけないことが多いが、1ループ分だけキーフレームで調整すれば、その後はoffsetを使うことで解決する。作画が「その場歩き」「1ループだけ歩き」の場合でも。
1ループ分のキーフレームをカット尻まで何度もコピペするのは非効率なうえ修正しにくくて、メリットがありません。これを使いましょう。
カット途中から動き出す場合は、loopOut
カット途中で止まる場合は、loopIn
■continue
continue = 「続ける」。それはわかる。何を続けるのかが問題。
「最後のキーフレームでの速度」を維持し「続ける」。
第二宇宙速度で地球とサヨナラしたようなイメージ。二度と戻っては来ない。
(他の星との干渉とか細かい話は置いといて)。使い所は、SL IN/OUTやBGFollowなど「あとはそんままどっか行っといて~」とか「その感じでフレームインしてきて~」というとき。
タイムシート的には、コンポのタイムライン範囲外にキーフレームを打たないといけないようなとき。見える範囲にキーフレ打って、あとはそのままサヨナラ~て感じに使える。(continueに限らず、loop全般に言えることだが...。)
continueだけは特別で、2つ目の引数は使えない。continueの挙動には「繰り返す範囲」という概念は存在しない。「繰り返す」よりは「維持」「持続」というイメージ。
スクリプトを使う
これらのパラメータを簡単に入力できるスクリプトを作りました。