JavaScriptのライブラリ構築時に使えるTips集
先日JumiJpさん主催の「スクリプト勉強会」に参加しました。まぁその後の呑み会目当てだったんですが、皆さん熱心でなんかやる気が出ました。という事で、今回のプログラミング入門は、AEスクリプト(Javascript)のTipsを紹介しつつ、俺ライブラリの構成方法を説明します。
サンプルスクリプトはまとめてここからダウンロードできます。
AE_scriptSample.zip
Tips.1 関数の戻り値
JavaScriptの関数は、returnで返される値は1個だけでたまに困ることがあります。その対処は簡単で、1個の変数に複数の値を収納させてしまえばいいです。
実装方法は以下の方法があります。
- グローバル変数で受け渡す。
- 配列にする。
- Objectを作成し、そのメンバ変数にして返す。
- 関数そのものをObjectにして対処。
具体的のコードは以下の通りです。
tips01_result.jsx
//--------------
//グローバル変数のサンプル
var a;
var b;
function foo1(p0,p1)
{
a = p0 + p1;
b = p0 - p1;
}
foo1(100,10);
alert( "a = "a +"\nb = " b);
//--------------
//配列のサンプル
function foo2(p0,p1)
{
var ret = new Array;
ret.push(p0 + p1);
ret.push(p0 - p1);
return ret;
}
var r2 = foo2(100,10);
alert( "r2[0] = "r2[0] +"\nr2[1] = " r2[1]);
//--------------
//Objectのサンプル
function foo3(p0,p1)
{
var ret = new Object;
ret.inc = p0 + p1;
ret.dec = p0 - p1;
return ret;
}
var r3 = foo3(100,10);
alert( "r3.inc = "r3.inc +"\nr3.dec = " r3.dec);
//--------------
//関数をObject化のサンプル
function foo4(p0,p1)
{
this.inc = p0 + p1;
this.dec = p0 - p1;
}
var r4 = new foo4(100,10);
alert( "r4.inc = "r4.inc +"\nr"4.dec = " r4.dec); |
どれを採用するかは好みですな。僕自身はObjectで返す方法をよく多用します。
Tips.2 関数の引数(arguments)
関数内だけで使える便利なオブジェクトにargumentsがあります。C言語がわかる人ならmain関数のargv配列とほぼ同じといえばわかるものです。
関数に渡された引数を自体をObject化したもので、
arguments.length
で、引数の数、
arguments[index]
で引数そのものにアクセスできます。
これをつかえば、C++でいう関数のオーバーロードを比較的簡単に実装できます。
具体例は後で解説します。
Tips.3 変数の種類を識別する。typeof/instanceof
JavaScriptの変数は柔軟で同じ変数名であっても、コード実行中にObject型を自由に変換できます。このこと自体は便利なんですが、型の不一致のよるエラーがJavaScriptのバグで一番多かったりします。特に関数の引数はJavaScript自体で型チェックは行わないので、プログラマが自前で行う必要があります。そのため、コメントで注意書きとかしておかないと過去作った関数が非常に使い辛くなってしまいます。
変数の型(type)を調べる命令としてinstanceofとtypeofがあります。
instanceofは、変数を指定されたObject型と比較して同じならtrueを返します。自作されたクラスでも比較可能です。
if ( obj instanceof FootageItem) {
// |
typeofは、その型の種類を文字列として返します。
var ts = typeof(obj); // |
AfterEffectsのスクリプト実行環境は、かなり変わってるのでこれを機会にinstanceof/typeofの動作を確認してみました。
tips3_chk_typeof_instanceof.jsx
このスクリプトは、AfterEfefctsで扱える変数にtypeof/instanceofした結果を表示します。

結果をいうと、String/Number/Booleanを除いた変数はinstanceofで識別することが可能でした。String/Number/Booleanだけはtypeofでしか判別できないようです。(After Effects CS4 Windows版でのみの確認)
具体例は後で解説します。
Tips.4 Function Objectについて
JavaScriptでは関数もObjectとして扱います。このことをわかっているといろいろ面白いことができます。C言語でいう関数ポインタやら関数配列が、何も気にしなくても実装できます。
/*
関数もObject
*/
var funcs = new Array;
//配列にしてみた
funcs.push( function(s){ alert(s);});
funcs.push( function(s){ alert(s);});
funcs.push( function(s){ alert(s);});
function dFunc(s)
{
alert(s);
}
//別に定義した関数も配列に追加できる
funcs.push(dFunc);
//こんな感じに実行出来る
funcs[0]("funcs[0]()で実行");
funcs[1]("funcs[1]()で実行");
funcs[2]("funcs[2]()で実行");
funcs[3]("funcs[3]()で実行");
//適当な変数に代入しても
var eFunc = funcs[3];
eFunc("他の変数に代入して実行");
//newで複製も出来る
var fFunc = new funcs[0]("newして実行");
//関数の引数にしても
function gFunc(fnc)
{
if ( fnc instanceof Function){
fnc("関数の引数で実行");
}
}
gFunc(funcs[0]);
// |
作例 args Object
上記のTipsを使ってexample01_arg.jsx
argsクラスは、コンストラクタ引数にargumentsを指定して使います。
var arg = new args(arguments); |
参考用にargsDisp()関数も作成しました。argsの内容を文字列に変換して表示するものです。
以下のコードがexample01_arg.jsxの後半の実行部です。
//抵当なObject
var o = new Object;
o.num = 1;
o.str ="bbb";
//適当な引数を作成
var ac = app.project.activeItem;
if ( ac!= null){
argsDisp(ac,"aaa",12,ac.selectedProperties,ac.selectedLayers,"BBB",false,24,0);
}else{
argsDisp("aaa",12,"BBB",false,24,o);
} |
実行して、引数が処理されてることを確認してみてください。
以下は、argsクラスのメンバ変数です。参照すれば柔軟に引数を解釈できます。
引数の数で処理動作を変えたり、引数を省略したときに適当な値に解釈したりできます。
具体例はまた後で。
| メンバ名 | 型 | 内容 |
|---|---|---|
| count | Number | 引数の数 |
| items | Array | オリジナルの引数の配列 |
| stringAry | Array | String Object引数の配列 |
| numberAry | Array | Number Object引数の配列 |
| booleanAry | Array | Boolean Object引数の配列 |
| compAry | Array | CompItem Object引数の配列 |
| footageAry | Array | FootageItem Object引数の配列 |
| folderItemAry | Array | FolderItem Object引数の配列 |
| fileAry | Array | File Object引数の配列 |
| folderAry | Array | Folder Object引数の配列 |
| layerAry | Array | AVLayer Object引数の配列 |
| propertyAry | Array | Property Object引数の配列 |
| propertyGAry | Array | PropertyGroup Objectg引数の配列 |
| objAry | Array | 上記以外のObject引数の配列 |
Tips.5 deleteのナゾ
Javascriptの命令でdeleteがあります。オブジェクトや変数を削除する命令ですが、実はかなり癖があります。
使うとわかるのですが何故か削除できません。最初びっくりしましたが、仕様を調べると意外と納得できます。
tips5_delete1.jsx
var a = 1;
var a = 1;
alert("delete前 "+a);
delete a;
alert("delete後1 "+a);
a = null;
alert("delete後2 "+a);
a = undefined;
alert("delete後3 "+a); |
この例を実行するとdeleteしても変数が消えてないことがわかります。nullで上書きしてだめです。undefinedを代入して見かけ上消えてるように偽装できますが、変数自体は破棄されません。
これは、deleteの問題ではなく実はvarの仕様のために起きる現象です。
tips5_delete2.jsx
//var を付けない変数は、deleteで消せる。
b = 2;
alert("delete前 "+b);
delete b;
alert("delete後 "+b); |
varを付けない変数は、ちゃんと消せます。
varは変数を定義する命令と思われていますが、JavaScriptでは別にvarを付けなくても変数を定義できます。varは現在のスコープ内でのみ有効な変数(ローカル)を宣言する命令で、varで定義された変数はそのスコープが終了するまで存在が確定されます。
つまり、var定義された変数は消せなくなります。
var宣言しない変数は、どこにあってもグローバル変数になります。
tips5_var.jsx
function foo()
{
var s = "マミ";
var i = 15;
return s + i +"才";
}
function foo2()
{
s = "ほむら";
i = 20000;
return s + i +"才";
}
s = "まどか"
i = 14;
alert("1: "+ foo());
alert("2: "+ s + i +"才");
alert("3: "+ foo2());
alert("4: "+ s + i +"才"); |
上のスクリプトを実行すると実感できると思います。
Tips.6 プロトタイプ
JavaScriptにはprototypeという便利な機能があります。簡単に説明すると既存のObjectに新しいメンバ関数を宣言できる機能です。
例えば文字列から拡張子を切り出す関数で説明して見ます。
tips6_prototype.jsx
function getExt(s)
{
if ((s == null)||(s == undefined)||(s == ""))return"";
var idx = s.lastIndexOf(".");
if ( idx==0){
return s;
} else if ( idx>0) {
return s.substr(idx);
}else{
return "";
}
} |
これを使う場合通常は
var s = "aaa01_012.png"; var e = getExt(s); |
となります。
プロトタイプとして使う方法は、以下の通りになります。
thisの使い方がポイントです。
String.prototype.getExt = function(){ return getExt(this);}
var s = "aaa01_012.png";
var e = s.getExt(); |
これだけだとピンと来ませんが、他のObjectにも適応させると有効性が見えてきます。
File.prototype.getExt = function(){ return getExt(this.name);}
Folder.prototype.getExt = function(){ return getExt(this.name);}
FootageItem.prototype.getExt = function(){ return getExt(this.name);}//新規平面の場合エラーチェックが必要
FootageItem.prototype.getFileExt = function(){ return getExt(this.file.name);}//新規平面の場合エラーチェックが必要
AVLayer.prototype.getExt = function(){ return getExt(this.name);}
AVLayer.prototype.getSrcExt = function(){ return getExt(this.source.file.name);}
CompItem.prototype.getExt = function(){ return getExt(this.name);} |
まぁ、細かいことはgoogle先生で(~_~)/
t_e_t_s_u_oさんのcompoZeroで配布されているae_prototype.jsxは、かなり便利で重宝させてもらってます。
余談ですが、JavaScriptで有名なライブラリprototype.jsがAfterEffectsで使えることが最近知ってびっくりしました。
作例 ものぐさmakeComp
また作例です。example2_makeComp.jsxで作成したmakeComp関数は、引数の解釈を適当に行い自由に記述できるようにしたものです。
//文字列だけなら、それをコンポ名にして作成。
app.project.makeComp("A1");
//数字が二つならwidth/heightの値と解釈
app.project.makeComp("A2",400,300);
//数字が一つならdurationと解釈
app.project.makeComp("A3",1.0);
//数字が三つならwidth/height/durationの値と解釈
app.project.makeComp("A4",400,300,1.5);
//数字が5個ならすべて採用
app.project.makeComp("A5",400,300,1,12,30);
var ac = app.project.activeItem;
if ( ac!= null){
//FootageItemがあればパラメータはそこから貰う。ついでにCompに追加する
app.project.makeComp("A6",ac);
//数字があればそれを優先
app.project.makeComp("A7",ac,100,50);
//FootageItemだけなら、全部採用
app.project.makeComp(ac);
} |
例にはありませんが、FolderItemを指定するとそのFolder内に作成します。数字の値の順番だけ意味を持ちますが、その他は順番を変えても期待通りに実行されます。(ここまでものぐさ仕様にすることはちょっとやりすぎと思いますが)
具体的な実装はソースコードを参照してください。
作例 interate
普段作るスクリプトのほとんどがある一定のパターン(アルゴリズム)になってます。- 対象となるObject(Footage/Comp/Propertyなど)を獲得する。
- 処理前の準備を行う
- 獲得したObjectに決められた処理を行う。大抵ループ処理になる。
- 処理後の後始末をする。
Objectに対する処理位が変わって、その他は毎回同じものになると思います。
今回解説したTipsを使って処理を分割してスクリプトを簡単に組めるクラスを作成して見ました。
example3_iterate.jsx
作成したiterateクラスは、汎用エントリー関数として使えます。
主なメンバ変数・関数は以下の通りです。
| メンバ名 | 型 | 内容 |
|---|---|---|
| activeComp | CompItem | 現在アクティブ状態になっているCompItem |
| targets | Array | 以下のget???関数で得られたObjectの配列 |
| count | Number | targets配列の要素数。 |
| targetFolderItem | FolderItem | 現在アクティブ状態になっているFolderItem |
| func | Function | run()関数が実行されたときに実行される関数 |
| errMes | String | エラーメッセージ |
| init | Function | メンバ変数を初期化する |
| getType | Function | ObjectのTypeを返す。内部で使用 |
| getActiveComp | Function | 現在アクティブなCompItemを確定する。 |
| getActiveFootage | Function | 現在アクティブなFootageItemを確定する。 |
| getActiveFolder | Function | 現在アクティブなFolderItemを確定する。 |
| getSelectedLayers | Function | 現在選択したレイヤを確定する。 |
| getSelectedProperties | Function | 現在選択したプロパティを確定する。 |
| getArgObj | Function | getArg()のサブ関数。指定されたObjectを判別する。内部で使用 |
| getArg | Function | 引数を解釈して、パターンマッチング定数を獲得する。内部で使用 |
| getItems | Function | 指定したFolderItemないにあるObjectをtargetsへ確定する。種類を指定できる |
| getItemsRecursionSub | Function | getItemsRecursion()関数のサブ関数。内部で使用 |
| getItemsRecursion | Function | getItems()と同じ。ただし、再帰してサブフォルダ内のものもリストアップする |
| getSelectedItems | Function | 選択したObjectをtargetsへ確定する |
| getItems | Function | 指定したパターンのObjectをtargetsへ確定する |
| run | Function | get関数で確定されたtargets配列のObjectにfunc関数を適応させる |
以下は、選択されたObjectでcompとfootageのみの名前をリストアップ表示するスクリプトのサンプルです。
var test01 = new iterate;
var result = "";
test01.func = function()
{
try{
if (arguments.length > 0){
result += arguments[0].name+"\n";
}
return true;
}catch(e){
this.errMes +="error in func\n";
return false;
}
}
test01.getSelectedItems("comp footage");
if (test01.count>0){
test01.run();
if ( test01.errMes != ""){
alert("ERROR!\n"+ test01.errMes);
}else{
alert(result);
}
} |
Tips.7 配列のsort
Number/String配列のsortは簡単ですが、Object配列のsort方法を良く忘れるので。比較関数を作成し、sort関数の引数にすればできる。
tips7_objSort.jsx
function obj()
{
this.name = "";
this.value = 0;
if ( arguments.length>=2){
this.name = arguments[0];
this.value = arguments[1];
}
}
//nameでの比較関数
function compareName(o0,o1)
{
//文字列を無理やり数値化して比較
var c0 = 0;
var c1 = 0;
var s0 = o0.name;
var s1 = o1.name;
if ( s0.length > 0){
for ( var i=0; i < s0.length;i++){
c0 += s0.charCodeAt(i);
}
}
if ( s1.length > 0){
for ( var i=0; i < s1.length;i++){
c1 += s1.charCodeAt(i);
}
}
return c0 - c1;
}
//valueでの比較関数
function compareValue(o0,o1)
{
return (o0.value - o1.value);
}
function objAryToString(oa)
{
var ret = "";
if (oa.length <= 0) return ret;
for ( var i=0; i < oa.length; i++){
ret += "name:"+ oa[i].name + " value:" + oa[i].value + "\n";
}
return ret;
}
var ary = new Array;
ary.push(new obj("まどか",30));
ary.push(new obj("ほむら",15));
ary.push(new obj("QB",12));
var mes = "org\n";
mes += objAryToString(ary);
ary.sort(compareName);
mes += "\nsort Name ------\n" + objAryToString(ary);
ary.sort(compareValue);
mes += "\nsort Value ------\n" + objAryToString(ary);
alert(mes); |
Tips.8 他のスクリプトファイルを読み込む
スクリプトの中から外部のスクリプトファイルを読み込む方法は、以下通りtips8_include_libA.js
libA = "LibAだ!"; |
tips8_include_libB.jsxinc
libB = "LibBだ!"; |
tips8_include.jsx
var libA = null;
var libB = null;
//@include "./tips8_include_libA.js"
alert("tips8_include_libA.js : "+libA);
/*
*/
#include "./tips8_include_libB.jsxinc"
alert("tips8_include_libB.jsxinc : "+libB); |
他にFileオブジェクトでファイルを読み込んだ後にeval()関数でロードする方法もあります。
AfterEffectsの場合は、#includepathという命令でインクルードするファイルのパスを指定できるので、#includeを使うのが今のところ一番楽です。
インクルードするファイルの拡張子は”.jsxinc”となってますが、特に何でもいいっぽいです。
#includepath "/c/Program Files/Adobe/Adobe After Effects CS4/Support Files/Scripts/(bryful);../(bryful);./(bryful)" |
上記の例のように、ファイルのパスをセミコロンで複数記述できるので、いちいちパス指定で悩む必要が無くなる。
他にも
#target aftereffects |
とうい命令もあります。
スクリプトの最初にと指定しておけば、ExtendScript Toolkitのエディタにターゲットのアプリ(この場合はAfter Effects)を指定できるます。ESTKでデバッグするときメニューでターゲットアプリを選択する手間が省けます。
Tips.9 変数の保護
スクリプトを多く使っていると、関数名・変数名が重複が問題になることがあります。特にstartupフォルダにスクリプトを入れてライブラリとして登録していると、注意しないと名前が重複して変数・関数が破壊され予期しない動作を起こす危険がある。
これを防止するには以下の方法がある。
基本的にスコープを利用している方法となる
- ユニークな名称にする。
- 無名関数を作成し、その中のみで動作させる
- クラスを定義し、その中のみで動作させる
無名関数とは、名前の無い状態でクラスオブジェクトを定義作成する方法で、具体的には以下のように記述する。
(function(){
//ここから
//個の間に記述
} )(); |
小さなスクリプトを作成する場合は、非常に楽な解決法になります。
ただ注意する点はとして
tips9_scope.jsx
var a = 10;
alert("外1 "+a);
( function(){
a = 20;
//var a = 20; //varつければ遮蔽できる
alert("中 "+a);
} )();
alert("外2 "+a); |
上記のサンプルのようにvarを付けずに使用した変数の場合、スコープを飛び越えてしまうので、まったく無意味になる。
後、ライプラリやpanelのようなUIを使う場合、無名関数を使う方法は基本的に使えない。
その場合は、その他の方法の組み合わせで回避する。
具体的にはObjectを作成してそれに変数・関数を定義していく方法で、C#等の高級言語にあるnamespaceっぽく実装します。
tips9_oreLib.jsx
//--------------------------------------------
var oreLib = new Object;
//--------------------------------------------
//関数の実体を作成して定義
oreLib.counterObj = new function()
{
this.count = 1;
this.inc = function(){ this.count++;}
this.dec = function(){ this.count--;}
}();
//--------------------------------------------
//関数の定義のみ。そのままでは実行できないが、newで作成できる
oreLib.countorClass = function()
{
this.count = 1;
this.inc = function(){ this.count++;}
this.dec = function(){ this.count--;}
}
//--------------------------------------------
//さらにオブジェクトを追加して入れ子構造にもできる
oreLib.sub = new Object;
oreLib.sub.header = "sub:";
oreLib.sub.funcA = function(s){ alert(this.header + "A/" + s);}
oreLib.sub.funcB = function(s){ alert(this.header + "B/" + s);}
//--------------------------------------------
oreLib.sub.sub = new function()
{
var count = 100; //var宣言なら遮蔽される
this.getCount = function() {return count;}
this.setCount = function(v) {count = v;}
}();
oreLib.counterObj.count = 10;
oreLib.counterObj.inc();
oreLib.counterObj.inc();
oreLib.counterObj.dec();
alert(oreLib.counterObj.count);
var newObj = new oreLib.countorClass;
newObj.count = 20;
newObj.inc();
newObj.dec();
newObj.dec();
alert(newObj.count);
oreLib.sub.funcA("test1");
oreLib.sub.funcB("test2");
alert("代入前: " + oreLib.sub.sub.getCount() );
oreLib.sub.sub.count = -50; //代入しても影響受けない
alert("代入後: " + oreLib.sub.sub.getCount() +"変化してない");
oreLib.sub.sub.setCount(200);
alert("代入後: " + oreLib.sub.sub.getCount() +"今度は成功"); |
以上の方法ならば、比較的簡単に安全なライブラリを構築できます。
ただ、varを付けない変数はやはりグローバル変数になるので注意します。
bryful’s ライブラリ
上記の方法で今作成中の僕用のライブラリです。まだ、作成途中で未実装・バグ有りなモノですが、参考にして下さい。
このライブラリを使えば、例えば使わない平面を集めるスクリプトは以下のように簡単に記述できます。
example4_bryfulLibSample.jsx
var main = new bryful.iterate;
main.getItems(ImageType.solid);
if ( main.count>0){
main.fld = bryful.fld.addFolder("使っていない平面");
main.cnt = 0;
main.func = function(f)
{
if (f.usedIn.length == 0){
f.parentFolder = this.fld;
this.cnt += 1;
}
}
app.beginUndoGroup("使っていない平面");
main.run();
if ( main.cnt ==0)
{
main.fld.remove();
alert("無いです");
}else{
alert(main.cnt+"枚集めました");
}
app.endUndoGroup();
} |
bryfulライブラリはここからダウンロードできます。
AE_Lib_bryful.zip
詳細は付属のreadme.txtを参照してください。
こっそりとバージョンアップしていく予定です。
終わりに
startupフォルダに自前ライブラリを入れておくとスクリプト作成が楽になったので、調子に乗っていっぱい追加していたんですが、数が100を越えたあたりからとうとうコンフリフト起こし始めました。人間半年もたつと忘れるもので、同じ機能のライブラリを重複して作成し関数名とか同じものを作ってしまってました。仕様がかなり似かよっていて普通に動かすと動いてしまうので発見に手間取りました。
こりゃあかんと色々調べた結果が今回の記事になりました。
C#みたいにnamespaceで管理できると楽なんですが、多くを求めすぎですな。
最後に今作ってるアプリの宣伝です。
JsxEdit JeSusX

C#テキストエディターエンジンAzuki1.6(http://azuki.sourceforge.jp/)を使うと比較的簡単にエディタが作れることがわかったので作り始めたAfterEffectsスクリプト専用のテキストエディタです。
比較的簡単に編集できるコード補完機能・過去作った自分のスクリプトから簡単にコピー&ペーストできるライブラリ等、スクリプトを作成する為に特化した機能を実装していく予定です。
ここのWEBからダウンロードできます。
実行ファイルのみと.Net framework4のランタイムを含んだ二つのアーカイブがあります。
それでは。



新着記事 : After Effectsユーザーのための、プログラミング入門 その6 AEスクリプトのTips http://bit.ly/hOPkkd
横から失礼しまーす。RT @K240: ゴチになります RT @horonig: いやー勉強になったー RT @AEUSERS After Effectsユーザーのための、プログラミング入門 その6 AEスクリプトのTips http://bit.ly/dIfXKm #aejp
はじめまして。
プラグインに関する質問なので、本記事とは直接関係の無い内容となってしまいますが、
最近の記事ということでこちらにコメントさせていただきます。
レイヤー等のパラメータをファイル出力することが必要になり、
スクリプトで達成できているのですが今回プラグインによる実装をすることになり、
こちらのプラグイン作成入門に、開発準備からの説明があり助かりました。
プラグイン作成入門(第5回)のコメントのtetuさんと同じように、
SDKのAEGPプラグインのProjectDumperを元にして
WindowsXP, AfterEffectsCS3, C/C++(VS2005)の環境で作成しています。
上記プラグインを作成している途中で、
出力形式を選択出来るようなダイアログを表示する必要が出てきたのですが、
SDKに用意されている機能のみでダイアログを生成することは可能でしょうか?
尚、選択はラジオボタン等で行いたいと考えています。
突然で申し訳ありませんが、余裕のある時にでも返信頂ければ助かります。
>SDKに用意されている機能のみでダイアログを生成することは可能でしょうか?
うる覚えですがCS3までならADM(AdobeDialogManager)でダイアログが作成できたはずです。
ただ、かなり面倒で直接WindowsAPIを呼んで自前でダイアログを作成したほうが楽でした。
SDKでもADMより直接APIを使ったりMFCを使ったりするサンプルの方が多いです。
迅速な回答に感謝します。
自分としてもAPIやMFCの方が経験あるので楽だとは思いますが、
方法はあるという事なので、勉強も兼ねて少し調べてみようと思います。
ありがとうございました。
「After Effectsユーザーのための、プログラミング入門 その6 AEスクリプトのTips」AEP Project http://t.co/3w9WTaC #CS5_jp
「After Effectsユーザーのための、プログラミング入門 その6 AEスクリプトのTips」AEP Project http://t.co/3w9WTaC #CS5_jp
スクリプトは経験があるけれどその他の言語はさわったことがないユーザーを対象に、C#/AEのJavaScriptをメインにプログラム全般を解説する連載の第6回です。http://t.co/3w9WTaC #CS5_jp
「After Effectsユーザーのための、プログラミング入門 その6 AEスクリプトのTips」AEP Project http://t.co/3w9WTaC #CS5_jp
@info_nekomataya 変態仕様ですかw無名関数に改良中なのですが、UI Panelは無名関数化できないとあったので、オブジェクトに閉じ込めようとしたら、慣れてないせいで訳わかんなくなってますね。こちらを参考にしています。http://t.co/FcJb7tz