こんにちは!
みなさん、AviUtlでスクリプト制御って使ってますか?
使ったことない人の中には、なんだこのテキストボックスと思ってる人もいるんじゃないでしょうか。(ぼくが以前そうだったんですが)
今はもう沢山のアニメーション効果が配られているので、わざわざスクリプト制御を使うことはあまり多くないと思います。
だけど、使い方を知らないままにしておくのはもったいない!…気がするので、スクリプト制御をオススメするためにこの記事を書きました。
とはいえ、スクリプト制御の解説記事とか動画なんかは沢山ありますから、今見ることができるものより簡潔にわかりやすく書きたいと思っています。
(こういう記事ってメチャクチャ詳しい人に怒られそうでビクビクしています…
スクリプト内でここ違うよ!というのがあったらやさしく教えてください…)
この記事は、音MAD Advent Calendar 2021 に参加しています。
(音MADに限らないような記事ですがご容赦ください)
目次
0. 本記事内の記法について
◆スクリプト
フィルタ効果 "スクリプト制御"内に書き込むスクリプトです。
見た目のわかりやすさを重視しているので、他のフィルタ効果を適用した上でこの記事のスクリプトをコピペした場合、記事の内容と異なる動作になることがあります。
ここにスクリプトが書かれます。
クリックで全選択できます。
◆変数, 関数
文中に変数名や関数名がある場合、このような表記で書くことがあります。
◆座標
(x,y,z)=(10,20,30) と書かれていた場合、X=10, Y=20, Z=30 の座標を示します。
◆単位
[単位] の形式で記載しています。
1. 基本的なオブジェクト描画をしてみる
スクリプト制御だけでなく、プログラミングも未経験のかた向けにプログラムの書き方も含め説明します。
パラメータを直接指定する
専用の変数に数値を代入してあげれば、そのとおりにオブジェクトを表示してくれます。
obj.ox = 100
obj.oy = -50
上のスクリプトの1行目、obj.oxはオブジェクトの相対座標Xを表す変数です。
(変数) = (変数/式/数値)の形で書くことで、" = "の左側に書いた変数に右側の値を代入することができます。
以下は値を設定可能な変数の例です。AviUtlに同梱されているlua.txtに一覧があります。
obj.ox obj.oy obj.oz |
基準座標からの相対座標[px] (それぞれ、X,Y,Z) |
obj.rx obj.ry obj.rz |
回転角度[度] (それぞれ、X軸,Y軸,Z軸) |
obj.zoom | 拡大率[倍] |
obj.alpha | 不透明度(0.0(透明)~1.0(不透明)) |
表中に"基準座標"とありますが、これは設定ダイアログ上の座標となります。
上記のスクリプトを記述したオブジェクトを配置すると以下の画像のようになります。
設定ダイアログの"X","Y"が0のもの(左)は(x,y)=(100,-50)に描画されているのに対して、"X"に-150,"Y"に-50を設定したもの(右)は(x,y)=(-50,-100)に描画されています。
オブジェクトを動かす
最終的に完成させたいのは動画ですので、オブジェクトを動かす必要があります。
オブジェクトを動かすには、時間の情報を含めて変数に代入してあげます。
obj.ox = -200 + 200 * obj.time
obj.oy = 50
obj.timeはオブジェクト基準での現在の時間[s]を表す変数です。
オブジェクトはフレーム毎に異なる座標に描画されています。
同様に時間を表す変数は以下のようなものがあります。
obj.totaltime | オブジェクトの総時間[s] |
obj.frame | オブジェクト基準での現在のフレーム番号 |
obj.totalframe | オブジェクトの総フレーム数 |
obj.framerate | フレームレート |
また、上記のスクリプトに書かれているように、計算記号が扱えます。
+ | - | * | / | % | ^ |
加算 | 減算 | 乗算 | 除算*1 | 余剰*2 | べき乗 |
変数を使って動きを連動させる
変数は新しく作ることができます。変数を使うと同じ値の使い回しがしやすくなり、スクリプトの見た目もスッキリします。
t = obj.frame / obj.totalframe
obj.ox = -200 + 400 * t
obj.oy = -100 + 200 * t
(画像内では便宜上1~60Frameと書いていますが、実際のobj.frameの値は0~59です)
変数を宣言する(作る)ためには、(作りたい変数) = (変数/式/数値)と書きます。代入式の左辺が新しい変数名に変わっただけです。
このとき宣言された変数は、他のオブジェクトでも使用することが可能です。
obj.ox = -200 + 100 * obj.time
obj.oy = -100
pink_x = obj.ox
obj.ox = 200
obj.oy = pink_x + 100
上記のスクリプトでは、ピンクの円のX座標の値を変数pink_xに入れ、緑の円のY座標にそれを連動させています。
このように変数を宣言するととても便利なのですが、注意が必要な点があります。
上の例からもわかるように単に宣言した変数はどこからでも読み取ることができてしますため、誤動作を起こすおそれがあります。
誤動作を引き起こさないためには、変数名をできるだけ他の変数と被りにくい名前にするのが無難です。
宣言した変数を他のオブジェクトで利用することがなければ、以下のようにlocal修飾子を付け加えればその変数は他のオブジェクトからは見えなくなります。
配布されているアニメーション効果などはlocalをつけていることがほとんどです。
local t = obj.frame / obj.totalframe --このtは他オブジェクトから参照不可
obj.ox = -200 + 400 * t
obj.oy = -100 + 200 * t
--以降はコメントになり、この部分は実行されません。併せて覚えておくと便利です。
関数を使う①(Lua標準ライブラリ)
関数は、簡単に言うと「何らかの処理のまとまり」のことです。
関数を呼び出すことで、複雑な処理を短い記述で終えることができます。
呼び出し時に数値等を渡すと、演算結果を返してくれるものもあります。
(このとき渡す値は引数, 返ってくる結果は戻り値と呼びます。)
AviUtlのスクリプトはLuaというプログラミング言語を使って書かれます。
そのため、Luaの標準ライブラリの関数を使うことができます。
local r = 200 --半径
local deg = 360 * (obj.frame / obj.totalframe) --角度[度]
local rad = math.rad(deg) --角度[ラジアン]
obj.ox = r * math.sin(rad)
obj.oy = r * -math.cos(rad)
math.rad()はラジアンを求める関数です。引数に角度[度]を与えると、戻り値に角度[ラジアン]が返ってきます。
math.sin(), math.cos()はそれぞれラジアンからサイン, コサインを返します。
これらをX座標, Y座標に割り当てることで円軌道でオブジェクトを移動させることができます。
上記以外にもLuaの標準ライブラリで使用できる関数はたくさんあります。
以下に関数の一覧が書かれています。
Lua 5.3 リファレンスマニュアル(6 - 標準ライブラリ)
(Lua5.1のマニュアルに数学関数の使い方が載っていなかったので5.3のものを置いています。5.1はこちら)
関数を使う②(AviUtl関数)
AviUtl専用に定義されている関数も使用可能です。
テキストオブジェクト内でのみ使用できるobj.mes()を使うと、動的なテキストを作成することができます。この記事内のアニメgif画像内のテキストもこれで書いています。
テキストオブジェクト内でスクリプトを記述するには、<? ~ ?>で囲みます。(テキストボックスに書いてください。)
local r = 100 --半径
local deg = 360 * (obj.frame / obj.totalframe) --角度[度]
local rad = math.rad(deg) --角度[ラジアン]
obj.ox = r * math.sin(rad)
obj.oy = r * -math.cos(rad)
pink_x, pink_y, pink_deg = obj.ox, obj.oy, deg --テキスト出力用
<? mes("deg = " .. pink_deg) --角度を表示 ?>
(obj.mes()に渡せる文字列は" ~ "で囲んだ文字列か変数です。..を使うと文字列を連結できます。)
ただ、これだと見た目がヒドいのでstring.format()を使ってテキストを整形します。こちらはLuaの標準ライブラリ関数です。
<?mes(string.format("x = %d\ny = %d\ndeg = %d",pink_x,pink_y,pink_deg))?>
<?mes(string.format("x = %4d\ny = %4d\ndeg = %3d",pink_x,pink_y,pink_deg))?>
<?mes(string.format("x = %6.1f\ny = %6.1f\ndeg = %04d",pink_x,pink_y,pink_deg))?>
string.format()には複数の引数をカンマで区切って渡すことができます。
1番目の引数にはテキストフォーマットを与えます。
\nは改行文字です。これを使用せずそのまま改行しても出力テキストには反映されません。
%dはフォーマット指定子です。2番目以降の引数の値が指定したフォーマットのテキストに変換され、その場所に置き換わります。
フォーマット指定子は主に以下のものを使用します。(数値は適宜読み替えてください)
%d | 整数 |
%4d | 4文字の整数(足りない分はスペースで埋める) |
%05d | 5文字の整数(足りない分は0で埋める) |
%f | 小数 |
%.3f | 小数(整数部は%dと同じ、小数部3桁) |
%6.2f | 小数点含め6文字の小数(小数部2桁) |
%c | 1文字 |
%s | 文字列 |
%% | "%"の文字 |
関数を使う③(フィルタ効果)
フィルタ効果の適用をスクリプト内で行うこともできます。
t = math.floor(obj.time) --小数点以下切り捨て
obj.effect("マスク","サイズ",40,"マスクの反転",1,"type",1)
obj.effect("画像ループ","横回数",t+1,"縦回数",t+1)
obj.effect()では、1番目の引数にフィルタ名を指定します。
2番目以降は、パラメータ名と値を組にして書き連ねます。
フィルタ名, パラメータ名は正しく指定する必要があります。
オブジェクトファイルやエイリアスを作成することで、これらを確認することができます。
条件分岐, 繰り返し
条件によって処理を変更したり、同じ処理を繰り返し行うことができます。
これを使いこなせれば効率的に動画づくりができます。
local t = obj.time
local n = 12 --個数
local r = 100
local rad,x,y,a = 0,0,0,0 --a:透明度
if( math.floor(t) % 2 == 1 ) then
a = 0.25 --透明
else
a = 1 --不透明
end
for i=1,n do
rad = (2 * math.pi) * (i / n)
x = r * math.sin(rad)
y = r * -math.cos(rad)
obj.draw(x,y,0,1,a)
end
オブジェクトの点滅はif文で制御しています。このスクリプトでは、obj.timeの秒数が奇数の場合透明度を下げており、1秒毎に点滅を切り替える動作になっています。
if文は以下のように書きます。インデントはなくても問題ありませんが、あるほうが読みやすくなります。elseif文は分岐が3パターン以上になる場合に追加します。
if(条件1) then
条件1成立時の処理
elseif(条件2)
条件1不成立時に、条件2成立時の処理
else
全条件不成立時の処理
end
条件判定に使う演算子は以下のものを使います。
A == B | A ~= B | A > B | A < B | A >= B | A<=B |
AとBが 等しい |
AとBが 異なる |
AがBより 大きい |
AがBより 小さい |
AがB以上 | AがB以下 |
条件文にはnot, and, orも使えます。not(A and B)と書くと、AとB両方が成立している場合を除いて条件成立となります。
円形配置はfor文で制御しています。描画座標の計算と描画、を指定した回数だけ繰り返しています。
for文は以下のように書きます。
for 繰り返し変数 = 初期値, 最終値, ステップ do
繰り返し処理
end
中の処理に対して、繰り返し変数の値が初期値から最終値までステップの値を加算しながら変化します。ステップの値を省略した場合は繰り返し変数の値は1ずつ増加します。(for 繰り返し変数 = 初期値, 最終値 do
のように書きます。)
他にも繰り返し処理を行う構文はありますが、ここでは省略します。
繰り返し処理で描画を複数行う場合、obj.draw()の関数を複数回実行して使います。相対座標XYZ, 拡大率, 不透明度, XYZ軸回転角度を引数に与えることができます。
obj.draw(相対座標X, 相対座標Y, 相対座標Z, 拡大率, 不透明度, X軸回転角度, Y軸回転角度, Z軸回転角度)
引数は省略することができますが、設定したい値までの引数は指定する必要があります。例えば拡大率だけを引数として与えたい場合は、相対座標XYZも引数に与えます。
注意事項として、obj.draw()を実行したあとは、obj.effect()の内容や、"スクリプト制御"の下に配置されたフィルタ効果の適用は行われません。
フィルタ効果を適用したい場合は、obj.effect()を引数無しで実行するとフィルタ効果を遡って実行した上で描画を行うことができます。(文字小さくてごめんなさい)
2.いろいろやってみる
ここからは、実際の製作例と併せて書いていきます。
ランダム、座標の切り上げ整数化
こんな動きをします。
ゲームの再現をするにあたって、描画座標の整数化をする必要がありました。
num=5
seed=1
for i=0,num-1 do
r=obj.rand(0,11,seed+i,1) --中心からの距離
deg=obj.rand(0,359,seed+i,1)
seed=seed+1
rad=math.rad(deg)
Xa=math.cos(rad) --X速度
Ya=math.sin(rad) --Y速度
x=math.ceil(Xa*(obj.frame+r))
y=math.ceil(Ya*(obj.frame+r))
obj.draw(x,y)
end
obj.rand()は乱数を生成するAviUtl関数です。描画ごとに値を変更するためにループの度にシード値を変更しています。
math.ceil()は小数点以下を切り上げるLua関数です。
このスクリプトの流れは以下のとおりです。
パーティクル出力系のアニメーション効果を使ってこの動きを作れないか考えたのですが、色々めんどくさくて結局スクリプトを書いて終わりました。
格子状に並べて、個別に描画するかを決める
"画像ループ"のフィルタ効果と似ていますが、オブジェクト一つひとつに対して描画するかどうかを選択できるようにしています。
同じオブジェクトを決まった場所に大量に描画する必要があり、そういうことができるアニメーション効果がなかったのでスクリプト制御での自作に至りました。
local nx,ny=6,4 --X,Y個数
local dx,dy=50,50 --X,Yオブジェ間距離
local en={1,0,1,0,0,1,0,1,1,0,1,0,1,0,1,0,1,0,1,1,0,1,0,1} --1:表示 0:非表示
for ix=1,nx do
for iy=1,ny do
x=dx*((2*ix-nx-1)/2)
y=dy*((2*iy-ny-1)/2)
if en[ix+(iy-1)*nx]==1 then
obj.draw(x,y)
end
end --iy
end --ix
ここでは、配列を使用しています。配列を使用すると連続した複数のデータを扱うことができます。このスクリプトでは、描画するかどうかのフラグを管理しています。(二次元配列にしてもいいんですが、何度も書き換える必要があったので更新しやすさを考えて一次元にしています。)
配列の使い方説明については省きます。
このスクリプトの流れは以下のとおりです。
これについてはアニメーション効果として配布しています。トラックバーで一部の値の設定ができるので便利です。(local変数にしていない問題があるのでそのうち[いつ?]直します)
外部Luaファイル読み込み, 仮想バッファ
多くのオブジェクトに後で一括変更可能な共通した値を使いたいのと、指定した範囲の中だけで描画を行いたいのと、登場する方向をオブジェクトごとに個別で設定しやすくしたいのをスクリプト制御で解決させました。
local dir = 6 --方向指定:4←/8↑/2↓/6→
dofile("script/自作/outer.lua") --aviutl.exeからの相対パス または 絶対パス
local st = OUTER_VALUE --移動開始距離
local ed,x,y = 0,0,0
local ratio = math.min(obj.time, OUTER_TIME) / OUTER_TIME --stからedまでの進捗(0~1)
local dist = ((ed-st) * ratio) + st --描画距離
x,y = Calcdir(dir,dist)
obj.setoption("drawtarget","tempbuffer",OUTER_VALUE,OUTER_VALUE) --描画先を仮想バッファに変更
obj.draw(x,y) --仮想バッファにオブジェクトを描画
obj.draw(-100,-100);obj.draw(100,100) --ついでに角にも置いてみる
obj.load("tempbuffer") --仮想バッファ読み込み ここで表示される
OUTER_TIME = 0.4 --登場時間[s]
OUTER_VALUE = 200 --登場距離や枠の大きさなどの値
function Calcdir(dir,param)
local x,y = 0,0
if dir==4 then
x=-param --左から
elseif dir==6 then
x=param --右から
elseif dir==2 then
y=param --下から
else
y=-param --上から
end
return x,y
end
dofile()でluaファイルとして保存したスクリプトを読み込んでいます。
引数に指定するパスはaviutl.exeからの相対パスまたは絶対パスなります。
読み込んでいるouter.luaには全オブジェクト共通で使用する変数と関数を書いています。
outer.luaに定義されているOUTER_TIMEはオブジェクトの移動時間です。これを過ぎるとオブジェクトが停止します。
同じくouter.luaに定義されているCalcdir()は、方向と距離を引数に渡すと描画用のX,Y座標を返してくれます。Calcdir(4,100)とした場合、(x,y)=(-100,0)に描画されます。
標準描画でX,Y座標を両方書き換えるのがめんどくさいので、変える数字が1つで済むやつを作りました。
オブジェクト側の説明に戻ります。
今回は一定の大きさの領域にオブジェクトを描画したいので仮想バッファを使用しました。仮想バッファにオブジェクトを描画後、それを通常通り描画しています。一旦付箋紙に絵を描いて、それをキャンバスに貼るような感覚です。
まずobj.setoption()でオブジェクトの描画先を仮想バッファに設定しています。
3,4番目の引数は仮想バッファの大きさになります。OUTER_VALUEとしているので、200*200の領域が確保されます。
その後obj.draw()でオブジェクトを描画しますが、仮想バッファに描画しているのでこのままスクリプトを終わっても描画したオブジェクトを見ることができません。
最終的にobj.load()で仮想バッファを読み込むと通常通り描画されます。このとき、仮想バッファからはみ出した部分は描画されません。
動作フローは以下のような感じになります。(これいる?)
ちなみに、実際に動画内で使用しているスクリプトは上のものと違っていて、イージング移動ができるようにしてあります。(こちらのトラックバー対応版のやつです)
しかし、補間方法選択でイージングが選択できなくなってしまったので、メモ程度にスクリプトを置いておきます。(原文そのままなので見苦しいところもあるかもです)
コピペしての使用は自己責任でお願いします。
local dir = 6
--★4←/8↑/2↓/6→
dofile("D:/MAD/bobomb/picross/picross.lua")
local st = getEdir()
local Eno = getEno()
local ratio = math.min(obj.time/Etime,1)
local ed,x,y = 0,0,0
local param = E.easing(Eno,ratio,st,ed-st,1)
x,y = Calcdir(dir,param)
obj.setoption("dst","tmp",Qsize,Qsize)
obj.draw(x,y)
obj.load("tempbuffer")
Etime = 0.4 --★イージング時間
multi = 10 --★グリッドの倍率と合わせる
Qsize = 48*multi
E = require("easing_aviutl")
function getEdir()
--★イージング距離
return 800
end
function getEno()
--★イージング番号
return 23
end
function Calcdir(dir,param)
local x,y = 0,0
if dir==4 then
x=-param --左から
elseif dir==6 then
x=param --右から
elseif dir==2 then
y=param --下から
else
y=-param --上から
end
return x,y
end
3.おわりに
かなりの長文になってしまいましたが、読んでいただきありがとうございました。
正直のところスクリプト制御を勉強しなくても、良い動画を作ることはできるはずです。
しかし、配布されているアニメーション効果だけで思った表現ができない場合、スクリプト制御を使えると…かなりいいと思います。(語彙不足)
スクリプト制御が難しそうと思っていた人は、この記事を読んでなんとなくできそうな気持ちになってくれたら嬉しいです。
リンク
AviUtlのバグを直すプラグイン作った / patch.aul - ニコニコ動画
コンソールが使えるようになります。スクリプトのデバッグに便利です。
おまけ
この記事に記載されているスクリプトをまとめたaupファイルです。
outer.luaも入っています。(読み込む場合は絶対パスで指定してあげてください)