After Effects エクスプレッション 初級ガイド

After Effects「エクスプレッション」初級ガイド

プログラミング経験ゼロから始める入門編

After Effectsを使っていると、同じ動きを何度も作ったり微調整に多大な時間を取られたりすることがよくあります。そんなときに役立つのがエクスプレッション(Expressions)です。この記事では、After Effectsのエクスプレッションを順次マスターできるよう、以下の内容を段階的に解説していきます。

初心者向けの基礎知識 - エクスプレッション有効化の方法や基本的な記述ルールなど、まったく触ったことがない人でも安心して始められる内容から開始します。コーディングの知識は前提としません。

実務で使える実用的なサンプル - 理屈だけでなく、すぐに現場で活用できる具体的なコード例を豊富に用意しました。コピー&ペーストで使えるサンプルやカスタマイズのポイントなども詳しく解説します。

段階的なスキルアップ - 初歩的な time wiggle() の使い方から、ゆくゆくは複雑な物理演算や外部データ連携まで、着実にレベルアップできる構成です。

人力では表現不可能な映像を作りたい方、プログラミング的なアプローチでクリエイティブワークを進めたい方、映像制作の効率化を図りたい方には、とりわけ役立つ内容となっています。

検証バージョン
Adobe After Effects
25.5.0
目次

基礎知識

エクスプレッションとは何か

After Effectsのエクスプレッションは、各レイヤーのプロパティにJavaScript形式のコードを書き込むことでアニメーションや動きを制御できる機能です。従来のキーフレームによる手動アニメーションとは異なり、数式や条件によって自動的に値を計算するため、より柔軟で効率的なアニメーションを実現できます。

例えば「時間の経過とともに回転し続ける」「他のレイヤーの動きに連動して拡大縮小する」「ランダムに揺れ動く」といった複雑な動作も、短いコードで簡単に表現できます。手作業だと数百個のキーフレームを打つ必要のある動作が、エクスプレッションなら数行で完了することも、珍しくありません。

キーフレームとエクスプレッションの違い

「手で動かす」キーフレームに対し、「数値で動かす」ことができるのがエクスプレッションです。ただし手動設定したキーフレームとエクスプレッションを組み合わせる手法もあります。

キーフレーム エクスプレッション
手動でポイントを設定 自動計算による動的制御
手直しの際は個別調整が必要 パラメータ変更で一括調整
複雑な動作は大量のキーフレーム 短いコードで複雑な表現
直感的だが時間がかかる 効率的だが学習コストあり

基本操作

エクスプレッションの有効化

aftereffects_00

エクスプレッションを使い始めるには、まずプロパティに対してエクスプレッション機能を有効化する必要があります。方法は非常にシンプルです。

  1. 対象のレイヤーを選択
  2. 制御したいプロパティ(位置、回転、不透明度など)を表示
  3. プロパティ名の左にあるストップウォッチアイコンを Alt(Windows) または Option(Mac) を押しながらクリック 1

するとプロパティの数値が赤色などに変わり、「エクスプレッションエディター」への入力が可能になります。

  • 1
    プロパティを選択した状態でメニューから「アニメーション」→「エクスプレッションを追加」でもOK。ショートカットはAltまたはOption + Shift + ^です。

エクスプレッションエディターの使い方

エクスプレッションエディターはコードを記述するためのテキスト入力欄です。ここでJavaScriptベースの記述を行います。

なおエディターの下端をドラッグすることでサイズ調整が可能です。メニューから「編集」→「環境設定」→「スクリプトとエクスプレッション」と進めばカスタマイズもできます。

aftereffects_01

[=]アイコンをクリックするとエクスプレッションの有効 / 無効を切り替えられます。またエクスプレッション自体を削除したいときは再度ストップウォッチアイコンをAlt / Optionキーを押しながらクリックしてください。

ではさっそくエクスプレッションを実行してみましょう。

  1. 適当なシェイプレイヤーを作って配置します。

    shape_layer_00

    今回は「多角形」を作成

  2. エクスプレッションを有効化

    コンテンツ > 多角形 1 > トランスフォームなどと展開し、「回転」プロパティのストップウォッチ上でAlt / Optionを押しながら左クリックします。

  3. エクスプレッションを入力

    aftereffects_02

    エディター内にデフォルトで記述されているcontent("多角形 1").transform.rotationといった文字列をすべて消去して time*100 と入力してみて下さい。

  4. 回転し続けるシェイプレイヤーとなりました。

構文

上記のように、エクスプレッションは1行だけでも動作します。例えば

time*100

これは経過秒数に100を掛けた値を表しています。適用するプロパティによって回転や歪曲のアニメーションになるわけです。先ほどは回転のプロパティに書き込んだため、1秒で100度回転する動きとなりました。

コメントや変数もJavaScriptと同じ書き方が可能です。さしあたり次の点をおさえておきましょう。

  • 大文字と小文字は区別される
  • コメントの書き方
    • コメントはコードの実行に影響を与えないメモのようなもので、主に説明や一時的に無効化したいコードを書くために使用する
    • スラッシュ2つ[//]を付けると以降がコメントとなる
    • コメントが複数行にわたる場合は[/*]と[*/]で挟む
    • 特にチーム作業では、コメントを充実させて他者が理解できるようにする
  • 四則演算
    • 加減乗除: それぞれ[+][-][*][/]
  • 変数について
    • [let]や[var]を前に付けると「変数」を作れる(「変数の宣言」という)
      • 省略しても動いてしまう場合は多いが、中級レベル以上を目指すなら最初から[let]や[var]を使う習慣をつけたほうが良い
      • 本記事ではひとまず違いを意識せず[let]を使用していく
    • 変数は「値を入れる箱」で、中身は変更可能
      • 記述の簡略化やアニメーションの調整などに絶大な威力を発揮する
      • コードがシンプルなうちに「値を変数に入れる癖をつける」のがお勧め

// これはコメントです
// let speed = 100;
let speed = 50; // 行末にもコメントを書き込めます
/*
こちらは
複数行にわたる
コメントです
*/
time*speed;

上の場合1行目と2行目はコメントなので無視されます。3行目で speed と名付けた変数に 50 を代入。4〜8行目もコメント。9行目、前述の通り time は経過秒数です。最終的に、このエクスプレッションは経過秒数に 50 を掛けた値を返します。

このように、エクスプレッションは上から順に処理され、最後の値がプロパティに適用される仕組みです。こういったことを「エクスプレッションが値を返す」と表現しています。

JavaScriptでは1つの処理ごとにセミコロン「;」を入れます。無くても動作することが多いものの、入れるのが基本です。ただし本記事ではコードが1行だけの場合セミコロンは省略します。

適用できる場所

エクスプレッションは、「位置」「回転」「不透明度」や各エフェクトのパラメータなど、ほぼすべてのプロパティに適用できます。

配列について

エクスプレッションは、 time のような単一の数値のほか、複数のデータをまとめて返すことも可能です。これを配列といいます。なかなかの初心者キラーなのですが、避けて通ることはできません。

「回転」や「不透明度」などのプロパティは1つの数値で制御可能です。したがって、エクスプレッションは値を1つだけ返せばよいことになります。

ところが例えば「アンカーポイント」はX座標とY座標の2つの数値が必要です。このためエクスプレッションは一度に2つの値を返さなくてはなりません。このとき2次元の配列を使うことになります。具体的には[100, 200]といった書き方です。「100」「200」を配列の要素といい、連番のインデックスを持っています。インデックス0番の要素がX座標で100、1番の要素がY座標で200です。

let anchor = [100, 200]

こういった形で変数 anchor を配列として用いるとき、 anchor[0] = 100 anchor[1] = 200 です。初めのうちは理解に苦しむところですが、ひとまず理屈は抜きにして、配列のインデックスは0から始まるものだと認識してください。このような書き方で配列の値を参照することができます。

以上の基礎を押さえれば、あとは「何をどう動かすか」を考えるだけです。

次の章では、実際によく使われるエクスプレッションの具体例を紹介します。

初級編

まずは「回転」や「位置」に適用して動きを確認すると理解が早まります。 time wiggle は短いコードで効果が出やすく、初心者にもおすすめです。

エクスプレッション例

time — 時間ベースのアニメーション

概要: コンポジションの経過時間(秒)を利用し、値を変化させます。

用途: 回転や移動を時間に応じて自動進行させたいとき。

例①:

time*100

「回転」プロパティに適用すると、1秒あたり100度で回転。

例②:

[time*300, time*100]

「位置」プロパティに適用すると、1秒あたり右に300ピクセル、下に100ピクセル移動。

※ 位置を表すためX軸とY軸の2つの情報が最低限必要です。そうした場合は[]の中に2つの値を記述し、間に[,]を挟みましょう。これが前述の「配列」です。

※ 3つ目の値を記述するとZ軸方向の成分になります(レイヤーが3Dの場合)。

wiggle — ランダムな揺れ(滑らか)

概要: 指定した回数・振幅でランダムに値を揺らします。

用途: 手ブレ、炎や煙の揺れ、自然な動きの演出。

書式: wiggle(回数/秒, 振幅)

例:

wiggle(3, 50)

「位置」に適用すると毎秒3回、最大50ピクセルの範囲でランダム移動。

random — ランダムな揺れ(飛び飛び)

概要: 指定した範囲内でランダムな値を返します。

用途: 位置・色・不透明度などをバラバラに設定するとき。

書式: random(最小値, 最大値)

例:

posterizeTime(3);
[random(0, 50), random(0, 50)];

「位置」に適用すると、X軸・Y軸ともに0から50の間でランダムに変化。

wiggle() が「滑らかに変化する(連続的)」のに対して、 random() は「フレームごとにサイコロを振り直す(離散的)」イメージです。毎フレーム値が変わるため「パカパカ切り替わる」印象になります。

posterizeTime(3) で1秒を3フレームに抑えました。これにより秒間3回ランダム値が更新されています。

linear / ease — 値の補間

概要: 入力値を指定範囲で別の値に変換。

用途: 時間や他プロパティの値をもとにスムーズに変化させる。

書式: linear(t, tMin, tMax, value1, value2)

ease(t, tMin, tMax, value1, value2)

これではよく分からないと思いますので、次の具体例をご覧ください。

例:

linear(time, 1, 3, 20, 100)

「不透明度」に適用するとタイムライン上の1秒から3秒の場所にかけて20%から100%に不透明度が変化。

linear のかわりに ease を使用すると変化が滑らかになります。

random() linear() のように、かっこ内に何らかの値を渡して別の値を受け取る仕組みをAEではメソッドと呼んでいます。渡す値のことを引数(ひきすう)、受け取る値のことを戻り値といい、メソッドごとにその数や種類が決まっています。

loopIn / loopOut — 繰り返し処理

概要: 設定したキーフレームを繰り返し再生します。

用途: 背景アニメや周期的な動きの自動化。

書式:

loopIn(type="cycle", numKeyframes=0)

loopOut(type="cycle", numKeyframes=0)

※ ループの種類は "cycle""pingpong" "offset" "continue" の4種類。省略して何も指定しなければ "cycle" になります。実際に変更して試すと動きの違いが分かるでしょう。

numKeyframes を指定すると適用されるキーフレームの数を設定できます。この引数を省略すると0として扱われ、全てのキーフレームがループします。

例:

loopOut("pingpong")

「位置」プロパティに始点や終点など適当に3つのキーフレームを設定後 "pingpong" を指定したことで、3点間を移動するアニメーションが無限ループしています。

loopIn は始点キーフレームまでエクスプレッションが適用され、 loopOut は終点キーフレーム以降に適用されます。

valueAtTime — 他のタイミングの値を参照

概要: 任意の時刻の値を取得できます。

用途: ディレイをかけた動き、トレイル(残像)効果の作成。

例:

thisComp.layer("UFO").transform.position.valueAtTime(time-0.2)

  1. 「UFO」ファイルを読み込んで配置
  2. 適宜キーフレームを打つなどしてUFOの動きを作る(今回はモーションスケッチで作成しました)
  3. 「star」ファイルを読み込んで配置
  4. 「star」レイヤーの[トランスフォーム]を展開し、「位置」プロパティのエクスプレッションに上のコード例を記載
  5. UFOの動きに0.2秒遅れて追従する動きとなります

※この例ではstarをもう1つ(0.4秒遅れで)追加しています。

このエクスプレッションは何をしているの?

[thisComp.layer("UFO").transform.position]の部分は「"UFO"レイヤーの位置プロパティの値」を指しています。そして[.]に続けて[valueAtTime(time-0.2)]と記述することで、0.2秒遅れたタイミングにおける"UFO"の位置プロパティの値を取得しています。その値が"star"の位置プロパティに適用され、結果としてディレイ効果となるわけです。

"UFO"や"star"の部分は実際のレイヤー名に合わせてください。1文字でも違っていると動作しません。筆者も経験がありますが、ちょっとしたスペルミス等で容易にエラーが生じます。これを防ぐために次の「ピックウィップ」を使用するのが適切です。

aftereffects_03

ピックウィップはAEのエクスプレッション作成を強烈に楽にしてくれる機能です。初心者が式を手打ちしなくてもリンクできるため、習熟初期にぜひマスターしておきたいポイントと言えます。

渦巻きのアイコンからリンク先のプロパティへドラッグすると、(今回の例では)[thisComp.layer("UFO").transform.position]という文字列が自動的に挿入されます。その後ろに[.valueAtTime(time-0.2)]と書けばOKです。あるいは変数[controller]に格納して次のように書くこともできます。


let controller = thisComp.layer("UFO").transform.position;
controller.valueAtTime(time-0.2);

ちなみに、この動きをエクスプレッションを使わず実現したい場合は、UFOの位置キーフレームを丸ごとstarの位置プロパティにコピペし、0.2秒分ずらせばOKです。

if — 条件分岐

概要: 条件に応じて異なる値を返します。

用途: アニメーションのON/OFF、段階的変化の制御。

書式:

if (条件式) {
(条件式が成り立った場合の処理)
} else {
(条件式が成り立たなかった場合の処理)
}

例: テキストレイヤーを作成し、「ソーステキスト」のプロパティに記述

// 現在の時間(秒単位)
let t = Math.floor(time);
// 3の倍数か、3が含まれるかチェック(0を除く)
if (t !== 0 && (t % 3 === 0 || t.toString().includes('3'))) {
"アホ";
} else {
t.toString();
}

あらゆるプログラミングの基礎となる条件分岐です。この例では、3の倍数または3を含む数字の時は「アホ」と出力、それ以外の場合はタイムライン上の秒数を出力しています。

Math.floor() は与えた数を小さい方の整数に切り捨てます。例えば Math.floor(2.75) は2。これにより秒の整数部分を得ています。

t !== 0 はtが0でないという意味です。数理的には0は3の倍数ですのでアホと表示されるのですが、いきなりアホから始まるのもどうかと感じたため、0秒台は除外することにしました。

&&は論理積(AND)。全条件がtrueのときのみ全体がtrueになります。

t % 3 は剰余演算子。tを3で割った余りを返します。 t % 3 === 0 で「tは3の倍数」です。

t.toString() はt(数値)を文字列に変換2.includes('3') によって、その文字列に3が含まれていればtrueを返します。

|| は論理和(OR)。左側か右側の条件のどちらかがtrueなら全体がtrueになります。

以上により「0でない」かつ「3の倍数である、または数字の中に3が含まれる」場合を判定。結果によって出力を分けています。

  • 2
    AEのテキストレイヤーは数値のままでも表示されますが、明示的に文字列化しておいた方が確実です。

Math — 数学関数

概要: JavaScript標準の数学関数が利用可能。[sin]や[cos]などで周期的な動きを作れます。

用途: バウンド、回転運動、波のようなアニメーション。注目を集める装飾、UIアニメーション。VJ、MV、ライブ演出など。

例①:

Math.sin(time * 2) * 25

数学的性質を生かしたアニメーションを作れるのが特徴です。「回転」プロパティに適用すると、中心を軸に±25度の範囲で時計の振り子のような動きをします。

三角関数の場合、角度の単位はラジアン(弧度法)を用いることに注意してください。上のエクスプレッションだと、例えばタイムライン上のちょうど2秒の場所では、  sin4[rad] はおよそ-0.76ですので、それに25を掛けた[-19.0]が回転角度の概算値 3 として返されます。

※ 角度(度数法)で考えたい場合は、 degreesToRadians() という関数を活用しましょう。カッコ内に角度を記述すると、ラジアンの値が返ってきます。これにより、「1秒 = 1度」とか「1秒 = 10度」などと対応させることで、「秒数 = 角度」という感覚でアニメーションを作れます。次の例をご参照ください。

  • 3
    精度の問題でAEのプロパティとしては多少ずれた値になります。

例②:

[Math.tan(time * 2) * 100, 0]

X座標に tan() を適用し、Y座標は0で固定しました。タンジェントの性質上、「位置」プロパティに適用すると2秒ごとに漸近線が来て、横方向へ急激に変化します。

同じ動きを度数法で書くならこのような形です( Math.PI は円周率)。

let deg = time * (2 * 180 / Math.PI);
[Math.tan(degreesToRadians(deg)) * 100, 0];

秒数を意識しながら、周期的な躍動感を演出できるでしょう。

例③:

let r = (Math.sin(time) + 1) / 2;
let g = (Math.cos(time) + 1) / 2;
let b = 1 - r;
[r, g, b, 1]

「カラー」に適用してください。カラーのプロパティは赤、緑、青、アルファチャンネルの値からなる4次元の配列です4 。全ての値を0.0~1.0の範囲で指定しなければなりません。After EffectsのUIで表示される255が1.0になる計算です。

  • R成分 → サイン波で周期的に0〜1を往復
  • G成分 → コサイン波(サイン波と90°ずれる)で0〜1を往復
  • B成分 → 1 - r なので、Rが強いときに弱く、Rが弱いときに強くなる
  • アルファチャンネル → 1で固定

この例では「赤と青がシーソー」しながら緑が90°ずれたタイミングで入ります。するとRGBがぐるぐる入れ替わるレインボー的な動きになりました。RGBの値にサイン波・コサイン波を用いて位相をずらすことで、シンプルなコードでも複雑に見える色変化を実現できます。

  • 4
    アルファチャンネルはAEのほとんどのカラー系プロパティで無視されますので、いつも1(完全不透明)に固定するぐらいで構いません。もし透明度をコントロールしたいなら「不透明度」プロパティに対してエクスプレッションを書く必要があります。
値の可視化
aftereffects_04

エクスプレッションのON / OFFを切り替える[=]アイコンの隣にある“小さなグラフのアイコン”をクリックすると、設定した色を可視化することができます。

aftereffects_05

その状態でグラフエディターに切り替えれば、エクスプレッションが返している値をグラフで見ることができます。ちなみに薄い赤・緑・青の線はエクスプレッションを適用しない場合の値です。

いかがでしょうか。ここまで、基本的にはコピペするだけで、適用するプロパティが間違っていない限り問題なくエクスプレッションが動作したはずです。「コードを書く怖さ」はかなり減ってきたのではないでしょうか。

応用編

ここまでに紹介したエクスプレッションを応用して、実際の案件や制作現場で使える具体例を示します。

蛍の乱舞

  1. 新規コンポジションを作成

    今回の画面サイズは1280x720でデュレーションを10秒間としました。

  2. ひな形を作成

    「1匹目の蛍」となるシェイプレイヤーを作成し、蛍っぽい色に塗ります。そしてレイヤー名を「蛍1」に変更してください。なお作例ではエフェクト「ブラー (ガウス)」を使って輪郭をぼかしてあります。

  3. 「トランスフォーム」から「位置」プロパティに次のエクスプレッションをコピペします。

    // 個体ごとのランダム初期位置
    seedRandom(index, true);
    let startX = random(0, 1280);
    let startY = random(0, 720);
    // 揺らぎ
    let flutter = wiggle(0.5, 150) - value;
    [[startX + flutter[0]], startY + flutter[[1]]];
    
    
  4. 「不透明度」プロパティに次のエクスプレッションを書き込みます。

    // 個体差のある明滅
    seedRandom(index + 100, true);
    let freq = random(0.2, 0.4);
    let phase = random(0, 2 * Math.PI);
    let flicker = Math.sin(time * freq * 2 * Math.PI + phase);
    let opacityVal = linear(flicker, -1, 1, 10, 100);
    // フェードアウト
    if (time < 8) {
    opacityVal
    } else {
    ease(time, 8, 9, opacityVal, 0)
    }
    
    
  5. シェイプレイヤー「蛍1」を好きな数だけコピペ(Ctrlまたはcommand + D)してください(作例では40匹です)。

位置エクスプレッション解説

一見ひいてしまうかも知れないコード量ですが、1行ずつ見てみると大したことはしていません。

seedRandom(index, true); 

👉これは読んで字のごとく「ランダム値のタネ」を仕込んでいるものです。

第1引数の値が同じであれば同じ乱数が発生します。今回は蛍ごとに初期位置や動きを変えたいため、全ての蛍で違う乱数を発生させなければなりません。つまり蛍ごとに第1引数を変える必要があるわけです。そういった際に好都合なのがindexで、各レイヤーに割り当てられた連番を指しています(AEの画面上では[#]アイコンの列に表示)。これを使うと後々コピペしてレイヤーを増やすたびに違う番号が割り当てられ、ランダム値のタネを変えることが可能です。

また第2引数をtrueとすることで、ランダム値が時間とともに変化するのを抑止できます。この引数のデフォルトはfalseなのですが、その場合は1フレームごとに乱数が発生し蛍がワープしてしまいます。簡単に確認できますので、ぜひ試してみてください。

以降の行で画面内に蛍を配置しています。

let startX = random(0, 1280);
let startY = random(0, 720);

👉X軸は0から1280までの間の乱数。Y軸も同様です。

let flutter = wiggle(0.5, 150) - value;

👉0.5から150ピクセルまでの範囲で滑らかなランダム値を発生させています。

valueはプロパティが元々持っている既定値、エクスプレッション適用前の値です。実はwiggle()は暗黙的にvalueを使用する(valueを基準に変動させる)仕組みなので、wiggle(0.5, 150)だけでは1匹目の位置に引きずられて2匹目以降の蛍の位置が偏ってしまいます。そのためvalueを引いて相殺しました。1匹目の蛍を左上(0, 0)に配置したのと同じ効果を持ちます。

[startX + flutter[0], startY + flutter[1]]; 

👉変数flutterはインデックス0にX座標、インデックス1にY座標の値を格納した配列です。この行ではXYそれぞれの初期値に揺らぎ値を足して配列を返しています。

これで蛍が初期位置に配置されて動くはずです。

不透明度エクスプレッション解説

seedRandom(index + 100, true); 

👉位置プロパティと同様、蛍ごとに異なるランダムシードを設定。

let freq = random(0.2, 0.4); 

👉点滅の周波数をランダムに設定。1秒間に0.2〜0.4回の点滅周期です。蛍ごとに点滅スピードを微妙に変えて自然な印象を与えます。

let phase = random(0, 2 * Math.PI); 

👉点滅の開始タイミング(位相)をランダム化。すべての蛍が同じタイミングで光ったり消えたりしないようにしています。2 * Math.PIはラジアン単位の 360°(1周)です。

let flicker = Math.sin(time * freq * 2 * Math.PI + phase); 

👉サイン波で明滅を作成。-1 〜 1 の範囲で周期的に上下します。これが蛍の「光り具合」の元データです。time * freq で秒数に応じて周期的に変動。* 2 * Math.PIの部分でサイン波を「1周期 = 1秒×freq」に調整。+ phaseとして個体ごとに開始位置をずらしています。

let opacityVal = linear(flicker, -1, 1, 10, 100); 

👉サイン波の出力(-1〜1)を不透明度の範囲(10〜100%)に変換しています。linear(value, oldMin, oldMax, newMin, newMax) で値をリマップしました。蛍は完全には消えず、10〜100% の範囲で光り方が優しく変わります。

// フェードアウト
if (time < 8) {
opacityVal
} else {
ease(time, 8, 9, opacityVal, 0)
}

👉タイムライン上の8秒までは作成した明滅を適用。9秒にかけて不透明度を0にし、フェードアウトさせています。9秒から10秒までの間は完全ブラックです。

カスタマイズ案

筆者は蛍の乱舞を目にした経験に乏しいため、想像で補って映像化してみました。

カスタマイズのポイントとしては

  • 蛍の形に個体差を加える
    • 円形でなく楕円形など虫の形に近づけても良さそう
  • 色自体を微妙に揺らす
  • 「グロー」エフェクトをかけて発光させる
    • 発光にも淡く揺らぎを加える
  • 残像のような軌跡を作る

などで、雰囲気を洗練させることができるでしょう。

アンビエント映像

「蛍」のバリエーションです。より抽象的な映像を目指しました。

  1. 新規コンポジションを作成

    こちらも画面サイズは1280x720、デュレーションを10秒間としています。

  2. パーティクルレイヤーの作成

    新規平面レイヤーを作成し、名前を「Particle 1」、サイズを16x16に設定。そして3DレイヤースイッチをONにしてください。

    次にエフェクトから「描画」→「楕円」を選択。幅と高さを16、内側のカラーを白、外側のカラーを適当に設定し(作例は[#00BBFF]です)、柔らかさを90%に変更します。このあたりのパラメータは好みに応じて変えていただいて構いません。

    さらにエフェクト「ブラー (ガウス)」を軽め(3.0)に適用しています。

  3. エクスプレッションを記述

    Particle 1レイヤーの位置プロパティで次のエクスプレッションを入力します。

    // ランダムシードを設定
    seedRandom(index, true);
    
    // 各パーティクルの個性を設定
    const radius = random(200, 800); // 螺旋の半径
    const speed = random(0.3, 1.5); // 回転速度
    const verticalSpeed = random(20, 80); // 上下移動速度
    const phase = random(0, Math.PI * 2); // 初期位相(開始角度)
    const wobble = random(0.5, 2.0); // ふらつき具合
    
    // 中心点
    const centerX = thisComp.width / 2;
    const centerY = thisComp.height / 2;
    const centerZ = random(-500, 500); // Z軸方向のランダムな中心
    
    // 時間経過による角度
    const angle = time * speed + phase;
    
    // 基本的な螺旋運動
    const baseX = centerX + Math.cos(angle) * radius;
    const baseY = centerY + Math.sin(angle) * radius;
    const baseZ = centerZ + Math.sin(time * verticalSpeed * 0.01) * 300;
    
    // ふらつきを追加(有機的な動き)
    const wobbleX = Math.sin(time * wobble) * 50;
    const wobbleY = Math.cos(time * wobble * 1.3) * 50;
    const wobbleZ = Math.sin(time * wobble * 0.7) * 100;
    
    // 波のような大きな動き
    const waveX = Math.sin(time * 0.5 + index * 0.1) * 150;
    const waveY = Math.cos(time * 0.7 + index * 0.15) * 150;
    
    // 最終位置
    const finalX = baseX + wobbleX + waveX;
    const finalY = baseY + wobbleY + waveY;
    const finalZ = baseZ + wobbleZ;
    
    [[finalX, finalY, finalZ]];
    
    

    同じレイヤーの不透明度プロパティには次のように入力してください。

    // ランダムシードを設定
    seedRandom(index, true);
    
    // 各パーティクルの点滅パターン
    const pulseSpeed = random(1, 4); // パルスの速さ
    const minOpacity = random(20, 40); // 最小の明るさ
    const maxOpacity = random(60, 100); // 最大の明るさ
    const offset = random(0, Math.PI * 2); // 位相差
    
    // 滑らかなパルス(正弦波)
    const pulse = Math.sin(time * pulseSpeed + offset);
    const normalizedPulse = (pulse + 1) / 2; // 0-1の範囲に正規化
    
    // 明るさの変化
    const opacity = minOpacity + (maxOpacity - minOpacity) * normalizedPulse;
    
    // カメラからの距離によるフェード(奥行き感)
    const pos = thisLayer.transform.position;
    const distance = Math.abs(pos[[2]]);
    const distanceFade = Math.max(0, 1 - distance / 1000);
    
    opacity * distanceFade;
    
    

    さらに「スケール」プロパティにもエクスプレッションを適用します。

    // Z位置に基づいてサイズを変更(遠近法)
    const pos = thisLayer.transform.position;
    const baseScale = 100;
    const perspective = 1000;
    
    // カメラに近いほど大きく、遠いほど小さく
    const scale = baseScale * (perspective / (perspective - pos[[2]]));
    
    // パルスによる微細な拡大縮小
    const pulseAmount = Math.sin(time * 3) * 0.1 + 1;
    
    [[scale * pulseAmount, scale * pulseAmount]];
    
    
  4. パーティクルの複製・カメラの追加

    Ctrlまたはcommand + Dでレイヤーを複製します。最初は少数から初めて、様子を見ながら増やすのもいいでしょう。

    そして新規カメラレイヤーを作成してください。今回は35mmのプリセットを選択しています。軽く動かしたいので、0秒地点の位置プロパティに[960, 540, -3000]、10秒地点に[960, 540, -1000]といったキーフレームを打ちます。動きは好みで加減してください5

    • 5
      もちろんここにエクスプレッションを適用することも可能です。ただ、あまり激しく動かしても酔いそうな映像になってしまうため、単純に前進する動きとしました。
  5. 背景の追加・仕上げエフェクト

    新規平面レイヤーを作成し、一番下のレイヤーに移動させます。作例のカラーは[#50789B]です。

    仕上げに新しい調整レイヤーを作成し、一番上に設置します。エフェクト「ブラー (ガウス)」を1.5くらいで適用後、エフェクト「スタイライズ」からグローも使ってみました。半径は30、強度は1.2です。

このエクスプレッションは、3D空間でパーティクルを螺旋状に回転させながら、有機的にふらつかせる動きを作っています。

今回は変数の宣言に[const]を使用しました。[let]との大きな違いは再代入ができない点で、一度定義したら後から値(中身)を変更することはできません。文字通り定数(constant)のような扱いが可能です。誤って値を上書きしてしまうのを防げるほか、「この値は変更しない」という意図を明確にしてコードを読みやすくできます6

以下、行単位で解説します。複雑にも見えますが、やっていることは変数を用意して順番に組み立て、最終的な数値を算出しているだけです。「繰り返し」とか「条件分岐」をはじめとするプログラミングらしい技法は限定的で、難解なアルゴリズムもありません。

  • 6
    ただし……今回のエクスプレッションに関しては[let]でも問題なく動作します。[const]の使用はベストプラクティスの1つとして頭に入れておいてください。

位置エクスプレッション解説

1. 準備
// ランダムシードを設定
seedRandom(index, true);

👉「蛍」と同様、各パーティクルに固有の「ランダムの種」を設定

  • index = このレイヤーの番号(1, 2, 3, 4...)
  • true = 「時間が経過してもランダム値を固定する」という設定

なぜ必要?

  • これがないと、すべてのパーティクルが同じ動きをしてしまう
  • レイヤー番号ごとに異なるランダム値を生成することで、各パーティクルに個性が生まれる
  • 何回再生しても同じパターンで動く(再現性がある)
2. 個性の設定
// 各パーティクルの個性を設定
const radius = random(200, 800); // 螺旋の半径

👉このパーティクルの「螺旋の半径」をランダムに決定

  • random(200, 800) = 200〜800の間のランダムな数値を1つ取得
  • const radius = その値を「radius(半径)」という変数に格納
  • 各パーティクルごとに異なる半径になる

視覚的イメージ:

  • radius = 300 → 中心から近い位置で回転(小さな円)
  • radius = 700 → 中心から遠い位置で回転(大きな円)
const speed = random(0.3, 1.5); // 回転速度

👉このパーティクルの「回転速度」をランダムに決定

  • random(0.3, 1.5) = 0.3〜1.5の間のランダムな数値
  • 数値が大きいほど速く回転、小さいほどゆっくり回転
const verticalSpeed = random(20, 80); // 上下移動速度

👉このパーティクルの「上下に揺れる速さ」をランダムに決定

  • 後で Math.sin() と組み合わせて、上下の波打つ動きを作る
  • 数値が大きいほど激しく上下に揺れる
const phase = random(0, Math.PI * 2); // 初期位相(開始角度)

👉このパーティクルの「開始位置(スタート地点の角度)」をランダムに決定

  • Math.PI * 2 = 360度(円1周分)をラジアン単位で表現
  • random(0, Math.PI * 2) = 0〜360度の間のランダムな角度
  • これにより、各パーティクルが円周上の異なる位置からスタートする

視覚的イメージ:

  • phase = 0 → 右側(3時の位置)からスタート
  • phase = Math.PI / 2 → 上側(12時の位置)からスタート
  • phase = Math.PI → 左側(9時の位置)からスタート

なぜ必要?

  • これがないと、すべてのパーティクルが同じ位置から回転を始めてしまう
  • ランダムにすることで円周上に均等に散らばる
const wobble = random(0.5, 2.0); // ふらつき具合

👉このパーティクルの「ふらつく速さ」をランダムに決定

  • 後でこの値を使って、小刻みに揺れる動きを作る
  • 数値が大きいほど速くふらつく
3. 中心点と角度の計算
// 中心点
const centerX = thisComp.width / 2;
const centerY = thisComp.height / 2;

👉画面の中心位置を計算

  • thisComp.width = コンポジションの幅(1280px)
  • thisComp.height = コンポジションの高さ(720px)
  • / 2 = それぞれを2で割る
  • 「蛍」のように値を直接書く(ハードコーディング)よりも再利用しやすいコードになる
const centerZ = random(-500, 500); // Z軸方向のランダムな中心

👉各パーティクルの「奥行き方向の中心位置」をランダムに決定

  • Z軸 = 奥行き(カメラに近い・遠い)
  • random(-500, 500) = -500〜500の間のランダムな値
  • マイナス値 = カメラに近い、プラス値 = カメラから遠い

視覚的イメージ:

  • centerZ = -500 → 手前側で回転
  • centerZ = 0 → 中央の奥行きで回転
  • centerZ = 500 → 奥側で回転

効果:

  • 各パーティクルが異なる奥行きで回転するため、立体的な空間が生まれる
// 時間経過による角度
const angle = time * speed + phase;

👉「今この瞬間、パーティクルが円周上のどの角度にいるか」を計算

  • time = コンポジション開始からの経過時間(秒)
  • time * speed = 時間 × 速度 = 現在の角度
    • 時間が経つほど角度が大きくなる = 回転する
  • + phase = 初期角度を加える

具体例: あるパーティクルで speed = 1.0 phase = 0 の場合

  • time = 0秒 → angle = 0
  • time = 1秒 → angle = 1(約57度回転)
  • time = 2秒 → angle = 2(約115度回転)
  • time = 6.28秒 → angle = 6.28(ちょうど1周)

なぜこの式?

  • 時間と速度を掛け算することで「継続的な回転」が実現する
  • phaseを加えることで各パーティクルが異なる位置からスタートする
4. 螺旋運動を作る
// 基本的な螺旋運動
const baseX = centerX + Math.cos(angle) * radius;

👉パーティクルのX座標(横位置)の基本値を計算

  • Math.cos(angle) = 角度をコサイン関数に入れる → 円周上のX座標(横位置)が-1〜1で返る
  • * radius = 半径を掛ける(円の大きさを決定)
  • centerX + = 中心位置を基準にする

具体例: centerX = 960, radius = 400, angle = 0のとき

  • Math.cos(0) = 1
  • baseX = 960 + 1 × 400 = 1360(画面右側)

視覚的イメージ: 時間が経つと angle が増加 → cos の値が変化 → X座標が左右に動く → 円運動

const baseY = centerY + Math.sin(angle) * radius;

👉パーティクルのY座標(縦位置)の基本値を計算

  • Math.sin(angle) = 角度をサイン関数に入れる → 円周上のY座標(縦位置)が-1〜1で返る
  • 上記X成分と同様に円運動のY成分を計算

cos と sin の組み合わせ:

  • cos(angle) がX座標を、 sin(angle) がY座標を決定
  • この2つを組み合わせることで円運動が実現
const baseZ = centerZ + Math.sin(time * verticalSpeed * 0.01) * 300;

👉パーティクルのZ座標(奥行き)の基本値を計算し、上下に波打つ動きを加える

  • time * verticalSpeed * 0.01 = 時間 × 速度 × 調整値
    • 時間が経つと値が増加していく
  • Math.sin(...) = サイン関数に入れる
    • -1〜1の値が返ってくる(波のように上下)
  • * 300 = 振幅(波の高さ)を300pxにする
  • centerZ + = 中心位置を基準にする

なぜ 0.01 を掛ける?

  • verticalSpeedは20〜80と大きな値なので、そのまま使うと波が速すぎる
  • 0.01を掛けることで、ゆっくりした上下運動に

視覚的イメージ:

  • 時間が経過 → sin の値が -1 → 0 → 1 → 0 → -1 と周期的に変化
  • Z座標が前後に波打つように動く
  • 螺旋のような3D的な動きが生まれる
5. 揺れを追加して最終位置を決定

このままでは円運動が完璧すぎますので、「不完全さ」を加えて有機的な動きにします。

// ふらつきを追加(有機的な動き)
const wobbleX = Math.sin(time * wobble) * 50;

👉X方向(横)の小刻みなふらつきを計算

  • time * wobble
    = 時間 × ふらつき速度
    • wobbleは各パーティクルで異なる(0.5〜2.0)
  • Math.sin(...)
    = -1〜1の周期的な値
  • * 50
    = 左右50pxの範囲でふらつく

効果:

  • 基本の円運動に小さな揺れが加わる
  • ロボットのような機械的な動きを避けられる
const wobbleY = Math.cos(time * wobble * 1.3) * 50;

👉Y方向(縦)の小刻みなふらつきを計算

  • Math.cos
    = サインではなくコサインを使用
    • XとYで異なる関数を使うことで、動きに多様性が出る
  • * 1.3
    = X方向とは異なる速度で揺らす
    • これにより、単純な円運動にならず、複雑な軌跡になる
  • * 50
    = 上下50pxの範囲でふらつく

なぜ 1.3 を掛ける?

  • XとYの揺れの周期をわざとずらすため
  • 同じ周期だと単純な斜めの動きになってしまう
  • 異なる周期にすることで、予測不可能な動きになる
const wobbleZ = Math.sin(time * wobble * 0.7) * 100;

👉Z方向(奥行き)の小刻みなふらつきを計算

  • * 0.7
    = XやYとは異なる速度(さらに遅く)
  • * 100
    = 前後100pxの範囲でふらつく(XYより大きい振幅)

なぜZ方向は振幅が大きい?

  • 奥行き方向の動きは視覚的に分かりにくいため
  • 大きめの振幅にすることで3D感を強調
// 波のような大きな動き
const waveX = Math.sin(time * 0.5 + index * 0.1) * 150;

👉パーティクル全体が波のようにうねる動きを作成

  • time * 0.5
    = ゆっくりとした周期(wobbleより遅い)
  • + index * 0.1
    = レイヤー番号に応じて位相をずらす
    • レイヤー1: +0.1
    • レイヤー2: +0.2
    • レイヤー3: +0.3
  • * 150
    = 左右150pxの範囲で動く

効果:

  • 各パーティクルが微妙に異なるタイミングで左右に動く
  • 「波」が群れ全体を通過していくような表現
  • 連続性のある動き

視覚的イメージ: レイヤー1が右に動き始めたとき、レイヤー2はまだ中央、レイヤー3は左にいる → 波のような連続した動きに見える

const waveY = Math.cos(time * 0.7 + index * 0.15) * 150;

👉Y方向(縦)の大きな波打つ動きを作成

  • time * 0.7
    = Xとは異なる速度(少し速い)
  • + index * 0.15
    = Xとは異なる位相差
  • Math.cos
    = Xとは異なる関数(多様性)

なぜXとYで速度を変える?

  • 同じ速度だと斜めに動くだけになる
  • 異なる速度にすることで、複雑な軌跡を描く
// 最終位置
const finalX = baseX + wobbleX + waveX;

👉すべての動きを合成して、最終的なX座標を決定

  • baseX
    = 基本の円運動(大きな動き)
  • + wobbleX
    = 小刻みなふらつき(中くらいの動き)
  • + waveX
    = 全体の波(大きな動き)
  • これら3つを足し合わせる

重ね合わせの原理: 複数の動きを足し算することで、複雑な動きを生成可能

視覚的イメージ:

  1. まず大きな円を描く(base)
  2. その円の軌道上で小刻みに揺れる(wobble)
  3. さらに全体が波のように動く(wave) → 予測不可能な美しい軌跡
const finalY = baseY + wobbleY + waveY;
const finalZ = baseZ + wobbleZ;

👉同様にすべての動きを合成して最終的なY、Z座標を決定

なぜwaveZがない?

  • Z方向(奥行き)は、baseとwobbleだけで十分な3D感が出る
  • waveを加えると動きが複雑すぎて分かりにくくなる可能性がある
[finalX, finalY, finalZ];

👉計算した3D座標を配列として返す

  • 【x, y, z】
     = 3D位置を表す配列
  • この値が「位置」プロパティに適用される
  • After Effectsがこの座標にパーティクルを配置する

配列の意味:

  • 0番目(finalX)= X座標(横)
  • 1番目(finalY)= Y座標(縦)
  • 2番目(finalZ)= Z座標(奥行き)

不透明度エクスプレッション解説

パーティクルの明るさを制御して滑らかに点滅させながら、カメラから遠いパーティクルを暗くすることで奥行き感を表現していきます。ramdom()は頻出のため説明は適宜省略します。各行末のコメントもご参照ください。

1. 準備、個性の設定
// ランダムシードを設定
seedRandom(index, true);
// 各パーティクルの点滅パターン
const pulseSpeed = random(1, 4); // パルスの速さ
const minOpacity = random(20, 40); // 最小の明るさ
const maxOpacity = random(60, 100); // 最大の明るさ
const offset = random(0, Math.PI * 2); // 位相差

pulseSpeed(パルス速度)

  • パルス(明滅)の速さを決定
  • 値が大きいほど速く点滅
  • 各パーティクルが異なる速度で明滅することで、一斉に光るのを防ぐ

minOpacity(最小不透明度)

  • 最も暗いときの不透明度
  • 完全に消えない(0にしない)ため、パーティクルの存在感を保つ

maxOpacity(最大不透明度)

  • 最も明るいときの不透明度
  • ランダムにすることで、常に明るいパーティクルと控えめなパーティクルが混在

offset(位相差)

  • 点滅開始のタイミングをずらす
  • これがないとすべてのパーティクルが同時に明滅する
  • ランダムにすることで、波打つような明滅パターンが生まれる
2. パルス効果の計算
// 滑らかなパルス(正弦波)
const pulse = Math.sin(time * pulseSpeed + offset);

  • time * pulseSpeed = 時間に速度を掛けて、連続的に増加する値を作る
  • + offset = 各パーティクルの開始タイミングをずらす
  • Math.sin(...) = -1〜1の範囲で周期的に変化する値を取得
const normalizedPulse = (pulse + 1) / 2; // 0-1の範囲に正規化

  • pulse + 1 = -1〜1の範囲を 0〜2に変換
  • / 2 = 0〜2を 0〜1に正規化

なぜ正規化(値の整形)が必要?

  • Math.sin() の結果は-1〜1だが、不透明度の計算には0〜1の範囲が便利
  • 後の計算で使いやすくするため
// 明るさの変化
const opacity = minOpacity + (maxOpacity - minOpacity) * normalizedPulse;

  • (maxOpacity - minOpacity) = 明るさの変化幅を計算
    • 例: max=100, min=20 なら、変化幅=80
  • * normalizedPulse = 0〜1の値を変化幅に掛ける
    • normalizedPulse=0 のとき → 変化量=0
    • normalizedPulse=1 のとき → 変化量=80
  • minOpacity + = 最小値を基準に加算

計算例: minOpacity=20, maxOpacity=100, normalizedPulse=0.5 の場合

  • 変化幅 = 100 - 20 = 80
  • 変化量 = 80 × 0.5 = 40
  • 最終的な不透明度 = 20 + 40 = 60

👉不透明度が minOpacity と maxOpacity の間を滑らかに行き来する

3. 距離によるフェード(奥行き感)
// カメラからの距離によるフェード(奥行き感)
const pos = thisLayer.transform.position;

  • 位置エクスプレッションで計算された最終的な3D座標を取得
const distance = Math.abs(pos[2] - centerZ);

  • pos【2】 = 現在のZ座標
    • 正の値 = カメラから遠い
    • 負の値 = カメラに近い
  • Math.abs(...) = 絶対値を取得(マイナスをプラスに変換)
  • どちら側に離れても距離として扱う

距離例:

  • pos【2】 = -300 → distance = 300(カメラに近い)
  • pos【2】 = 0 → distance = 0(基準位置)
  • pos【2】 = 500 → distance = 500(カメラから遠い)
const distanceFade = Math.max(0, 1 - distance / 1000);

  • distance / 1000 = 距離を1000で割って正規化
    • distance = 0 → 0 / 1000 = 0
    • distance = 500 → 500 / 1000 = 0.5
    • distance = 1000 → 1000 / 1000 = 1.0
  • 1 - ... = 反転(近いほど大きく、遠いほど小さく)
    • distance = 0 → 1 - 0 = 1.0(最も明るい)
    • distance = 500 → 1 - 0.5 = 0.5(半分の明るさ)
    • distance = 1000 → 1 - 1.0 = 0(消える)
  • Math.max(0, ...) = 負の値を0にクランプ
    • 数値が0未満であれば0に置き換え、0以上であれば元の値のままにする
    • distance > 1000 の場合も0を維持

なぜ 1000 で割る?

  • 1000px以上離れたパーティクルを完全に消すため
  • この値を変えることでフェードの範囲を調整可能
    • 小さくする(例: 500)→ 早く消える
    • 大きくする(例: 2000)→ 遠くまで見える
opacity * distanceFade;

  • パルス効果で計算した不透明度に距離フェード係数を掛ける
  • 掛け算なので、どちらかが0なら結果も0

計算例: opacity=80, pos[2]=600 の場合

  • distance = 600
  • distanceFade = 1 - 600/1000 = 0.4
  • 最終不透明度 = 80 × 0.4 = 32

結果:

  • 基準位置(Z=0)付近のパーティクルは本来の明るさで光る
  • 遠ざかるほど暗くなり、1000px以上離れると完全に消える
  • これにより、3D空間の奥行き感が強調される
パラメータ調整のヒント

明滅を激しくする

  • pulseSpeed の範囲を大きく(例: 2〜8)

明滅を穏やかにする

  • pulseSpeed の範囲を小さく(例: 0.5〜2)

常に明るいパーティクルを増やす

  • minOpacity を大きく(例: 50〜70)
  • maxOpacity も大きく(例: 80〜100)

スケールエクスプレッション解説

1. 基本設定
// Z位置に基づいてサイズを変更(遠近法)
const pos = thisLayer.transform.position;
const baseScale = 100;
const perspective = 1000;

👉遠近法計算に必要な基本パラメータを設定

pos(現在位置)

  • 位置エクスプレッションで計算された最終的な3D座標を取得
  • Z座標(奥行き)の値を使ってスケールを計算する

baseScale(基本スケール)

  • 基準位置(Z=0)でのスケール値
  • 100 = 元のサイズ(100%)
  • この値を基準に、遠近法によって拡大縮小される

perspective(透視距離)

  • カメラの「視点の強さ」を決定する値
  • 値が小さい → 遠近感が強い(遠くのものがより小さく見える)
  • 値が大きい → 遠近感が弱い(サイズ変化が緩やか)
  • 1000 は適度な遠近感を与える標準的な値
2. 遠近法によるスケール計算
// カメラに近いほど大きく、遠いほど小さく
const scale = baseScale * (perspective / (perspective - pos[2]));

👉Z座標に基づいて遠近法に従ったスケール値を計算

遠近法の式

scale = baseScale × (perspective ÷ (perspective - Z座標))

この式は、実世界のカメラの遠近法を数学的にシミュレートしています。

式の各要素:

  1. perspective - pos【2】   = カメラとオブジェクトの「有効距離」
    • -500(カメラに近い)→ 1000 - (-500) = 1500
    • 0(基準位置)→ 1000 - 0 = 1000
    • 500(カメラから遠い)→ 1000 - 500 = 500
  2. perspective / (perspective - pos【2】) = スケール係数
    • 有効距離が大きい → 係数が小さい → 縮小
    • 有効距離が小さい → 係数が大きい → 拡大
  3. baseScale * = 基本サイズに係数を掛ける
3. パルス効果の追加
// パルスによる微細な拡大縮小
const pulseAmount = Math.sin(time * 3) * 0.1 + 1;
[scale * pulseAmount, scale * pulseAmount];

👉遠近法による基本スケールに、時間経過による微細な拡大縮小を加える

pulseAmount の計算

  • time * 3 = 時間を3倍速で増加
    • 比較的速いペースでパルスが変化
    • 3 は速度調整値(大きいほど速く脈打つ)
  • Math.sin(time * 3) = -1〜1の範囲で周期的に変化
    • time = 0 → sin = 0
    • 時間経過で -1 → 0 → 1 → 0 → -1 と繰り返す
  • * 0.1 = 振幅を0.1に制限
    • -0.1 〜 0.1 の範囲に変換
    • 小さな値なので微細な変化になる
  • + 1 = 基準値を1.0にする
    • 0.9 〜 1.1 の範囲に変換
    • 1.0 = 元のサイズ
    • 0.9 = 10%縮小
    • 1.1 = 10%拡大

pulseAmount の変化例:

  • time = 0秒 → sin(0) = 0 → pulseAmount = 0×0.1 + 1 = 1.0
  • time = 0.26秒 → sin(0.78) ≈ 0.7 → pulseAmount ≈ 1.07
  • time = 0.52秒 → sin(1.57) ≈ 1.0 → pulseAmount = 1.1
  • time = 1.05秒 → sin(3.14) ≈ 0 → pulseAmount = 1.0

最終スケールの適用

[scale * pulseAmount, scale * pulseAmount];

  • scale * pulseAmount = 遠近法スケール × パルス係数
  • 配列【 X, Y 】として返す(X方向とY方向に同じスケールを適用)
  • Z方向のスケールは指定しない(2D形状のため)

計算例:

遠近法で scale = 150 の場合

  • pulseAmount = 0.9 → 最終スケール = 150 × 0.9 = 135
  • pulseAmount = 1.0 → 最終スケール = 150 × 1.0 = 150
  • pulseAmount = 1.1 → 最終スケール = 150 × 1.1 = 165

結果: 基本サイズ(150)を中心に、135〜165の範囲でゆっくりと拡大縮小を繰り返す

なぜパルス効果を加えるの?

視覚的効果:

  • 完全に静止したサイズだと「貼り付けた画像」のように見える
  • 微細な拡大縮小により、「呼吸している」印象を与える
  • 不透明度のパルスと組み合わさり、より有機的な表現になる

パルスの特徴:

  • すべてのパーティクルが同じタイミングで拡大縮小
  • 不透明度と異なり、 seedRandom() を使わない
  • 全体が統一的に「呼吸」することで、群れとしての一体感が生まれる

まとめ

ここまでエクスプレッションの具体例を見てきました。JavaScript のプログラミング経験がなくても、基本的なエクスプレッションは十分使えることが実感できたかと思います。

改めて実務での活用ポイントをまとめると次の通りです(本記事では言及していない部分も含みます)。

エクスプレッションが特に有効な場面

  • 繰り返しの動き: ループ、周期的なアニメーション
  • ランダム性が必要な表現: 自然な揺れ、バラつき
  • 複数オブジェクトの連動: 1つの変更で全体を制御
  • 数値ベースのアニメーション: カウンター、プログレスバー
  • パラメトリックなデザイン: 数値(パラメータ)調整で全体が変化する表現

従来の手法では困難だった表現

  • 数百個のオブジェクトの統一的な制御
  • 物理法則に基づいた自然な動き
  • 音声やデータと連動したビジュアライゼーション
  • パラメトリックな生成的デザイン

エクスプレッションを使うメリット

  • 効率性: キーフレーム設定の手間を大幅削減
  • 柔軟性: パラメータ調整で即座に全体が変化
  • 正確性: 数学的に正確な動きを実現
  • 再利用性: 一度書けば他のプロジェクトでも活用可能
  • 拡張性: レイヤー数が増えても管理が容易

単純な動きはキーフレーム手打ちの方が直感的な場合もありますが、上記のようなメリットを踏まえて「コードで制御する」という新しいアプローチに慣れることで、従来の手法では不可能だった創造的な表現が可能になります。

またエクスプレッションを学ぶことは、プログラミング的思考を身につけることでもあります。

  • 問題を分解して考える力
  • 論理的に手順を組み立てる力
  • 試行錯誤から学ぶ力

これらのスキルは、After Effects以外の場面でも活きてくるでしょう。

After Effectsのエクスプレッションは、最初は難しく感じるかも知れません。しかし、基本を理解し、少しずつ実践を重ねることで、必ず使いこなせるようになります。

さらなる学習リソース

Adobe公式のエクスプレッションに関するヘルプはこちらです。

最も信頼できる情報源と言えます。ただ書き方がなかなか高度で、少なくとも初心者向けではありません。また[thisComp.layer("Layer B").effect("Gaussian Blur")("Blurriness")]というコード例がなぜか[thisCompayer(“Layer B”)ffect(“Gaussian Blur”)(“Blurriness”)]と書かれているなど、英語から日本語への翻訳・変換ミス (?) も散見されます。

ただし習熟するにつれて内容が分かるようになるはずです。いずれ必ず参考にしていくべき資料ではありますので、折に触れて読んでみてください。

掲載したエクスプレッションについては動作確認を入念に重ねたものの、バージョンや設定の違い等の理由により動かない可能性はあります。またコードにミスがあるかも知れません。そのような場合はお問い合わせください。

EDITED BY
LIBERE